Làm thế nào để bạn tránh getters và setters?


85

Tôi đang có một cái gì đó khó khăn với việc thiết kế các lớp học theo cách oo. Tôi đã đọc rằng các đối tượng phơi bày hành vi của họ, không phải dữ liệu của họ; do đó, thay vì sử dụng getter / setters để sửa đổi dữ liệu, các phương thức của một lớp nhất định sẽ là "động từ" hoặc các hành động hoạt động trên đối tượng. Ví dụ, trong một đối tượng 'Tài khoản', chúng ta sẽ có những phương pháp Withdraw()Deposit()hơn setAmount()vv Xem: Tại sao getter và setter là ác .

Vì vậy, ví dụ, được cung cấp một lớp Khách hàng lưu giữ nhiều thông tin về khách hàng, ví dụ Tên, DOB, Điện thoại, Địa chỉ, v.v., làm thế nào để tránh getter / setters để nhận và thiết lập tất cả các thuộc tính đó? Phương thức loại 'Hành vi' nào người ta có thể viết để điền vào tất cả dữ liệu đó?




8
Tôi sẽ chỉ ra rằng nếu bạn phải đối phó với đặc tả Java Beans , bạn sẽ có getters và setters. Rất nhiều thứ sử dụng Đậu Java (Ngôn ngữ biểu hiện trong jsps) và cố gắng tránh điều đó có thể sẽ ... thách thức.

4
... và, theo quan điểm ngược lại từ MichaelT: nếu bạn không sử dụng thông số JavaBeans (và cá nhân tôi nghĩ bạn nên tránh nó trừ khi bạn sử dụng đối tượng của mình trong bối cảnh cần thiết), thì không cần mụn cóc "get" trên getters, đặc biệt là đối với các thuộc tính không có bất kỳ setter tương ứng nào. Tôi nghĩ rằng một phương pháp gọi name()trên Customerlà càng rõ ràng, hoặc rõ ràng hơn, hơn là một phương pháp được gọi là getName().
Daniel Pryden

2
@Daniel Pryden: name () có thể có nghĩa là cả hai để đặt hoặc nhận? ...
IntelliData

Câu trả lời:


55

Như đã nêu trong một vài câu trả lời và ý kiến, DTOs phù hợp và hữu ích trong một số trường hợp, đặc biệt là trong việc chuyển dữ liệu qua các biên giới (ví dụ serializing để JSON để gửi thông qua một dịch vụ web). Đối với phần còn lại của câu trả lời này, tôi sẽ ít nhiều bỏ qua điều đó và nói về các lớp miền và cách chúng có thể được thiết kế để giảm thiểu (nếu không loại bỏ) getters và setters, và vẫn hữu ích trong một dự án lớn. Tôi cũng sẽ không nói về lý do tại sao loại bỏ getters hoặc setters, hoặc khi nào nên làm như vậy, bởi vì đó là những câu hỏi của riêng họ.

Ví dụ, hãy tưởng tượng rằng dự án của bạn là một trò chơi cờ như Cờ vua hoặc Chiến hạm. Bạn có thể có nhiều cách khác nhau để thể hiện điều này trong một lớp trình bày (ứng dụng bảng điều khiển, dịch vụ web, GUI, v.v.), nhưng bạn cũng có một miền cốt lõi. Một lớp bạn có thể có Coordinate, đại diện cho một vị trí trên bảng. Cách "ác" để viết nó sẽ là:

public class Coordinate
{
    public int X {get; set;}
    public int Y {get; set;}
}

.

Xóa Setters: Bất biến

Trong khi các getters và setters công cộng đều có khả năng có vấn đề, setters là "ác" hơn nhiều của hai. Họ cũng thường dễ dàng hơn để loại bỏ. Quá trình này là một đơn giản - đặt giá trị từ bên trong hàm tạo. Thay vào đó, bất kỳ phương thức nào đã làm đột biến đối tượng sẽ trả về một kết quả mới. Vì thế:

public class Coordinate
{
    public int X {get; private set;}
    public int Y {get; private set;}

    public Coordinate(int x, int y)
    {
        X = x;
        Y = y;
    }
}

Lưu ý rằng điều này không bảo vệ chống lại các phương thức khác trong lớp đột biến X và Y. Để hoàn toàn bất biến hơn, bạn có thể sử dụng readonly( finaltrong Java). Nhưng dù bằng cách nào - cho dù bạn làm cho tài sản của mình thực sự bất biến hay chỉ ngăn chặn sự đột biến công khai trực tiếp thông qua setters- thì đó là mẹo để loại bỏ setters công khai của bạn. Trong phần lớn các tình huống, điều này hoạt động tốt.

Xóa Getters, Phần 1: Thiết kế cho hành vi

Trên đây là tất cả tốt và tốt cho setters, nhưng về mặt getters, chúng tôi thực sự tự bắn vào chân mình trước khi bắt đầu. Quá trình của chúng tôi là nghĩ về tọa độ là gì - dữ liệu mà nó đại diện - và tạo ra một lớp xung quanh đó. Thay vào đó, chúng ta nên bắt đầu với hành vi nào chúng ta cần từ tọa độ. Nhân tiện, quá trình này được hỗ trợ bởi TDD, nơi chúng tôi chỉ trích xuất các lớp như thế này một khi chúng tôi có nhu cầu, vì vậy chúng tôi bắt đầu với hành vi mong muốn và làm việc từ đó.

Vì vậy, hãy nói rằng nơi đầu tiên bạn thấy mình cần Coordinatelà để phát hiện va chạm: bạn muốn kiểm tra xem hai mảnh có chiếm cùng một không gian trên bảng không. Đây là cách "xấu xa" (các nhà xây dựng bị bỏ qua cho ngắn gọn):

public class Piece
{
    public Coordinate Position {get; private set;}
}

public class Coordinate
{
    public int X {get; private set;}
    public int Y {get; private set;}
}

    //...And then, inside some class
    public bool DoPiecesCollide(Piece one, Piece two)
    {
        return one.X == two.X && one.Y == two.Y;
    }

Và đây là cách tốt:

public class Piece
{
    private Coordinate _position;
    public bool CollidesWith(Piece other)
    {
        return _position.Equals(other._position);
    }
}

public class Coordinate
{
    private readonly int _x;
    private readonly int _y;
    public bool Equals(Coordinate other)
    {
        return _x == other._x && _y == other._y;
    }
}

( IEquatablethực hiện viết tắt cho đơn giản). Bằng cách thiết kế cho hành vi thay vì mô hình hóa dữ liệu, chúng tôi đã quản lý để xóa getters của chúng tôi.

Lưu ý điều này cũng có liên quan đến ví dụ của bạn. Bạn có thể đang sử dụng ORM hoặc hiển thị thông tin khách hàng trên trang web hoặc thứ gì đó, trong trường hợp đó một loại CustomerDTO nào đó có thể có ý nghĩa. Nhưng chỉ vì hệ thống của bạn bao gồm khách hàng và họ được đại diện trong mô hình dữ liệu không tự động có nghĩa là bạn nên có một Customerlớp trong miền của mình. Có thể khi bạn thiết kế cho hành vi, người ta sẽ xuất hiện, nhưng nếu bạn muốn tránh các getters, đừng tạo ra một cái trước.

Xóa Getters, Phần 2: Hành vi bên ngoài

Vì vậy, ở trên là một khởi đầu tốt, nhưng sớm hay muộn bạn có thể sẽ gặp phải tình huống bạn có hành vi liên quan đến một lớp, theo một cách nào đó phụ thuộc vào trạng thái của lớp, nhưng không thuộc về lớp. Loại hành vi này là những gì thường sống trong lớp dịch vụ của ứng dụng của bạn.

Lấy Coordinateví dụ của chúng tôi , cuối cùng bạn sẽ muốn thể hiện trò chơi của mình cho người dùng và điều đó có thể có nghĩa là vẽ lên màn hình. Ví dụ, bạn có thể có một dự án UI dùng Vector2để thể hiện một điểm trên màn hình. Nhưng nó sẽ không phù hợp khi Coordinatelớp chịu trách nhiệm chuyển đổi từ tọa độ sang điểm trên màn hình - điều đó sẽ mang tất cả các loại lo ngại về trình bày vào miền cốt lõi của bạn. Thật không may loại tình huống này là cố hữu trong thiết kế OO.

Tùy chọn đầu tiên , được lựa chọn rất phổ biến, chỉ là phơi bày những getters chết tiệt và nói với địa ngục với nó. Điều này có lợi thế của sự đơn giản. Nhưng vì chúng ta đang nói về việc tránh các getters, hãy nói vì lý do chúng ta từ chối cái này và xem những lựa chọn khác có.

Tùy chọn thứ hai là thêm một số loại .ToDTO()phương thức vào lớp của bạn. Điều này - hoặc tương tự - dù sao cũng có thể cần thiết, ví dụ như khi bạn muốn lưu trò chơi, bạn cần nắm bắt khá nhiều trạng thái của mình. Nhưng sự khác biệt giữa làm điều này cho các dịch vụ của bạn và chỉ cần truy cập trực tiếp vào getter là ít nhiều mang tính thẩm mỹ. Nó vẫn còn nhiều "điều ác" với nó.

Tùy chọn thứ ba - mà tôi đã thấy Zoran Horvat ủng hộ trong một vài video Pluralsight - là sử dụng phiên bản sửa đổi của mẫu khách truy cập. Đây là một cách sử dụng và biến thể khá khác thường của mô hình và tôi nghĩ rằng số dặm của mọi người sẽ thay đổi ồ ạt vào việc liệu nó có thêm phức tạp để không có lợi ích thực sự hay liệu đó có phải là một sự thỏa hiệp tốt đẹp cho tình huống hay không. Ý tưởng về cơ bản là sử dụng mẫu khách truy cập tiêu chuẩn, nhưng có các Visitphương thức lấy trạng thái họ cần làm tham số, thay vì lớp họ đang truy cập. Ví dụ có thể được tìm thấy ở đây .

Đối với vấn đề của chúng tôi, một giải pháp sử dụng mẫu này sẽ là:

public class Coordinate
{
    private readonly int _x;
    private readonly int _y;

    public T Transform<T>(IPositionTransformer<T> transformer)
    {
        return transformer.Transform(_x,_y);
    }
}

public interface IPositionTransformer<T>
{
    T Transform(int x, int y);
}

//This one lives in the presentation layer
public class CoordinateToVectorTransformer : IPositionTransformer<Vector2>
{
    private readonly float _tileWidth;
    private readonly float _tileHeight;
    private readonly Vector2 _topLeft;

    Vector2 Transform(int x, int y)
    {
        return _topLeft + new Vector2(_tileWidth*x + _tileHeight*y);
    }
}

Như bạn có thể nói, _x_ykhông thực sự gói gọn nữa. Chúng ta có thể trích xuất chúng bằng cách tạo một IPositionTransformer<Tuple<int,int>>cái mà chỉ cần trả về chúng trực tiếp. Tùy thuộc vào khẩu vị, bạn có thể cảm thấy điều này làm cho toàn bộ bài tập trở nên vô nghĩa.

Tuy nhiên, với các công cụ thu thập công khai, việc thực hiện sai cách rất dễ dàng, chỉ cần lấy dữ liệu ra trực tiếp và sử dụng nó vi phạm Tell, Đừng hỏi . Trong khi sử dụng mẫu này thực sự đơn giản hơn để thực hiện đúng cách: khi bạn muốn tạo hành vi, bạn sẽ tự động bắt đầu bằng cách tạo một loại được liên kết với nó. Vi phạm TDA sẽ rất rõ ràng và có thể yêu cầu làm việc xung quanh một giải pháp đơn giản hơn, tốt hơn. Trong thực tế, những điểm này làm cho việc thực hiện nó trở nên dễ dàng hơn nhiều, OO, cách hơn là cách "xấu xa" mà các getters khuyến khích.

Cuối cùng , ngay cả khi điều đó không rõ ràng ban đầu, trên thực tế có thể có nhiều cách để phơi bày đủ những gì bạn cần là hành vi để tránh cần phải phơi bày trạng thái. Ví dụ: sử dụng phiên bản trước của chúng tôi Coordinatecó thành viên công khai duy nhất Equals()(trong thực tế, nó sẽ cần IEquatablethực hiện đầy đủ ), bạn có thể viết lớp sau trong lớp trình bày của mình:

public class CoordinateToVectorTransformer
{
    private Dictionary<Coordinate,Vector2> _coordinatePositions;

    public CoordinateToVectorTransformer(int boardWidth, int boardHeight)
    {
        for(int x=0; x<boardWidth; x++)
        {
            for(int y=0; y<boardWidth; y++)
            {
                _coordinatePositions[new Coordinate(x,y)] = GetPosition(x,y);
            }
        }
    }

    private static Vector2 GetPosition(int x, int y)
    {
        //Some implementation goes here...
    }

    public Vector2 Transform(Coordinate coordinate)
    {
        return _coordinatePositions[coordinate];
    }
}

Hóa ra, có lẽ đáng ngạc nhiên, tất cả các hành vi chúng ta thực sự cần từ một tọa độ để đạt được mục tiêu của chúng tôi là kiểm tra sự bình đẳng! Tất nhiên, giải pháp này phù hợp với vấn đề này và đưa ra các giả định về việc sử dụng / hiệu suất bộ nhớ chấp nhận được. Đây chỉ là một ví dụ phù hợp với miền vấn đề cụ thể này, chứ không phải là một kế hoạch chi tiết cho một giải pháp chung.

Và một lần nữa, ý kiến ​​sẽ khác nhau về việc trong thực tế điều này là phức tạp không cần thiết. Trong một số trường hợp, không có giải pháp nào như thế này có thể tồn tại, hoặc nó có thể cực kỳ kỳ lạ hoặc phức tạp, trong trường hợp đó bạn có thể trở lại ba điều trên.


Đẹp trả lời! Tôi muốn chấp nhận, nhưng trước tiên một số ý kiến: 1. Tôi nghĩ rằng toDTO () là tuyệt vời, bcuz ur không truy cập get / set, cho phép bạn thay đổi các trường được cung cấp cho DTO w / o phá vỡ mã hiện có. 2. Nói rằng Khách hàng có đủ hành vi để biện minh cho việc biến nó thành một thực thể, làm thế nào bạn có thể truy cập các đạo cụ để sửa đổi chúng, ví dụ: thay đổi địa chỉ / điện thoại, v.v.
IntelliData

@IntelliData 1. Khi bạn nói "thay đổi các trường", bạn có nghĩa là thay đổi định nghĩa lớp hoặc thay đổi dữ liệu? Điều thứ hai chỉ có thể tránh được bằng cách loại bỏ các setters công khai nhưng để lại các getters, vì vậy khía cạnh dto là không liên quan. Cái trước đây không thực sự là lý do (toàn bộ) rằng các getters công cộng là "xấu xa". Xem các lập trình viên.stackexchange.com/questions / 157526 / ví dụ.
Ben Aaronson

@IntelliData 2. Điều này rất khó trả lời nếu không biết hành vi. Nhưng có lẽ, câu trả lời là: bạn sẽ không. Hành vi nào mà một Customerlớp có thể yêu cầu có thể đột biến số điện thoại của nó? Có lẽ số điện thoại của khách hàng thay đổi và tôi cần phải tiếp tục thay đổi cơ sở dữ liệu đó, nhưng không ai trong số đó là trách nhiệm của một đối tượng miền cung cấp hành vi. Đó là mối quan tâm truy cập dữ liệu và có thể sẽ được xử lý bằng DTO và, giả sử, một kho lưu trữ.
Ben Aaronson

@IntelliData Giữ Customerdữ liệu của đối tượng miền tương đối mới (đồng bộ với db) là vấn đề quản lý vòng đời của nó, đây cũng không phải là trách nhiệm của riêng mình và một lần nữa có thể sẽ sống trong kho lưu trữ hoặc nhà máy hoặc thùng chứa IOC hoặc bất cứ điều gì bắt đầu Customers.
Ben Aaronson

2
Tôi thực sự thích khái niệm Design for Behavior . Điều này thông báo cho "cấu trúc dữ liệu" cơ bản và giúp tránh tất cả các lớp thiếu máu quá phổ biến, khó sử dụng. cộng với một.
radarbob

71

Cách đơn giản nhất để tránh setters là trao các giá trị cho phương thức constructor khi bạn newlên đối tượng. Đây cũng là mô hình thông thường khi bạn muốn làm cho một đối tượng bất biến. Điều đó nói rằng, mọi thứ không phải lúc nào cũng rõ ràng trong thế giới thực.

Đúng là phương pháp nên về hành vi. Tuy nhiên, một số đối tượng, như Khách hàng, tồn tại chủ yếu để chứa thông tin. Đó là những loại đối tượng được hưởng lợi nhiều nhất từ ​​getters và setters; không có nhu cầu nào cho các phương pháp như vậy, chúng tôi chỉ đơn giản là sẽ loại bỏ chúng hoàn toàn.

Đọc thêm
Khi nào Getters và Setters được chứng minh


3
vậy tại sao tất cả sự cường điệu về 'Getter / Setters' là xấu xa?
IntelliData

46
Chỉ cần sự thông báo thông thường của các nhà phát triển phần mềm, những người nên biết rõ hơn. Đối với bài viết bạn đã liên kết, tác giả đang sử dụng cụm từ "getters và setters là xấu xa" để thu hút sự chú ý của bạn, nhưng điều đó không có nghĩa là tuyên bố đó là đúng về mặt phân loại.
Robert Harvey

12
@IntelliData một số người sẽ nói với bạn rằng chính java là xấu xa.
null

18
@MetaFightsetEvil(null);

4
Chắc chắn eval là Evil Incarnate. Hoặc một người anh em họ gần.
giám mục

58

Hoàn toàn ổn khi có một đối tượng phơi bày dữ liệu thay vì hành vi. Chúng tôi chỉ gọi nó là một "đối tượng dữ liệu". Mẫu tồn tại dưới các tên như Đối tượng truyền dữ liệu hoặc Đối tượng giá trị. Nếu mục đích của đối tượng là giữ dữ liệu, thì getters và setters là hợp lệ để truy cập dữ liệu.

Vậy tại sao ai đó lại nói "phương thức getter và setter là xấu xa"? Bạn sẽ thấy điều này rất nhiều - ai đó lấy một hướng dẫn hoàn toàn hợp lệ trong một bối cảnh cụ thể và sau đó loại bỏ bối cảnh để có được một tiêu đề khó hơn. Ví dụ: " ưu tiên thành phần trên thừa kế " là một nguyên tắc tốt, nhưng chẳng mấy chốc ai đó sẽ xóa bối cảnh và viết " Tại sao kéo dài là xấu xa " (hey, cùng tác giả, thật là trùng hợp!) Hoặc " thừa kế là xấu xa và phải là bị phá hủy ".

Nếu bạn nhìn vào nội dung của bài viết thì nó thực sự có một số điểm hợp lệ, nó chỉ kéo dài điểm để tạo tiêu đề nhấp chuột. Ví dụ, bài viết nói rằng các chi tiết thực hiện không nên được tiết lộ. Đây là nguyên tắc đóng gói và ẩn dữ liệu là cơ bản trong OO. Tuy nhiên, một phương thức getter không theo định nghĩa phơi bày chi tiết thực hiện. Trong trường hợp đối tượng dữ liệu của Khách hàng , các thuộc tính của Tên , Địa chỉ , v.v. không phải là chi tiết triển khai mà là toàn bộ mục đích của đối tượng và phải là một phần của giao diện chung.

Đọc phần tiếp theo của bài viết mà bạn liên kết đến, để xem cách anh ấy đề xuất thực sự thiết lập các thuộc tính như 'tên' và 'lương' trên một đối tượng 'Nhân viên' mà không sử dụng các setters xấu. Hóa ra anh ta sử dụng một mẫu có 'Trình xuất', được điền bằng các phương thức gọi là thêm Tên, thêm Mức lương để lần lượt đặt các trường có cùng tên ... Vì vậy, cuối cùng anh ta kết thúc bằng cách sử dụng chính xác mẫu setter, chỉ với một quy ước đặt tên khác nhau.

Điều này giống như bạn nghĩ rằng bạn sẽ tránh được những cạm bẫy của những người độc thân bằng cách đổi tên chúng thành trị liệu trong khi vẫn giữ nguyên cách thực hiện.


Trong oop, các chuyên gia được gọi là dường như nói rằng đây không phải là trường hợp.
IntelliData

8
Sau đó, một lần nữa, một số người đã nói 'Không bao giờ sử dụng OOP': harm.cat-v.org/software/OO_programming
JacquesB

7
FTR, tôi nghĩ rằng nhiều chuyên gia của người Viking, giáo viên vẫn dạy cách tạo ra getters / setters một cách vô nghĩa cho mọi thứ , hơn là không bao giờ tạo ra bất kỳ thứ gì cả. IMO, sau này là lời khuyên ít sai lầm.
rẽ trái

4
@leftaroundabout: OK, nhưng tôi đang đề xuất một nền tảng trung gian giữa 'luôn luôn' và 'không bao giờ', đó là 'sử dụng khi thích hợp'.
JacquesB

3
Tôi nghĩ vấn đề là nhiều lập trình viên biến mọi đối tượng (hoặc quá nhiều đối tượng) thành DTO. Đôi khi chúng là cần thiết, nhưng tránh chúng càng nhiều càng tốt vì chúng tách dữ liệu khỏi hành vi. (Giả sử bạn là một OOPer sùng đạo)
user949300

11

Để chuyển đổi Customerlớp từ một đối tượng dữ liệu, chúng ta có thể tự hỏi mình những câu hỏi sau về các trường dữ liệu:

Chúng tôi muốn sử dụng {trường dữ liệu} như thế nào? Trường dữ liệu} được sử dụng ở đâu? Có thể và nên sử dụng {trường dữ liệu} vào lớp không?

Ví dụ:

  • Mục đích của là Customer.Namegì?

    Câu trả lời có thể, hiển thị tên trong một trang web đăng nhập, sử dụng tên trong thư cho khách hàng.

    Điều này dẫn đến các phương thức:

    • Khách hàng.FillInTemplate (Tìm)
    • Khách hàng.IsApplossibleForMending (Hoài)
  • Mục đích của là Customer.DOBgì?

    Xác nhận tuổi của khách hàng. Giảm giá vào ngày sinh nhật của khách hàng. Gửi thư.

    • Khách hàng.IsApplossibleForSản phẩm ()
    • Khách hàng.GetPersonalDiscount ()
    • Khách hàng.IsApplossibleForM gửi ()

Đưa ra các nhận xét, đối tượng ví dụ Customer- cả đối tượng dữ liệu và đối tượng "thực" với trách nhiệm riêng của mình - quá rộng; tức là nó có quá nhiều tài sản / trách nhiệm. Điều này dẫn đến rất nhiều thành phần tùy thuộc vào Customer(bằng cách đọc thuộc tính của nó) hoặc Customertùy thuộc vào nhiều thành phần. Có lẽ tồn tại những quan điểm khác nhau về khách hàng, có lẽ mỗi người nên có lớp 1 riêng biệt :

  • Khách hàng trong bối cảnh Accountvà giao dịch tiền tệ có lẽ chỉ được sử dụng để:

    • giúp con người xác định rằng việc chuyển tiền của họ đến đúng người; và
    • nhóm Accounts.

    Khách hàng này không cần các lĩnh vực như DOB, FavouriteColour, Tel, và có lẽ thậm chí không Address.

  • Khách hàng trong bối cảnh người dùng đăng nhập vào trang web ngân hàng.

    Các lĩnh vực liên quan là:

    • FavouriteColour, có thể đến dưới dạng chủ đề cá nhân;
    • LanguagePreferences
    • GreetingName

    Thay vì các thuộc tính với getters và setters, chúng có thể được ghi lại trong một phương thức duy nhất:

    • PersonaliseWebPage (Trang mẫu);
  • Các khách hàng trong bối cảnh tiếp thị và gửi thư cá nhân.

    Ở đây không dựa vào các thuộc tính của một dataobject, mà thay vào đó bắt đầu từ trách nhiệm của đối tượng; ví dụ:

    • IsCustomerInterestedInAction (); và
    • GetPersonalDiscounts ().

    Thực tế là đối tượng khách hàng này có một FavouriteColourtài sản và / hoặc một Addresstài sản trở nên không liên quan: có lẽ việc triển khai sử dụng các thuộc tính này; nhưng nó cũng có thể sử dụng một số kỹ thuật học máy và sử dụng các tương tác trước đó với khách hàng để khám phá sản phẩm nào khách hàng có thể quan tâm.


1. Tất nhiên, các lớp CustomerAccountcác ví dụ là ví dụ và đối với một ví dụ đơn giản hoặc bài tập về nhà, việc chia tách khách hàng này có thể là quá mức, nhưng với ví dụ về việc chia tách, tôi hy vọng chứng minh rằng phương pháp biến đối tượng dữ liệu thành đối tượng trách nhiệm sẽ làm việc.


4
Upvote bởi vì bạn thực sự đang trả lời câu hỏi :) Tuy nhiên, rõ ràng là các giải pháp được đề xuất còn tệ hơn nhiều so với việc chỉ có các gettes / setters - ví dụ FillInTemplate phá vỡ rõ ràng nguyên tắc phân tách mối quan tâm. Mà chỉ đi để cho thấy rằng tiền đề của câu hỏi là thiếu sót.
JacquesB

@Kasper van den Berg: Và khi bạn có nhiều thuộc tính trong Khách hàng như thường lệ, ban đầu bạn sẽ đặt chúng như thế nào?
IntelliData

2
@IntelliData các giá trị của bạn có khả năng đến từ cơ sở dữ liệu, XML, v.v. Không hoàn hảo, nhưng bạn thường có thể tránh setters công cộng. (Xem câu trả lời của tôi để biết thêm chi tiết)
user949300

6
Tôi không nghĩ rằng lớp khách hàng nên biết về bất kỳ điều nào trong số này.
CodeInChaos

Một cái gì đó như thế Customer.FavoriteColornào?
Gabe

8

TL; DR

  • Làm mẫu cho hành vi là tốt.

  • Mô hình hóa cho trừu tượng tốt (!) Là tốt hơn.

  • Đôi khi các đối tượng dữ liệu được yêu cầu.


Hành vi và trừu tượng

Có một số lý do để tránh getters và setters. Một là, như bạn đã lưu ý, để tránh mô hình hóa dữ liệu. Đây thực sự là lý do nhỏ. Lý do lớn hơn là để cung cấp trừu tượng.

Trong ví dụ của bạn với tài khoản ngân hàng rõ ràng: Một setBalance()phương pháp sẽ thực sự tồi tệ vì đặt số dư không phải là tài khoản nên được sử dụng cho mục đích gì. Hành vi của tài khoản nên trừu tượng khỏi số dư hiện tại của nó càng nhiều càng tốt. Nó có thể tính đến số dư khi quyết định có rút tiền không, nó có thể cấp quyền truy cập vào số dư hiện tại, nhưng sửa đổi tương tác với tài khoản ngân hàng không nên yêu cầu người dùng tính số dư mới. Đó là những gì tài khoản nên tự làm.

Ngay cả một cặp deposit()withdraw()phương thức cũng không lý tưởng để mô hình hóa tài khoản ngân hàng. Cách tốt hơn là chỉ cung cấp một transfer()phương thức lấy tài khoản khác và số tiền làm đối số. Điều này sẽ cho phép lớp tài khoản đảm bảo một cách tầm thường rằng bạn không vô tình tạo / hủy tiền trong hệ thống của mình, nó sẽ cung cấp một sự trừu tượng rất hữu dụng và nó thực sự sẽ cung cấp cho người dùng cái nhìn sâu sắc hơn vì nó sẽ buộc sử dụng các tài khoản đặc biệt cho kiếm được / đầu tư / mất tiền (xem kế toán kép ). Tất nhiên, không phải mọi việc sử dụng tài khoản đều cần mức độ trừu tượng này, nhưng chắc chắn đáng để xem xét mức độ trừu tượng mà các lớp của bạn có thể cung cấp.

Lưu ý rằng việc cung cấp trừu tượng hóa và ẩn nội bộ dữ liệu không phải lúc nào cũng giống nhau. Hầu như bất kỳ ứng dụng nào cũng chứa các lớp chỉ là dữ liệu. Tuples, từ điển và mảng là những ví dụ thường xuyên. Bạn không muốn ẩn tọa độ x của một điểm khỏi người dùng. Có rất ít sự trừu tượng mà bạn có thể / nên làm với một điểm.


Lớp khách hàng

Một khách hàng chắc chắn là một thực thể trong hệ thống của bạn nên cố gắng cung cấp các tóm tắt hữu ích. Chẳng hạn, nó có thể được liên kết với giỏ hàng và sự kết hợp giữa giỏ hàng và khách hàng sẽ cho phép thực hiện giao dịch mua, có thể khởi động các hành động như gửi cho anh ta các sản phẩm được yêu cầu, tính tiền cho anh ta (có tính đến khoản thanh toán đã chọn của anh ta phương pháp), v.v.

Điều hấp dẫn là, tất cả dữ liệu mà bạn đề cập không chỉ liên quan đến khách hàng, tất cả dữ liệu đó cũng có thể thay đổi. Khách hàng có thể di chuyển. Họ có thể thay đổi công ty thẻ tín dụng của họ. Họ có thể thay đổi địa chỉ email và số điện thoại của họ. Heck, họ thậm chí có thể thay đổi tên và / hoặc giới tính của họ! Vì vậy, một lớp khách hàng đầy đủ tính năng thực sự phải cung cấp quyền truy cập sửa đổi đầy đủ cho tất cả các mục dữ liệu này.

Tuy nhiên, các setters có thể / nên cung cấp các dịch vụ không tầm thường: Họ có thể đảm bảo định dạng đúng địa chỉ email, xác minh địa chỉ bưu chính, v.v. Tương tự như vậy, "getters" có thể cung cấp các dịch vụ cấp cao như cung cấp địa chỉ email theo Name <user@server.com>định dạng sử dụng các trường tên và địa chỉ email được gửi hoặc cung cấp địa chỉ bưu chính được định dạng chính xác, v.v ... Tất nhiên, chức năng cấp cao này có ý nghĩa gì phụ thuộc rất nhiều vào trường hợp sử dụng của bạn. Nó có thể là quá mức cần thiết, hoặc nó có thể kêu gọi một lớp khác làm điều đó đúng. Sự lựa chọn mức độ trừu tượng không phải là một điều dễ dàng.


Nghe có vẻ đúng, mặc dù tôi không đồng ý về phần tình dục ...;)
IntelliData

6

Cố gắng mở rộng câu trả lời của Kasper, thật dễ dàng để chống lại và loại bỏ setters. Trong một cuộc tranh luận khá mơ hồ, bằng tay (và hy vọng là hài hước):

Khi nào thì Khách hàng sẽ thay đổi?

Ít khi. Có lẽ họ đã kết hôn. Hoặc đi vào bảo vệ nhân chứng. Nhưng trong trường hợp đó, bạn cũng muốn kiểm tra và có thể thay đổi nơi cư trú, người thân và thông tin khác.

Khi nào DOB ​​sẽ thay đổi?

Chỉ khi tạo ban đầu, hoặc trên một vít nhập dữ liệu. Hoặc nếu họ là một cầu thủ bóng chày Domincan. :-)

Những trường này không thể truy cập được với setters thông thường, bình thường. Có thể bạn có một Customer.initialEntry()phương thức, hoặc một Customer.screwedUpHaveToChange()phương thức đòi hỏi quyền đặc biệt. Nhưng không có một Customer.setDOB()phương pháp công khai .

Thông thường Khách hàng được đọc từ cơ sở dữ liệu, API REST, một số XML, bất cứ điều gì. Có một phương thức Customer.readFromDB(), hoặc, nếu bạn chặt chẽ hơn về SRP / tách các mối quan tâm, bạn sẽ có một trình xây dựng riêng, ví dụ: một CustomerPersisterđối tượng với một read()phương thức. Trong nội bộ, bằng cách nào đó, họ đặt các trường (tôi thích sử dụng truy cập gói hoặc lớp bên trong, YMMV). Nhưng một lần nữa, tránh setters công cộng.

(Phụ lục như Câu hỏi đã thay đổi phần nào ...)

Giả sử ứng dụng của bạn sử dụng nhiều cơ sở dữ liệu quan hệ. Nó sẽ là ngu ngốc để có Customer.saveToMYSQL()hoặc Customer.readFromMYSQL()phương pháp. Điều đó tạo ra sự ghép nối không mong muốn với một thực thể cụ thể, không chuẩn và có khả năng thay đổi . Ví dụ: khi bạn thay đổi lược đồ hoặc thay đổi thành Postwards hoặc Oracle.

Tuy nhiên, IMO, nó hoàn toàn có thể chấp nhận để vài khách hàng đến một tiêu chuẩn trừu tượng , ResultSet. Một đối tượng trợ giúp riêng biệt (tôi sẽ gọi nó CustomerDBHelper, có lẽ là một lớp con AbstractMySQLHelper) biết về tất cả các kết nối phức tạp đến DB của bạn, biết các chi tiết tối ưu hóa khó khăn, biết các bảng, truy vấn, tham gia, v.v ... (hoặc sử dụng một ORM như Hibernate) để tạo Kết quả. Đối tượng của bạn nói chuyện với ResultSet, đó là một tiêu chuẩn trừu tượng , không có khả năng thay đổi. Khi bạn thay đổi cơ sở dữ liệu cơ bản hoặc thay đổi lược đồ, Khách hàng sẽ không thay đổi , nhưng CustomerDBHelper thì có. Nếu bạn may mắn, đó chỉ là Tóm tắt MyQueryHelper thay đổi tự động thực hiện các thay đổi cho Khách hàng, Người bán, Giao hàng, v.v.

Bằng cách này, bạn có thể (có thể) tránh hoặc giảm bớt sự cần thiết của getters và setters.

Và, điểm chính của bài viết Holub, so sánh và đối chiếu ở trên với nó sẽ như thế nào nếu bạn sử dụng getters và setters cho mọi thứ và thay đổi cơ sở dữ liệu.

Tương tự, giả sử bạn sử dụng nhiều XML. IMO, thật tốt khi kết hợp Khách hàng của bạn với một tiêu chuẩn trừu tượng, chẳng hạn như Python xml.etree.EuityTree hoặc Java org.w3c.dom.Euity . Khách hàng có được và tự đặt ra từ đó. Một lần nữa, bạn có thể (có lẽ) giảm bớt sự cần thiết của getters và setters.


bạn có thể nói rằng nên sử dụng Mô hình Builder không?
IntelliData

Builder rất hữu ích để làm cho việc xây dựng một đối tượng dễ dàng và mạnh mẽ hơn, và, nếu bạn muốn, cho phép đối tượng là bất biến. Tuy nhiên, nó vẫn (một phần) phơi bày thực tế rằng đối tượng bên dưới có trường DOB, vì vậy nó không phải là con ong - tất cả là kết thúc.
user949300

1

Vấn đề có getters và setters có thể là vấn đề thực tế là một lớp có thể được sử dụng trong logic nghiệp vụ theo một cách nhưng bạn cũng có thể có các lớp trợ giúp để tuần tự hóa / giải tuần tự dữ liệu từ cơ sở dữ liệu hoặc tệp hoặc lưu trữ liên tục khác.

Do thực tế có nhiều cách để lưu trữ / truy xuất dữ liệu của bạn và bạn muốn tách rời các đối tượng dữ liệu khỏi cách chúng được lưu trữ, việc đóng gói có thể bị "xâm phạm" bằng cách làm cho các thành viên này công khai hoặc làm cho chúng có thể truy cập được thông qua getters và setters mà gần như là xấu như làm cho chúng công khai.

Có nhiều cách khác nhau xung quanh điều này. Một cách là làm cho dữ liệu có sẵn cho một "người bạn". Mặc dù tình bạn không được kế thừa, nhưng điều này có thể được khắc phục bằng bất kỳ trình tuần tự hóa nào yêu cầu thông tin từ người bạn, tức là trình tuần tự cơ sở "chuyển tiếp" thông tin.

Lớp của bạn có thể có phương thức "fromMetadata" hoặc "toMetadata" chung. Từ siêu dữ liệu xây dựng một đối tượng vì vậy cũng có thể là một nhà xây dựng. Nếu nó là ngôn ngữ được gõ động, siêu dữ liệu khá chuẩn đối với ngôn ngữ đó và có lẽ là cách chính để xây dựng các đối tượng đó.

Nếu ngôn ngữ của bạn là C ++ cụ thể, một cách khác là sử dụng "struct" dữ liệu công khai và sau đó lớp của bạn có một thể hiện của "struct" này với tư cách là thành viên và trên thực tế tất cả dữ liệu bạn sẽ lưu trữ / lấy để được lưu trữ trong đó. Sau đó, bạn có thể dễ dàng viết "trình bao bọc" để đọc / ghi dữ liệu của mình ở nhiều định dạng.

Nếu ngôn ngữ của bạn là C # hoặc Java không có "cấu trúc" thì bạn có thể làm tương tự nhưng cấu trúc của bạn bây giờ là lớp thứ cấp. Không có khái niệm thực sự về "quyền sở hữu" dữ liệu hoặc tài sản, vì vậy nếu bạn đưa ra một thể hiện của lớp chứa dữ liệu của bạn và tất cả đều công khai, bất cứ điều gì được giữ đều có thể sửa đổi nó. Bạn có thể "nhân bản" nó mặc dù điều này có thể tốn kém. Ngoài ra, bạn có thể làm cho lớp này có dữ liệu riêng tư nhưng sử dụng các bộ truy cập. Điều đó mang lại cho người dùng của lớp bạn một cách vòng để lấy dữ liệu nhưng đó không phải là giao diện trực tiếp với lớp của bạn và thực sự là một chi tiết trong việc lưu trữ dữ liệu của lớp cũng là trường hợp sử dụng.


0

OOP là về đóng gói và che giấu hành vi bên trong các đối tượng. Đối tượng là hộp đen. Đây là một cách để thiết kế điều. Tài sản trong nhiều trường hợp người ta không cần biết trạng thái bên trong của một thành phần khác và tốt hơn là không phải biết nó. Bạn có thể thực thi ý tưởng đó với các giao diện chủ yếu hoặc bên trong một đối tượng có khả năng hiển thị và chỉ cẩn thận các động từ / hành động được phép có sẵn cho người gọi.

Điều này làm việc tốt cho một số loại vấn đề. Ví dụ trong giao diện người dùng để mô hình hóa thành phần UI cá nhân. Khi bạn can thiệp vào một hộp văn bản, bạn chỉ bị chặn trong việc thiết lập văn bản, nhận nó hoặc nghe sự kiện thay đổi văn bản. Bạn thường không được can thiệp vào vị trí của con trỏ, phông chữ được sử dụng để vẽ văn bản hoặc cách sử dụng bàn phím. Đóng gói cung cấp rất nhiều ở đây.

Ngược lại khi bạn gọi một dịch vụ mạng, bạn cung cấp một đầu vào rõ ràng. Thường có một ngữ pháp (như trong JSON hoặc XML) và tất cả các tùy chọn gọi dịch vụ không có lý do gì để bị ẩn. Ý tưởng là bạn có thể gọi dịch vụ theo cách bạn muốn và định dạng dữ liệu được công khai và xuất bản.

Trong trường hợp này, hoặc nhiều thứ khác (như truy cập vào cơ sở dữ liệu) bạn thực sự làm việc với dữ liệu được chia sẻ. Vì vậy, không có lý do để che giấu nó, trái lại bạn muốn làm cho nó có sẵn. Có thể có mối quan tâm về truy cập đọc / ghi hoặc tính nhất quán của dữ liệu nhưng ở cốt lõi này, khái niệm cốt lõi nếu điều này là công khai.

Đối với yêu cầu thiết kế như vậy, nơi bạn muốn tránh đóng gói và làm cho mọi thứ công khai và rõ ràng, bạn muốn tránh các đối tượng. Những gì bạn thực sự cần là bộ dữ liệu, cấu trúc C hoặc tương đương của chúng, không phải đối tượng.

Nhưng nó cũng xảy ra trong các ngôn ngữ như Java, điều duy nhất bạn có thể mô hình hóa là các đối tượng hoặc mảng các đối tượng. Các đối tượng themselve có thể chứa một vài loại bản địa (int, float ...) nhưng chỉ có vậy. Nhưng các đối tượng cũng có thể hành xử giống như một cấu trúc đơn giản chỉ với các trường công khai và tất cả.

Vì vậy, nếu bạn mô hình hóa dữ liệu, bạn có thể được thực hiện chỉ với các trường công khai bên trong các đối tượng vì bạn không cần nhiều hơn. Bạn không sử dụng đóng gói vì bạn không cần nó. Điều này được thực hiện theo cách này trong nhiều ngôn ngữ. Trong java, về mặt lịch sử, một bông hồng tiêu chuẩn trong đó với getter / setter ít nhất bạn có thể có điều khiển đọc / ghi (bằng cách không thêm setter chẳng hạn) và các công cụ và khung bằng cách sử dụng API instrospection sẽ tìm kiếm các phương thức getter / setter và sử dụng nó để tự động điền nội dung hoặc hiển thị luận văn dưới dạng các trường có thể sửa đổi trong giao diện người dùng được tạo tự động.

Ngoài ra còn có đối số bạn có thể thêm một số logic / kiểm tra trong phương thức setter.

Trong thực tế hầu như không có lời biện minh nào cho getter / setters vì chúng thường được sử dụng để mô hình hóa dữ liệu thuần túy. Các khung và nhà phát triển sử dụng các đối tượng của bạn thực sự mong đợi getter / setter không làm gì khác hơn là thiết lập / nhận các trường. Bạn thực sự không làm gì nhiều với getter / setter hơn những gì có thể được thực hiện với các trường công khai.

Nhưng đó là những thói quen cũ và thói quen cũ rất khó để loại bỏ ... Bạn thậm chí có thể bị đe dọa bởi đồng nghiệp hoặc giáo viên của mình nếu bạn không đặt getters / setter một cách mù quáng ở mọi nơi nếu họ thiếu nền tảng để hiểu rõ hơn về họ và họ là gì không phải.

Bạn có thể sẽ cần phải thay đổi ngôn ngữ để có được tất cả các mã soạn thảo mã getters / setters. (Giống như C # hoặc lisp). Đối với tôi getters / setter chỉ là một sai lầm một tỷ đô la ...


6
Các thuộc tính C # không thực sự thực hiện đóng gói nhiều hơn các getters và setters ...
IntelliData

1
Ưu điểm của [gs] etters là bạn có thể thực hiện những việc bạn thường không làm (kiểm tra giá trị, thông báo cho người quan sát), mô phỏng các trường không tồn tại, v.v., và chủ yếu là bạn có thể thay đổi nó sau . Với Lombok , nồi hơi đã biến mất : @Getter @Setter class MutablePoint3D {private int x, y, z;}.
maaartinus

1
@maaartinus Chắc chắn [gs] etter có thể làm bất cứ điều gì như bất kỳ phương pháp nào khác có thể. Điều này có nghĩa là bất kỳ mã người gọi nào cũng nên biết bất kỳ giá trị nào họ đặt có thể đưa ra một ngoại lệ, được thay đổi hoặc có khả năng gửi thông báo thay đổi ... hoặc bất cứ điều gì khác. Rằng ít nhiều [gs] etter không cung cấp quyền truy cập vào một trường mà thực hiện mã trọng tài.
Nicolas Bousquet

Các thuộc tính @IntelliData C # cho phép không viết 1 ký tự đơn vô dụng của nồi hơi và không quan tâm đến tất cả các công cụ getter / setter này ... Đây là thành tích tốt hơn so với dự án lombok. Ngoài ra với tôi, một POJO chỉ với getters / setter không ở đây để cung cấp đóng gói mà là để xuất bản một định dạng dữ liệu mà người ta có thể đọc hoặc viết miễn phí khi trao đổi với một dịch vụ. Đóng gói sau đó là ngược lại với yêu cầu thiết kế.
Nicolas Bousquet

Tôi không thực sự nghĩ rằng tài sản là thực sự tốt. Chắc chắn, bạn lưu tiền tố và dấu ngoặc đơn, tức là 5 ký tự cho mỗi cuộc gọi, nhưng 1. chúng trông giống như các trường, gây nhầm lẫn. 2. chúng là một điều bổ sung mà bạn cần hỗ trợ trong việc phản ánh. Không có vấn đề lớn, nhưng cũng không có lợi thế lớn (khi so sánh với Java + Lombok; Java thuần túy đang thua lỗ rõ ràng).
maaartinus

0

Vì vậy, ví dụ, được cung cấp một lớp Khách hàng giữ rất nhiều thông tin về khách hàng, ví dụ Tên, DOB, Điện thoại, Địa chỉ, v.v., làm thế nào để tránh getter / setters để nhận và thiết lập tất cả các thuộc tính đó? Phương thức loại 'Hành vi' nào người ta có thể viết để điền vào tất cả dữ liệu đó?

Tôi nghĩ rằng câu hỏi này rất khó bởi vì bạn lo lắng về các phương thức hành vi để điền dữ liệu nhưng tôi không thấy bất kỳ dấu hiệu nào về hành vi mà Customerlớp đối tượng dự định đóng gói.

Đừng nhầm lẫn Customervới tư cách là một lớp đối tượng với 'Khách hàng' với tư cách là người dùng / diễn viên thực hiện các tác vụ khác nhau bằng phần mềm của bạn.

Khi bạn nói được cung cấp một lớp Khách hàng giữ rất nhiều nếu thông tin về khách hàng thì theo như hành vi, có vẻ như lớp Khách hàng của bạn có rất ít phân biệt nó với một tảng đá. A Rockcó thể có màu, bạn có thể đặt tên cho nó, bạn có thể có một trường để lưu địa chỉ hiện tại của nó nhưng chúng tôi không mong đợi bất kỳ loại hành vi thông minh nào từ một tảng đá.

Từ bài viết được liên kết về getters / setters là xấu xa:

Quy trình thiết kế OO tập trung vào các trường hợp sử dụng: người dùng thực hiện các tác vụ độc lập có một số kết quả hữu ích. (Đăng nhập không phải là trường hợp sử dụng vì nó thiếu kết quả hữu ích trong miền sự cố. Vẽ bảng lương là trường hợp sử dụng.) Hệ thống OO, sau đó, thực hiện các hoạt động cần thiết để diễn ra các tình huống khác nhau bao gồm trường hợp sử dụng.

Không có bất kỳ hành vi nào được xác định, việc đề cập đến một tảng đá là Customerkhông thay đổi thực tế rằng nó chỉ là một đối tượng với một số thuộc tính bạn muốn theo dõi và không có vấn đề gì bạn muốn chơi để thoát khỏi getters và setters. Một hòn đá không quan tâm nếu nó có tên hợp lệ và một hòn đá sẽ không được biết liệu một địa chỉ có hợp lệ hay không.

Hệ thống đặt hàng của bạn có thể liên kết Rockvới một đơn đặt hàng và miễn là Rockđịa chỉ đó được xác định thì một phần của hệ thống có thể đảm bảo một mặt hàng được giao đến một tảng đá.

Trong tất cả các trường hợp này, Rocknó chỉ là một Đối tượng dữ liệu và sẽ tiếp tục là một đối tượng cho đến khi chúng ta xác định các hành vi cụ thể với kết quả hữu ích thay vì các giả thuyết.


Thử đi:

Khi bạn tránh quá tải từ 'Khách hàng' với 2 nghĩa có khả năng khác nhau, điều đó sẽ giúp mọi thứ dễ dàng hơn để khái niệm hóa.

Liệu một Rockđối tượng có đặt hàng hay đó là thứ mà con người làm bằng cách nhấp vào các thành phần UI để kích hoạt các hành động trong hệ thống của bạn?


Khách hàng là một diễn viên làm nhiều việc, nhưng cũng có rất nhiều thông tin liên quan đến nó; điều đó có biện minh cho việc tạo 2 lớp riêng biệt, 1 với tư cách là một tác nhân và 1 là một đối tượng dữ liệu không?
IntelliData

@IntelliData truyền các đối tượng phong phú qua các lớp là nơi mọi thứ trở nên khó khăn. Nếu bạn đang gửi đối tượng từ bộ điều khiển đến một khung nhìn, thì khung nhìn cần phải hiểu hợp đồng chung của đối tượng (ví dụ tiêu chuẩn JavaBeans). Nếu bạn đang gửi đối tượng qua dây, JaxB hoặc tương tự cần có khả năng khôi phục lại đối tượng đó cho một đối tượng câm (vì bạn đã không cung cấp cho họ đối tượng phong phú đầy đủ). Đối tượng phong phú là tuyệt vời để thao túng dữ liệu - họ nghèo để chuyển trạng thái. Ngược lại, các đối tượng câm rất kém trong việc thao tác dữ liệu và ok để chuyển trạng thái.

0

Tôi thêm 2 xu của tôi ở đây đề cập đến cách tiếp cận đối tượng nói SQL .

Cách tiếp cận này dựa trên khái niệm về đối tượng khép kín. Nó có tất cả các nguồn lực cần thiết để thực hiện hành vi của mình. Không cần phải nói làm thế nào để thực hiện công việc của mình - yêu cầu khai báo là đủ. Và một đối tượng chắc chắn không phải giữ tất cả dữ liệu của nó dưới dạng các thuộc tính lớp. Nó thực sự không - và không nên - cho dù họ đến từ đâu.

Nói về một tổng hợp , bất biến cũng không phải là một vấn đề. Giả sử, bạn có một chuỗi các trạng thái tổng hợp có thể giữ: Tổng hợp gốc như saga Hoàn toàn tốt khi thực hiện mỗi trạng thái dưới dạng đối tượng độc lập. Có lẽ bạn có thể đi xa hơn: trò chuyện với chuyên gia tên miền của bạn. Rất có thể anh ta hoặc cô ta không xem tập hợp này là một thực thể hợp nhất. Có lẽ mỗi tiểu bang có ý nghĩa riêng của nó, xứng đáng là đối tượng của riêng nó.

Cuối cùng, tôi muốn lưu ý rằng quá trình tìm kiếm đối tượng rất giống với phân rã hệ thống thành các hệ thống con . Cả hai đều dựa trên hành vi, không phải bất cứ điều gì khác.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.