Bạn nên chia tay lớp học theo câu hỏi.
Mỗi lớp nên làm một số nhiệm vụ đơn giản. Nếu nhiệm vụ của bạn quá phức tạp để kiểm tra, thì nhiệm vụ mà lớp thực hiện là quá lớn.
Bỏ qua sự ngu ngốc của thiết kế này:
class NewYork
{
decimal GetRevenue();
decimal GetExpenses();
decimal GetProfit();
}
class Miami
{
decimal GetRevenue();
decimal GetExpenses();
decimal GetProfit();
}
class MyProfit
{
MyProfit(NewYork new_york, Miami miami);
boolean bothProfitable();
}
CẬP NHẬT
Vấn đề với các phương thức khai thác trong một lớp là bạn đang vi phạm việc đóng gói. Thử nghiệm của bạn nên được kiểm tra để xem liệu hành vi bên ngoài của đối tượng có khớp với thông số kỹ thuật hay không. Bất cứ điều gì xảy ra bên trong đối tượng không phải là việc của nó.
Thực tế là FullName sử dụng FirstName và LastName là một chi tiết triển khai. Không có gì ngoài lớp nên quan tâm đó là sự thật. Bằng cách chế nhạo các phương thức công khai để kiểm tra đối tượng, bạn đang đưa ra một giả định về đối tượng đó được thực hiện.
Tại một số điểm trong tương lai, giả định đó có thể chấm dứt là chính xác. Có lẽ tất cả logic tên sẽ được chuyển đến một đối tượng Tên mà Người chỉ đơn giản gọi. Có lẽ FullName sẽ truy cập trực tiếp các biến thành viên First_name và last_name sau đó gọi FirstName và LastName.
Câu hỏi thứ hai là tại sao bạn cảm thấy cần phải làm như vậy. Sau khi tất cả các lớp người của bạn có thể được kiểm tra một cái gì đó như:
Person person = new Person("John", "Doe");
Test.AssertEquals(person.FullName(), "John Doe");
Bạn không nên cảm thấy cần thiết bất cứ điều gì cho ví dụ này. Nếu bạn làm thế thì bạn vẫn còn hạnh phúc và ... dừng lại đi! Không có lợi ích gì khi chế nhạo các phương thức ở đó bởi vì dù sao bạn cũng đã kiểm soát được những gì trong đó.
Trường hợp duy nhất có vẻ hợp lý đối với các phương thức mà FullName sử dụng bị chế giễu là nếu bằng cách nào đó FirstName () và LastName () là các hoạt động không tầm thường. Có thể bạn đang viết một trong những trình tạo tên ngẫu nhiên đó hoặc FirstName và LastName truy vấn cơ sở dữ liệu để trả lời hoặc một cái gì đó. Nhưng nếu đó là những gì đang xảy ra thì nó gợi ý rằng đối tượng đang làm điều gì đó không thuộc về lớp Người.
Đặt nó theo một cách khác, chế nhạo các phương thức là lấy đối tượng và chia thành hai mảnh. Một mảnh đang bị chế giễu trong khi mảnh kia đang được thử nghiệm. Những gì bạn đang làm về cơ bản là một sự phá vỡ đặc biệt của đối tượng. Nếu đó là trường hợp, chỉ cần phá vỡ các đối tượng.
Nếu lớp học của bạn đơn giản, bạn không nên cảm thấy cần phải chế giễu các phần của nó trong bài kiểm tra. Nếu lớp học của bạn đủ phức tạp để bạn cảm thấy cần phải chế giễu, thì bạn nên chia lớp thành những phần đơn giản hơn.
CẬP NHẬT
Theo cách tôi nhìn thấy, một đối tượng có hành vi bên ngoài và bên trong. Hành vi bên ngoài bao gồm trả về các giá trị cuộc gọi vào các đối tượng khác, v.v ... Rõ ràng, bất cứ điều gì trong danh mục đó nên được kiểm tra. (nếu không bạn sẽ kiểm tra cái gì?) Nhưng hành vi nội bộ không thực sự được kiểm tra.
Bây giờ hành vi nội bộ được kiểm tra, bởi vì đó là những gì dẫn đến hành vi bên ngoài. Nhưng tôi không viết các bài kiểm tra trực tiếp về hành vi bên trong, chỉ gián tiếp thông qua hành vi bên ngoài.
Nếu tôi muốn kiểm tra một cái gì đó, tôi nghĩ rằng nó nên được di chuyển để nó trở thành hành vi bên ngoài. Đó là lý do tại sao tôi nghĩ rằng nếu bạn muốn chế giễu một cái gì đó, bạn nên chia nhỏ đối tượng để điều bạn muốn chế giễu bây giờ nằm trong hành vi bên ngoài của các đối tượng được đề cập.
Nhưng, nó có gì khác biệt? Nếu FirstName () và LastName () là thành viên của một đối tượng khác thì nó có thực sự thay đổi vấn đề của FullName () không? Nếu chúng ta quyết định rằng nó không cần thiết để chế nhạo FirstName và LastName có thực sự giúp họ ở trên một đối tượng khác không?
Tôi nghĩ rằng nếu bạn sử dụng phương pháp chế giễu của mình, thì bạn sẽ tạo ra một đường may trong đối tượng. Bạn có các hàm như FirstName () và LastName () giao tiếp trực tiếp với nguồn dữ liệu ngoài. Bạn cũng có FullName () mà không có. Nhưng vì tất cả họ đều ở trong cùng một lớp không rõ ràng. Một số phần không được phép truy cập trực tiếp vào nguồn dữ liệu và các phần khác thì không. Mã của bạn sẽ rõ ràng hơn nếu chỉ cần tách ra khỏi hai nhóm đó.
CHỈNH SỬA
Hãy lùi lại một bước và hỏi: tại sao chúng ta chế nhạo các đối tượng khi chúng ta kiểm tra?
- Làm cho các bài kiểm tra chạy ổn định (tránh truy cập vào những thứ thay đổi từ chạy sang chạy)
- Tránh truy cập các tài nguyên đắt tiền (không đánh các dịch vụ của bên thứ ba, v.v.)
- Đơn giản hóa hệ thống đang thử nghiệm
- Giúp dễ dàng kiểm tra tất cả các tình huống có thể xảy ra (ví dụ: những thứ như mô phỏng thất bại, v.v.)
- Tránh phụ thuộc vào chi tiết của các đoạn mã khác để thay đổi trong các đoạn mã khác đó sẽ không phá vỡ bài kiểm tra này.
Bây giờ, tôi nghĩ lý do 1-4 không áp dụng cho kịch bản này. Việc nhạo báng nguồn bên ngoài khi kiểm tra fullname sẽ xử lý tất cả những lý do đó để chế nhạo. Phần duy nhất không được xử lý đó là sự đơn giản, nhưng có vẻ như đối tượng đủ đơn giản không phải là vấn đề đáng lo ngại.
Tôi nghĩ rằng mối quan tâm của bạn là lý do số 5. Mối quan tâm là tại một thời điểm nào đó trong tương lai thay đổi việc triển khai FirstName và LastName sẽ phá vỡ bài kiểm tra. Trong tương lai FirstName và LastName có thể lấy tên từ một vị trí hoặc nguồn khác nhau. Nhưng FullName có thể sẽ luôn luôn như vậy FirstName() + " " + LastName()
. Đó là lý do tại sao bạn muốn kiểm tra FullName bằng cách chế nhạo FirstName và LastName.
Những gì bạn có sau đó, là một số tập hợp con của đối tượng người có nhiều khả năng thay đổi sau đó những người khác. Phần còn lại của đối tượng sử dụng tập hợp con này. Tập hợp con đó hiện đang tìm nạp dữ liệu của nó bằng một nguồn, nhưng có thể tìm nạp dữ liệu đó theo một cách hoàn toàn khác vào một ngày sau đó. Nhưng với tôi có vẻ như tập hợp con đó là một đối tượng riêng biệt đang cố gắng thoát ra.
Dường như với tôi rằng nếu bạn chế giễu phương thức của đối tượng thì bạn đang chia tách đối tượng. Nhưng bạn đang làm như vậy một cách đặc biệt. Mã của bạn không làm rõ rằng có hai phần riêng biệt bên trong đối tượng Person của bạn. Vì vậy, chỉ cần phân tách đối tượng đó trong mã thực tế của bạn, để nó rõ ràng khỏi việc đọc mã của bạn những gì đang xảy ra. Chọn phân chia thực tế của đối tượng có ý nghĩa và không cố gắng phân tách đối tượng khác nhau cho mỗi thử nghiệm.
Tôi nghi ngờ bạn có thể phản đối việc chia tách đối tượng của bạn, nhưng tại sao?
CHỈNH SỬA
Tôi đã sai.
Bạn nên phân tách các đối tượng thay vì giới thiệu các phân tách đặc biệt bằng cách chế nhạo các phương thức riêng lẻ. Tuy nhiên, tôi đã quá tập trung vào một phương pháp chia tách các đối tượng. Tuy nhiên, OO cung cấp nhiều phương pháp phân tách một đối tượng.
Những gì tôi đề xuất:
class PersonBase
{
abstract sring FirstName();
abstract string LastName();
string FullName()
{
return FirstName() + " " + LastName();
}
}
class Person extends PersonBase
{
string FirstName();
string LastName();
}
class FakePerson extends PersonBase
{
void setFirstName(string);
void setLastName(string);
string getFirstName();
string getLastName();
}
Có lẽ đó là những gì bạn đã làm tất cả cùng. Nhưng tôi không nghĩ rằng phương pháp này sẽ có những vấn đề tôi thấy với các phương thức chế giễu bởi vì chúng tôi đã phân định rõ ràng từng phương pháp được áp dụng. Và bằng cách sử dụng tính kế thừa, chúng tôi tránh được sự lúng túng sẽ phát sinh nếu chúng tôi sử dụng một đối tượng trình bao bọc bổ sung.
Điều này không giới thiệu một số phức tạp và chỉ với một vài chức năng tiện ích, có lẽ tôi chỉ cần kiểm tra chúng bằng cách chế nhạo nguồn bên thứ 3 bên dưới. Chắc chắn, chúng có nguy cơ bị phá vỡ nhưng nó không đáng để sắp xếp lại. Nếu bạn có một đối tượng đủ phức tạp mà bạn cần phải phân tách nó, thì tôi nghĩ một cái gì đó như thế này là một ý tưởng tốt.