Mô hình Builder không giải quyết được vấn đề về mối quan hệ của nhiều người. Nhưng tại sao nhiều tranh luận có vấn đề?
- Chúng cho thấy lớp học của bạn có thể làm quá nhiều . Tuy nhiên, có nhiều loại có chứa nhiều thành viên hợp pháp không thể được nhóm lại một cách hợp lý.
- Kiểm tra và hiểu một chức năng với nhiều đầu vào trở nên phức tạp hơn theo cấp số nhân - theo nghĩa đen!
- Khi ngôn ngữ không cung cấp các tham số được đặt tên, một lệnh gọi hàm không tự ghi lại . Đọc một lệnh gọi hàm với nhiều đối số là khá khó khăn vì bạn không biết tham số thứ 7 được cho là phải làm gì. Bạn thậm chí sẽ không nhận ra nếu đối số thứ 5 và thứ 6 bị hoán đổi một cách tình cờ, đặc biệt nếu bạn đang sử dụng ngôn ngữ được gõ động hoặc mọi thứ xảy ra là một chuỗi hoặc khi tham số cuối cùng là
true
vì một lý do nào đó.
Làm giả tên
Các mẫu Builder địa chỉ chỉ là một trong những vấn đề này, cụ thể là những mối quan tâm bảo trì của các cuộc gọi chức năng với nhiều đối số * . Vì vậy, một chức năng gọi như
MyClass o = new MyClass(a, b, c, d, e, f, g);
có thể trở thành
MyClass o = MyClass.builder()
.a(a).b(b).c(c).d(d).e(e).f(f).g(g)
.build();
Pattern Mẫu Builder ban đầu được dự định là một cách tiếp cận đại diện - bất khả tri để lắp ráp các đối tượng hỗn hợp, đó là một khát vọng lớn hơn nhiều so với các đối số chỉ được đặt tên cho các tham số. Đặc biệt, mẫu xây dựng không yêu cầu giao diện lưu loát.
Điều này mang lại một chút an toàn vì nó sẽ nổ tung nếu bạn gọi một phương thức xây dựng không tồn tại, nhưng nó không mang lại cho bạn bất cứ điều gì mà một lời nhận xét trong lời gọi của nhà xây dựng sẽ không có. Ngoài ra, việc tạo một trình xây dựng theo cách thủ công yêu cầu mã và nhiều mã hơn luôn có thể chứa nhiều lỗi hơn.
Trong các ngôn ngữ dễ xác định loại giá trị mới, tôi thấy rằng sử dụng microtyping / loại nhỏ để mô phỏng các đối số được đặt tên là cách tốt hơn . Nó được đặt tên như vậy bởi vì các loại thực sự nhỏ, nhưng cuối cùng bạn lại gõ nhiều hơn ;-)
MyClass o = new MyClass(
new MyClass.A(a), new MyClass.B(b), new MyClass.C(c),
new MyClass.D(d), new MyClass.E(e), new MyClass.F(f),
new MyClass.G(g));
Rõ ràng, các tên kiểu A
, B
, C
, ... nên có tên tự tài liệu minh họa ý nghĩa của các tham số, thường là tên giống như bạn muốn cung cấp cho các biến tham số. So với thành ngữ của trình xây dựng cho tên được đặt tên, việc triển khai được yêu cầu đơn giản hơn rất nhiều và do đó ít có khả năng chứa lỗi. Ví dụ: (với cú pháp Java-ish):
class MyClass {
...
public static class A {
public final int value;
public A(int a) { value = a; }
}
...
}
Trình biên dịch giúp bạn đảm bảo rằng tất cả các đối số đã được cung cấp; với Trình tạo, bạn phải kiểm tra thủ công các đối số bị thiếu hoặc mã hóa máy trạng thái vào hệ thống loại ngôn ngữ máy chủ - cả hai đều có thể chứa lỗi.
Có một cách tiếp cận phổ biến khác để mô phỏng các đối số được đặt tên: một đối tượng tham số trừu tượng duy nhất sử dụng cú pháp lớp nội tuyến để khởi tạo tất cả các trường. Trong Java:
MyClass o = new MyClass(new MyClass.Arguments(){{ argA = a; argB = b; argC = c; ... }});
class MyClass {
...
public static abstract class Arguments {
public int argA;
public String ArgB;
...
}
}
Tuy nhiên, có thể quên các trường và đây là một giải pháp khá cụ thể về ngôn ngữ (tôi đã thấy sử dụng trong JavaScript, C # và C).
May mắn thay, hàm tạo vẫn có thể xác thực tất cả các đối số, đó không phải là trường hợp khi các đối tượng của bạn được tạo ở trạng thái được xây dựng một phần và yêu cầu người dùng cung cấp thêm các đối số thông qua setters hoặc một init()
phương thức - những yêu cầu ít nỗ lực mã hóa nhất, nhưng thực hiện khó khăn hơn để viết chương trình chính xác .
Vì vậy, trong khi có nhiều cách tiếp cận để giải quyết vấn đề, nhiều tham số chưa được đặt tên khiến mã khó duy trì vấn đề, thì các vấn đề khác vẫn còn.
Tiếp cận vấn đề gốc
Ví dụ như bài toán kiểm tra. Khi tôi viết thử nghiệm đơn vị, tôi cần khả năng tiêm dữ liệu thử nghiệm và cung cấp các triển khai thử nghiệm để loại bỏ các phụ thuộc và hoạt động có tác dụng phụ bên ngoài. Tôi không thể làm điều đó khi bạn khởi tạo bất kỳ lớp nào trong hàm tạo của bạn. Trừ khi trách nhiệm của lớp bạn là tạo ra các đối tượng khác, nó không nên khởi tạo bất kỳ lớp không tầm thường nào. Điều này đi đôi với vấn đề trách nhiệm duy nhất. Trách nhiệm của một lớp càng tập trung, thì càng dễ kiểm tra (và thường dễ sử dụng hơn).
Cách tiếp cận dễ nhất và thường xuyên nhất là để nhà xây dựng lấy các phụ thuộc được xây dựng đầy đủ làm tham số , mặc dù điều này làm giảm trách nhiệm quản lý các phụ thuộc cho người gọi - không lý tưởng, trừ khi các phụ thuộc là các thực thể độc lập trong mô hình miền của bạn.
Đôi khi, các nhà máy (trừu tượng) hoặc khung tiêm phụ thuộc đầy đủ được sử dụng thay thế, mặc dù những điều này có thể là quá mức cần thiết trong phần lớn các trường hợp sử dụng. Cụ thể, những điều này chỉ làm giảm số lượng đối số nếu nhiều trong số các đối số này là các đối tượng toàn cầu hoặc các giá trị cấu hình không thay đổi giữa khởi tạo đối tượng. Ví dụ: nếu tham số a
và d
là toàn cầu, chúng tôi sẽ nhận được
Dependencies deps = new Dependencies(a, d);
...
MyClass o = deps.newMyClass(b, c, e, f, g);
class MyClass {
MyClass(Dependencies deps, B b, C c, E e, F f, G g) {
this.depA = deps.newDepA(b, c);
this.depB = deps.newDepB(e, f);
this.g = g;
}
...
}
class Dependencies {
private A a;
private D d;
public Dependencies(A a, D d) { this.a = a; this.d = d; }
public DepA newDepA(B b, C c) { return new DepA(a, b, c); }
public DepB newDepB(E e, F f) { return new DepB(d, e, f); }
public MyClass newMyClass(B b, C c, E e, F f, G g) {
return new MyClass(deps, b, c, e, f, g);
}
}
Tùy thuộc vào ứng dụng, đây có thể là một công cụ thay đổi trò chơi trong đó các phương thức xuất xưởng gần như không có đối số vì tất cả có thể được cung cấp bởi người quản lý phụ thuộc hoặc có thể là một số lượng lớn mã làm phức tạp hóa việc tạo tức thời mà không có lợi ích rõ ràng. Các nhà máy như vậy rất hữu ích cho việc ánh xạ các giao diện tới các loại cụ thể hơn là để quản lý các tham số. Tuy nhiên, phương pháp này cố gắng giải quyết vấn đề gốc của quá nhiều tham số thay vì chỉ ẩn nó với giao diện khá trôi chảy.