Điều chỉnh các lớp 'chức năng tiện ích'


15

Trong cơ sở mã Java của chúng tôi, tôi tiếp tục thấy mẫu sau:

/**
 This is a stateless utility class
 that groups useful foo-related operations, often with side effects.
*/
public class FooUtil {
  public int foo(...) {...}
  public void bar(...) {...}
}

/**
 This class does applied foo-related things.
*/
class FooSomething {
  int DoBusinessWithFoo(FooUtil fooUtil, ...) {
    if (fooUtil.foo(...)) fooUtil.bar(...);
  }
}

Điều làm phiền tôi là tôi phải vượt qua một ví dụ FooUtilở khắp mọi nơi, bởi vì thử nghiệm.

  • Tôi không thể làm cho FooUtilcác phương thức tĩnh, vì tôi sẽ không thể chế nhạo chúng để thử nghiệm cả hai FooUtilvà các lớp máy khách của nó.
  • Tôi không thể tạo một ví dụ FooUtiltại nơi tiêu thụ với một newlần nữa vì tôi sẽ không thể chế giễu nó để thử nghiệm.

Tôi cho rằng đặt cược tốt nhất của tôi là sử dụng thuốc tiêm (và tôi cũng vậy), nhưng nó bổ sung thêm những rắc rối riêng. Ngoài ra, việc truyền một số trường hợp sử dụng làm tăng kích thước của danh sách tham số phương thức.

Có cách nào để xử lý việc này tốt hơn mà tôi không thấy?

Cập nhật: vì các lớp tiện ích là không trạng thái, tôi có thể có thể thêm một INSTANCEthành viên singleton tĩnh hoặc getInstance()phương thức tĩnh , trong khi vẫn giữ khả năng cập nhật trường tĩnh bên dưới của lớp để kiểm tra. Không có vẻ siêu sạch, quá.


4
Và giả định của bạn rằng tất cả các phụ thuộc cần phải được chế giễu để thực hiện kiểm tra đơn vị một cách hiệu quả là không chính xác (mặc dù tôi vẫn đang tìm kiếm câu hỏi thảo luận về điều đó).
Telastyn

4
Tại sao các phương thức này không phải là thành viên của lớp có dữ liệu mà chúng thao tác?
Jules

3
Có một bộ Junit riêng biệt gọi fns tĩnh nếu FooUtility. Sau đó, bạn có thể sử dụng nó mà không cần chế giễu. Nếu đôi khi bạn cần phải chế giễu thì tôi nghi ngờ nó không chỉ là một tiện ích mà còn thực hiện một số IO hoặc logic nghiệp vụ và trên thực tế nên là một tham chiếu thành viên lớp và được khởi tạo thông qua phương thức constructor, init hoặc setter.
tgkprog

2
+1 cho bình luận của Jules. Lớp Util là một mùi mã. Tùy thuộc vào ngữ nghĩa nên có một giải pháp tốt hơn. Có lẽ các FooUtilphương thức nên được đưa vào FooSomething, có thể có các thuộc tính FooSomethingcần được thay đổi / mở rộng để các FooUtilphương thức không còn cần thiết nữa. Xin vui lòng cho chúng tôi một ví dụ cụ thể hơn về những gì FooSomethinggiúp chúng tôi cung cấp một câu trả lời tốt cho câu hỏi này.
valenterry

13
@valenterry: Lý do duy nhất các lớp sử dụng là mùi mã là vì Java không cung cấp một cách tốt để có các hàm độc lập. Có những lý do rất tốt để có các chức năng không dành riêng cho các lớp.
Robert Harvey

Câu trả lời:


16

Trước hết hãy để tôi nói rằng có những cách tiếp cận lập trình khác nhau cho các vấn đề khác nhau. Đối với công việc của tôi, tôi thích một thiết kế OO mạnh mẽ để đạt cực khoái và bảo trì và câu trả lời của tôi phản ánh điều này. Một cách tiếp cận chức năng sẽ có một câu trả lời rất khác nhau.

Tôi có xu hướng thấy rằng hầu hết các lớp tiện ích được tạo bởi vì đối tượng mà các phương thức nên có không tồn tại. Điều này hầu như luôn xuất phát từ một trong hai trường hợp, hoặc ai đó đang chuyển các bộ sưu tập xung quanh hoặc ai đó đang chuyển đậu xung quanh (Chúng thường được gọi là POJO nhưng tôi tin rằng một loạt các setters và getters được mô tả tốt nhất như một hạt đậu).

Khi bạn có một bộ sưu tập mà bạn đang đi qua, phải có mã muốn là một phần của bộ sưu tập đó và cuối cùng, nó sẽ được rắc khắp hệ thống của bạn và cuối cùng được thu thập vào các lớp tiện ích.

Đề nghị của tôi là bọc các bộ sưu tập của bạn (Hoặc đậu) với bất kỳ dữ liệu liên quan chặt chẽ nào khác vào các lớp mới và đặt mã "tiện ích" trong các đối tượng thực tế.

Bạn có thể mở rộng bộ sưu tập và thêm các phương thức vào đó nhưng bạn mất quyền kiểm soát trạng thái đối tượng của mình theo cách đó - Tôi khuyên bạn nên đóng gói hoàn toàn và chỉ hiển thị các phương thức logic nghiệp vụ cần thiết (Hãy nghĩ "Đừng hỏi đối tượng của bạn về dữ liệu của họ, cung cấp cho các đối tượng của bạn các lệnh và để chúng hành động trên dữ liệu riêng tư của chúng ", một người thuê OO quan trọng và một người mà khi bạn nghĩ về nó, yêu cầu cách tiếp cận mà tôi đề xuất ở đây).

"Gói" này hữu ích cho bất kỳ đối tượng thư viện mục đích chung nào chứa dữ liệu nhưng bạn không thể thêm mã vào - Đặt mã với dữ liệu mà nó thao tác là một khái niệm chính khác trong thiết kế OO.

Có nhiều kiểu mã hóa trong đó khái niệm gói này sẽ là một ý tưởng tồi - ai muốn viết cả một lớp khi chỉ thực hiện một kịch bản hoặc thử nghiệm một lần? Nhưng tôi nghĩ việc đóng gói bất kỳ loại / bộ sưu tập cơ bản nào mà bạn không thể thêm mã vào chính mình là bắt buộc đối với OO.


Câu trả lời này thật tuyệt vời. Tôi đã có vấn đề này (rõ ràng), đọc câu trả lời này với một chút ngờ vực, quay lại và xem mã của tôi và hỏi "đối tượng nào bị thiếu nên có các phương thức này". Lo và kìa, giải pháp tao nhã được tìm thấy và lấp đầy khoảng trống mô hình đối tượng.
GreenAsJade


@Dedsplator Trong 40 năm lập trình, tôi hiếm khi (chưa bao giờ?) Đã thấy trường hợp tôi bị chặn bởi quá nhiều mức độ gián tiếp (ngoài cuộc chiến thường xuyên với IDE của tôi để chuyển sang triển khai thay vì giao diện) nhưng tôi ve thường (Rất thường xuyên) thấy trường hợp người ta viết mã khủng khiếp hơn là tạo ra một lớp rõ ràng cần thiết. Mặc dù tôi tin rằng quá nhiều sự thiếu quyết đoán có thể đã cắn một số người, tôi vẫn mắc lỗi về các nguyên tắc OO thích hợp, chẳng hạn như mỗi lớp làm tốt một việc, ngăn chặn thích hợp, v.v.
Bill K

@BillK Nói chung, một ý kiến ​​hay. Nhưng không có lối thoát, đóng gói thực sự có thể cản trở. Và có một vẻ đẹp nhất định đối với các thuật toán miễn phí mà bạn có thể phù hợp với bất kỳ loại nào bạn muốn, mặc dù phải thừa nhận bằng bất kỳ ngôn ngữ OO nghiêm ngặt nào nhưng OO khá khó xử.
Ded repeatator

@Ded repeatator Khi viết 'tiện ích' Các chức năng phải được viết bên ngoài các yêu cầu kinh doanh cụ thể, bạn đã đúng. Các lớp tiện ích có đầy đủ các hàm (như bộ sưu tập) đơn giản là lúng túng trong OO vì chúng không được liên kết với dữ liệu. đây là "Escape hatch" mà những người không thoải mái khi sử dụng OO (đồng thời, các nhà cung cấp công cụ phải sử dụng chúng cho chức năng rất chung chung). Đề xuất của tôi ở trên là bọc các bản hack này trong các lớp khi bạn đang làm việc với logic nghiệp vụ để bạn có thể liên kết lại mã của mình với dữ liệu của mình.
Bill K

1

Bạn có thể sử dụng một mô hình nhà cung cấp và tiêm của bạn FooSomethingvới một FooUtilProviderđối tượng (có thể là trong một constructor; chỉ một lần).

FooUtilProvidersẽ chỉ có một phương thức : FooUtils get();. Lớp sau đó sẽ sử dụng bất kỳ FooUtilstrường hợp nào nó cung cấp.

Bây giờ chỉ còn một bước nữa là sử dụng khung Tiêm phụ thuộc, khi bạn chỉ phải kết nối bộ đồng xử lý DI hai lần: cho mã sản xuất và cho bộ thử nghiệm của bạn. Bạn chỉ cần ràng buộc FooUtilsgiao diện cho một trong hai RealFooUtilshoặc MockedFooUtils, và phần còn lại sẽ xảy ra cách tự động. Mỗi đối tượng phụ thuộc vào FooUtilsProviderđược phiên bản thích hợp của FooUtils.

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.