Làm thế nào để một đối số giữ số lượng thấp và vẫn tách biệt các phụ thuộc của bên thứ ba?


13

Tôi sử dụng thư viện của bên thứ ba. Họ truyền cho tôi một POJO mà, với ý định và mục đích của chúng tôi, có lẽ được triển khai như thế này:

public class OurData {
  private String foo;
  private String bar;
  private String baz;
  private String quux;
  // A lot more than this

  // IMPORTANT: NOTE THAT THIS IS A PACKAGE PRIVATE CONSTRUCTOR
  OurData(/* I don't know what they do */) {
    // some stuff
  }

  public String getFoo() {
    return foo;
  }

  // etc.
}

Vì nhiều lý do, bao gồm nhưng không giới hạn trong việc đóng gói API của họ và tạo điều kiện cho thử nghiệm đơn vị, tôi muốn bọc dữ liệu của họ. Nhưng tôi không muốn các lớp cốt lõi của mình phụ thuộc vào dữ liệu của họ (một lần nữa, vì lý do kiểm tra)! Vì vậy, ngay bây giờ tôi có một cái gì đó như thế này:

public class DataTypeOne implements DataInterface {
  private String foo;
  private int bar;
  private double baz;

  public DataTypeOne(String foo, int bar, double baz) {
    this.foo = foo;
    this.bar = bar;
    this.baz = baz;
  }
}

public class DataTypeTwo implements DataInterface {
  private String foo;
  private int bar;
  private double baz;

  public DataTypeOne(String foo, int bar, double baz, String quux) {
    this.foo = foo;
    this.bar = bar;
    this.baz = baz;
    this.quux = quux;
  }
}

Và sau đó:

public class ThirdPartyAdapter {
  public static makeMyData(OurData data) {
    if(data.getQuux() == null) {
      return new DataTypeOne(
        data.getFoo(),
        Integer.parseInt(data.getBar()),
        Double.parseDouble(data.getBaz()),
      );
    } else {
      return new DataTypeTwo(
        data.getFoo(),
        Integer.parseInt(data.getBar()),
        Double.parseDouble(data.getBaz()),
        data.getQuux();
      );
  }
}

Lớp bộ điều hợp này được kết hợp với một vài lớp khác PHẢI biết về API của bên thứ ba, hạn chế tính phổ biến của nó thông qua phần còn lại của hệ thống của tôi. Tuy nhiên ... giải pháp này là GROSS! Trong Clean Code, trang 40:

Hơn ba đối số (đa âm) đòi hỏi sự biện minh rất đặc biệt - và sau đó không nên sử dụng.

Những điều tôi đã xem xét:

  • Tạo một đối tượng nhà máy chứ không phải là một phương thức trợ giúp tĩnh
    • Không giải quyết được vấn đề có một cuộc tranh cãi bajillion
  • Tạo một lớp con của DataTypeOne và DataTypeTwo có hàm tạo phụ thuộc
    • Vẫn có một hàm tạo được bảo vệ đa âm
  • Tạo các triển khai hoàn toàn riêng biệt phù hợp với cùng một giao diện
  • Nhiều ý tưởng trên cùng một lúc

Tình huống này nên được xử lý như thế nào?


Lưu ý đây không phải là một tình huống chống tham nhũng . Không có gì sai với API của họ. Các vấn đề là:

  • Tôi không muốn cấu trúc dữ liệu của tôi có import com.third.party.library.SomeDataStructure;
  • Tôi không thể xây dựng cấu trúc dữ liệu của họ trong các trường hợp thử nghiệm của mình
  • Giải pháp hiện tại của tôi dẫn đến số lượng đối số rất cao. Tôi muốn giữ số lượng đối số thấp, KHÔNG chuyển qua cấu trúc dữ liệu của họ.
  • Câu hỏi đó là " những gì một lớp chống tham nhũng là gì?". Câu hỏi của tôi là " làm thế nào tôi có thể sử dụng một mẫu, bất kỳ mẫu nào, để giải quyết tình huống này?"

Tôi cũng không yêu cầu mã (nếu không câu hỏi này sẽ có trên SO), chỉ yêu cầu đủ câu trả lời để cho phép tôi viết mã hiệu quả (câu hỏi đó không cung cấp).


Nếu có một số POJO của bên thứ 3 như vậy, có thể đáng để viết mã kiểm tra tùy chỉnh sử dụng Bản đồ với một số quy ước (ví dụ: đặt tên cho các khóa int_bar) làm đầu vào kiểm tra của bạn. Hoặc sử dụng JSON hoặc XML với một số mã trung gian tùy chỉnh. Trong thực tế, sắp xếp một DSL để thử nghiệm com.thirdparty.
dùng949300

Trích dẫn đầy đủ từ Clean Code:The ideal number of arguments for a function is zero (niladic). Next comes one (monadic), followed closely by two (dyadic). Three arguments (triadic) should be avoided where possible. More than three (polyadic) requires very special justification — and then shouldn’t be used anyway.
Lilienthal

11
Tuân thủ mù quáng với một mô hình hoặc hướng dẫn lập trình là chống mẫu riêng của nó .
Lilienthal

2
"Đóng gói API của họ và tạo điều kiện cho thử nghiệm đơn vị" Âm thanh như thế này có thể là một trường hợp của Thử nghiệm quá mức và / hoặc Thiệt hại thiết kế do thử nghiệm gây ra cho tôi (hoặc chỉ ra rằng bạn có thể thiết kế khác nhau để bắt đầu). Hãy tự hỏi mình điều này: điều này có thực sự làm cho mã của bạn dễ hiểu hơn và thay đổi và sử dụng lại không? Tôi sẽ đặt tiền của mình vào "không." Làm thế nào thực tế có khả năng là bạn sẽ trao đổi thư viện này? Có lẽ là không. Nếu bạn trao đổi nó ra, điều này có thực sự làm cho việc thả một cái hoàn toàn khác vào vị trí dễ dàng hơn không? Một lần nữa, tôi đặt cược vào "không."
jpmc26

1
@JamesAnderson Tôi chỉ sao chép toàn bộ trích dẫn bởi vì tôi thấy nó thú vị nhưng tôi không rõ ràng về đoạn trích cho dù nó đề cập đến các chức năng nói chung hay các nhà xây dựng cụ thể. Tôi không có ý tán thành yêu cầu đó và, như jpmc26 đã nói, bình luận tiếp theo của tôi sẽ cho bạn một số dấu hiệu cho thấy tôi đã không làm như vậy. Tôi không chắc tại sao bạn cảm thấy cần phải tấn công các học giả nhưng sử dụng các từ đa nghĩa không khiến ai đó trở thành một nhà tinh hoa học thuật ngồi trên tháp ngà của mình trên những đám mây.
Lilienthal

Câu trả lời:


10

Chiến lược tôi đã sử dụng khi có một vài tham số khởi tạo là tạo một loại chỉ chứa các tham số để khởi tạo

public class DataTypeTwoParameters {
    public String foo;  // use getters/setters instead if it's appropriate
    public int bar;
    public double baz;
    public String quuz;
}

Sau đó, hàm tạo cho DataTypeTwo lấy một đối tượng DataTypeTwoParameter và DataTypeTwo được xây dựng thông qua:

DataTypeTwoParameters p = new DataTypeTwoParameters();
p.foo = "Hello";
p.bar = 4;
p.baz = 3;
p.quuz = "World";

DataTypeTwo dtt = new DataTypeTwo(p);

Điều này mang lại rất nhiều cơ hội để làm cho nó rõ ràng tất cả các tham số đi vào DataTypeTwo là gì và ý nghĩa của chúng. Bạn cũng có thể cung cấp các giá trị mặc định hợp lý trong hàm tạo DataTypeTwoParameter để chỉ các giá trị cần được đặt mới có thể được thực hiện theo bất kỳ thứ tự nào mà người tiêu dùng thích API.


Cách tiếp cận thú vị. Bạn sẽ đặt một liên quan ở Integer.parseIntđâu? Trong một setter, hoặc bên ngoài lớp tham số?
durron597

5
Bên ngoài lớp tham số. Lớp tham số phải là một đối tượng "ngu ngốc" và không nên cố gắng làm bất cứ điều gì ngoài việc thể hiện các đầu vào cần thiết và các kiểu của chúng là gì. Phân tích cú pháp nên được thực hiện ở nơi khác, như : p.bar = Integer.parseInt("4").
Erik

7
âm thanh này giống như mẫu Đối tượng tham số
gnat

9
... Hoặc chống mẫu.
Telastyn

1
... Hoặc bạn có thể đổi tên DataTypeTwoParametersthành DataTypeTwo.
dùng253751

14

Bạn thực sự có hai mối quan tâm riêng biệt ở đây: gói một API và giữ cho số lượng đối số thấp.

Khi gói một API, ý tưởng là thiết kế giao diện như thể từ đầu, không biết gì ngoài các yêu cầu. Bạn nói rằng API của họ không có gì sai, trong cùng một hơi thở liệt kê một số điều sai với API của họ: khả năng kiểm tra, khả năng xây dựng, quá nhiều tham số trong một đối tượng, v.v. Viết API mà bạn muốn . Nếu điều đó đòi hỏi nhiều đối tượng thay vì một đối tượng, hãy làm điều đó. Nếu nó yêu cầu gói một cấp cao hơn, đối với các đối tượng tạo POJO, hãy làm điều đó.

Sau đó, khi bạn có API mong muốn, số lượng tham số có thể không còn là vấn đề nữa. Nếu có, có một số mẫu phổ biến để xem xét:

  • Một đối tượng tham số, như trong câu trả lời của Erik .
  • Mẫu trình xây dựng , nơi bạn tạo một đối tượng trình tạo riêng biệt, sau đó gọi một số setters để đặt các tham số riêng lẻ, sau đó tạo đối tượng kết thúc của bạn.
  • Các mô hình nguyên mẫu , nơi bạn sao chép lớp con của đối tượng mong muốn của bạn với các trường đã được thiết lập trong nội bộ.
  • Một nhà máy, mà bạn đã quen thuộc.
  • Một số kết hợp của trên.

Lưu ý rằng các mẫu sáng tạo này thường kết thúc việc gọi một hàm tạo đa hình, mà bạn nên xem xét ổn khi nó được đóng gói. Vấn đề với các nhà xây dựng đa âm không gọi cho họ một lần, đó là khi bạn buộc phải gọi cho họ mỗi khi bạn cần xây dựng một đối tượng.

Lưu ý rằng thông thường việc chuyển qua API bên dưới sẽ dễ dàng và dễ bảo trì hơn nhiều bằng cách lưu trữ một tham chiếu đến OurDatađối tượng và chuyển tiếp các cuộc gọi phương thức, thay vì cố gắng thực hiện lại các phần bên trong của nó. Ví dụ:

public class DataTypeTwo implements DataInterface {
  private OurData data;

  public DataTypeOne(OurData data) {
    this.data = data;
  }

   public String getFoo() {
    return data.getFoo();
  }

  public int getBar() {
    return Integer.parseInt(data.getBar());
  }
  ...
}

Nửa đầu của câu trả lời này: tuyệt vời, rất hữu ích, +1. Nửa sau của câu trả lời này: "chuyển qua API cơ bản bằng cách lưu trữ một tham chiếu đến OurDataĐối tượng" - đây là điều tôi đang cố gắng tránh, ít nhất là trong lớp cơ sở, để đảm bảo không có sự phụ thuộc.
durron597

1
Đó là lý do tại sao bạn chỉ làm điều đó trong một trong những triển khai của bạn DataInterface. Bạn tạo một triển khai khác cho các đối tượng giả của bạn.
Karl Bielefeldt

@ durron597: có, nhưng bạn đã biết cách giải quyết vấn đề đó nếu nó thực sự làm phiền bạn.
Doc Brown

1

Tôi nghĩ rằng bạn có thể diễn giải đề nghị của chú Bob quá nghiêm ngặt. Đối với các lớp bình thường, với logic và các phương thức và các hàm tạo và như vậy, một hàm tạo đa giác thực sự cảm thấy rất giống mùi mã. Nhưng đối với một cái gì đó hoàn toàn là một thùng chứa dữ liệu phơi bày các trường và được tạo ra bởi những gì thực chất là một đối tượng Factory, tôi không nghĩ nó quá tệ.

Bạn có thể sử dụng mẫu Đối tượng tham số, như được đề xuất trong một nhận xét, có thể bao bọc các tham số hàm tạo này cho bạn , về cơ bản, trình bao bọc kiểu dữ liệu cục bộ của bạn đã là một đối tượng Tham số. Tất cả các đối tượng Parameter của bạn sẽ làm là đóng gói các tham số (Bạn sẽ tạo nó như thế nào? Với một hàm tạo đa hình?) Và sau đó giải nén chúng một giây sau đó vào một đối tượng gần giống nhau.

Nếu bạn không muốn để lộ setters cho các lĩnh vực của mình và gọi cho họ, tôi nghĩ rằng việc gắn bó với một nhà xây dựng đa hình bên trong một nhà máy được xác định rõ và đóng gói là tốt.


Vấn đề là, số lượng các trường trong cấu trúc dữ liệu của tôi đã thay đổi nhiều lần và có thể sẽ thay đổi một lần nữa. Điều đó có nghĩa là tôi cần cấu trúc lại hàm tạo trong tất cả các trường hợp thử nghiệm của mình. Mẫu tham số với mặc định hợp lý nghe có vẻ tốt hơn; có một phiên bản có thể thay đổi được lưu vào dạng bất biến có thể giúp cuộc sống của tôi dễ dàng hơn bằng nhiều cách.
durron597
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.