Khi nào nên sử dụng thuốc generic trong thiết kế giao diện


11

Tôi có một số giao diện mà tôi dự định các bên thứ ba sẽ triển khai trong tương lai và tôi tự cung cấp một triển khai cơ sở. Tôi sẽ chỉ sử dụng một vài để hiển thị ví dụ.

Hiện tại, chúng được định nghĩa là

Mục:

public interface Item {

    String getId();

    String getName();
}

Mục kho:

public interface ItemStackFactory {

    ItemStack createItemStack(Item item, int quantity);
}

ItemStackContainer:

public interface ItemStackContainer {

    default void add(ItemStack stack) {
        add(stack, 1);
    }

    void add(ItemStack stack, int quantity);
}

Bây giờ, ItemItemStackFactorytôi hoàn toàn có thể thấy trước một số bên thứ ba cần gia hạn trong tương lai. ItemStackContainercũng có thể được mở rộng trong tương lai, nhưng không phải theo cách mà tôi có thể thấy trước, ngoài việc triển khai mặc định được cung cấp của tôi.

Bây giờ, tôi đang cố gắng làm cho thư viện này mạnh nhất có thể; điều này vẫn còn ở giai đoạn đầu (tiền pre-alpha) vì vậy đây có thể là một hành động của kỹ thuật quá mức (YAGNI). Đây có phải là một nơi thích hợp để sử dụng thuốc generic?

public interface ItemStack<T extends Item> {

    T getItem();

    int getQuantity();
}

public interface ItemStackFactory<T extends ItemStack<I extends Item>> {

    T createItemStack(I item, int quantity);
}

Tôi sợ rằng điều này có thể sẽ khiến việc triển khai và sử dụng trở nên khó đọc và khó hiểu hơn; Tôi nghĩ rằng đó là khuyến nghị để tránh chung chung lồng nhau bất cứ nơi nào có thể.


1
Kinda cảm thấy như bạn đang phơi bày chi tiết thực hiện bằng bất kỳ cách nào. Tại sao người gọi cần phải làm việc và biết / quan tâm đến ngăn xếp chứ không phải số lượng đơn giản?
cHao

Đó là điều mà tôi chưa từng nghĩ đến. Điều đó cung cấp một số cái nhìn sâu sắc tốt đẹp về vấn đề cụ thể này. Tôi sẽ đảm bảo thực hiện các thay đổi cho mã của tôi.
Zymus

Câu trả lời:


9

Bạn sử dụng generic trong giao diện của bạn khi triển khai của bạn có khả năng là chung chung.

Ví dụ, bất kỳ cấu trúc dữ liệu nào có thể chấp nhận các đối tượng tùy ý là một ứng cử viên tốt cho giao diện chung. Ví dụ: List<T>Dictionary<K,V>.

Bất kỳ tình huống nào bạn muốn cải thiện an toàn loại theo cách tổng quát là một ứng cử viên tốt cho thuốc generic. A List<String>là một danh sách chỉ hoạt động trên chuỗi.

Bất kỳ tình huống nào bạn muốn áp dụng SRP một cách khái quát và tránh các phương pháp cụ thể theo kiểu là một ứng cử viên tốt cho thuốc generic. Ví dụ: PersonDocument, PlaceDocument và ThingDocument có thể được thay thế bằng a Document<T>.

Mẫu Tóm tắt Factory là trường hợp sử dụng tốt cho chung, trong khi Phương thức Factory thông thường chỉ đơn giản là tạo các đối tượng kế thừa từ giao diện cụ thể, phổ biến.


Tôi thực sự không đồng ý với ý kiến ​​rằng các giao diện chung nên được kết hợp với các triển khai chung. Khai báo một tham số loại chung trong một giao diện và sau đó tinh chỉnh hoặc khởi tạo nó trong các triển khai khác nhau là một kỹ thuật mạnh mẽ. Nó đặc biệt phổ biến ở Scala. Giao diện những thứ trừu tượng; các lớp học chuyên về họ.
Benjamin Hodgson

@BenjaminHodgson: điều đó chỉ có nghĩa là bạn đang tạo ra các triển khai trên một giao diện chung cho các loại cụ thể. Cách tiếp cận tổng thể vẫn còn chung chung, mặc dù có phần ít hơn như vậy.
Robert Harvey

Tôi đồng ý. Có lẽ tôi đã hiểu nhầm câu trả lời của bạn - bạn dường như đang khuyên chống lại kiểu thiết kế đó.
Benjamin Hodgson

@BenjaminHodgson: Không phải Phương thức xuất xưởng sẽ được viết dựa trên giao diện cụ thể chứ không phải là chung chung? (mặc dù một Nhà máy Trừu tượng sẽ chung chung)
Robert Harvey

Tôi nghĩ rằng chúng tôi đồng ý :) Có lẽ tôi sẽ viết ví dụ của OP như một cái gì đó như thế class StackFactory { Stack<T> Create<T>(T item, int count); }. Tôi có thể viết một ví dụ về ý nghĩa của tôi bằng cách "tinh chỉnh tham số loại trong lớp con" trong câu trả lời nếu bạn muốn làm rõ hơn, mặc dù câu trả lời sẽ chỉ liên quan đến câu hỏi ban đầu.
Benjamin Hodgson

8

Kế hoạch của bạn về cách giới thiệu tính tổng quát cho giao diện của bạn dường như là chính xác với tôi. Tuy nhiên, câu hỏi của bạn về việc đó sẽ là một ý tưởng tốt hay không đòi hỏi một câu trả lời phức tạp hơn.

Trong những năm qua, tôi đã phỏng vấn một số ứng cử viên cho các vị trí Kỹ sư phần mềm thay mặt cho các công ty mà tôi đã làm việc và tôi nhận ra rằng phần lớn những người tìm việc ngoài kia đều cảm thấy thoải mái khi sử dụng các lớp chung, (ví dụ: các lớp thu thập tiêu chuẩn,) nhưng không viết các lớp chung. Hầu hết chưa bao giờ viết một lớp chung chung trong sự nghiệp của họ, và trông như thể họ sẽ hoàn toàn bị mất nếu được yêu cầu viết một lớp. Vì vậy, nếu bạn buộc sử dụng thuốc generic cho bất kỳ ai muốn triển khai giao diện của bạn hoặc mở rộng triển khai cơ sở của bạn, bạn có thể sẽ khiến mọi người bỏ qua.

Tuy nhiên, những gì bạn có thể thử là cung cấp cả phiên bản chung và không chung cho giao diện và triển khai cơ sở của bạn, để những người không làm thuốc generic có thể sống hạnh phúc trong thế giới hẹp, nặng kiểu của họ, trong khi những người làm thuốc generic có thể gặt hái những lợi ích của sự hiểu biết rộng hơn về ngôn ngữ của họ.


3

Bây giờ, ItemItemStackFactorytôi hoàn toàn có thể thấy trước một số bên thứ ba cần gia hạn trong tương lai.

Có quyết định của bạn cho bạn. Nếu bạn không cung cấp thuốc generic, họ sẽ cần tăng cường triển khai Itemmỗi khi họ sử dụng:

class MyItem implements Item {
}
MyItem item = new MyItem();
ItemStack is = createItemStack(item, 10);
// They would have to cast here.
MyItem theItem = (MyItem)is.getItem();

Bạn ít nhất nên làm cho ItemStackchung chung theo cách bạn đề xuất. Lợi ích sâu sắc nhất của thuốc generic là bạn hầu như không cần phải đúc bất cứ thứ gì, và đưa ra mã buộc người dùng phải sử dụng là IMHO phạm tội hình sự.


2

Wow ... tôi có một cách hoàn toàn khác về điều này.

Tôi hoàn toàn có thể thấy trước một số nhu cầu của bên thứ ba

Đây là một sai lầm. Bạn không nên viết mã "cho tương lai".

Ngoài ra, giao diện được viết từ trên xuống. Bạn không nhìn vào một lớp và nói "Này, lớp này hoàn toàn có thể thực hiện một giao diện và sau đó tôi cũng có thể sử dụng giao diện đó ở một nơi khác". Đó là một cách tiếp cận sai, bởi vì chỉ có lớp sử dụng giao diện mới thực sự biết giao diện nên là gì. Thay vào đó, bạn nên suy nghĩ "Ở điểm này, lớp của tôi cần một sự phụ thuộc. Hãy để tôi xác định một giao diện cho sự phụ thuộc đó. Khi tôi viết xong lớp này, tôi sẽ viết một triển khai của sự phụ thuộc đó." Việc ai đó sẽ sử dụng lại giao diện của bạn và thay thế việc triển khai mặc định của bạn bằng giao diện của họ hay không là hoàn toàn không liên quan. Họ có thể không bao giờ làm. Điều này không có nghĩa là giao diện là vô dụng. Nó làm cho mã của bạn tuân theo nguyên tắc Inversion of Control, điều này làm cho mã của bạn rất mở rộng ở nhiều nơi "


0

Tôi nghĩ rằng sử dụng thuốc generic trong tình huống của bạn là quá phức tạp.

Nếu bạn chọn phiên bản chung của giao diện, thiết kế chỉ ra rằng

  1. ItemStackContainer <A> chứa một loại ngăn xếp, A. (tức là ItemStackContainer không thể chứa nhiều loại ngăn xếp.)
  2. ItemStack dành riêng cho một loại mặt hàng cụ thể. Vì vậy, không có loại trừu tượng của tất cả các loại ngăn xếp.
  3. Bạn cần xác định một ItemStackFactory cụ thể cho từng loại mặt hàng. Nó được coi là quá tốt như Mô hình nhà máy.
  4. Viết mã khó hơn như ItemStack <? mở rộng Mục>, giảm khả năng bảo trì.

Những giả định và hạn chế ngầm này là những điều rất quan trọng để quyết định cẩn thận. Vì vậy, đặc biệt trong giai đoạn đầu, những thiết kế sớm này không nên được giới thiệu. Đơn giản, dễ hiểu, thiết kế chung là mong muốn.


0

Tôi muốn nói rằng nó phụ thuộc chủ yếu vào câu hỏi này: Nếu ai đó cần mở rộng chức năng của ItemItemStackFactory,

  • họ sẽ chỉ ghi đè lên các phương thức và vì vậy các thể hiện của các phân nhóm của chúng sẽ được sử dụng giống như các lớp cơ sở, chỉ hành xử khác nhau?
  • hoặc họ sẽ thêm thành viên và sử dụng các lớp con khác nhau?

Trong trường hợp đầu tiên, không cần phải có thuốc generic, trong trường hợp thứ hai, có lẽ là có.


0

Có lẽ, bạn có thể có một hệ thống phân cấp giao diện, trong đó Foo là một giao diện, Bar là một giao diện khác. Bất cứ khi nào một loại phải là cả hai, đó là một FooAndBar.

Để tránh bị giảm quá mức, chỉ tạo loại FooAndBar khi cần thiết và giữ một danh sách các giao diện sẽ không bao giờ mở rộng bất cứ điều gì.

Điều này thoải mái hơn nhiều đối với hầu hết các lập trình viên (hầu hết thích kiểm tra kiểu của họ hoặc không bao giờ muốn xử lý kiểu gõ tĩnh nào). Rất ít người đã viết lớp khái quát của riêng họ.

Cũng như lưu ý: giao diện là về những hành động có thể có thể được chạy. Chúng thực sự nên là động từ không phải danh từ.


Đây có thể là một thay thế cho việc sử dụng thuốc generic, nhưng câu hỏi ban đầu hỏi về khi nào nên sử dụng thuốc generic so với những gì bạn đề xuất. Bạn có thể cải thiện câu trả lời của mình để đề xuất rằng thuốc generic không cần thiết bao giờ, nhưng sử dụng nhiều giao diện là câu trả lời cho tất cả?
Jay Elston
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.