Thực hành tốt hay xấu để che dấu các bộ sưu tập Java với các tên lớp có ý nghĩa?


46

Gần đây tôi đã có thói quen "che giấu" các bộ sưu tập Java với các tên lớp thân thiện với con người. Một số ví dụ đơn giản:

// Facade class that makes code more readable and understandable.
public class WidgetCache extends Map<String, Widget> {
}

Hoặc là:

// If you saw a ArrayList<ArrayList<?>> being passed around in the code, would you
// run away screaming, or would you actually understand what it is and what
// it represents?
public class Changelist extends ArrayList<ArrayList<SomePOJO>> {
}

Một đồng nghiệp đã chỉ ra cho tôi rằng đây là một thực tiễn tồi và giới thiệu độ trễ / độ trễ, cũng như là một mô hình chống OO. Tôi có thể hiểu nó giới thiệu một mức độ hiệu suất rất nhỏ , nhưng không thể tưởng tượng được nó có ý nghĩa gì cả. Vì vậy, tôi hỏi: điều này tốt hay xấu để làm, và tại sao?


11
Nó đơn giản hơn nhiều so với điều này. Đó là cách thực hành tồi vì tôi tưởng tượng bạn đang mở rộng việc triển khai Bộ sưu tập JDK cơ bản của Java. Trong Java, bạn chỉ có thể mở rộng một lớp, vì vậy bạn phải suy nghĩ và thiết kế nhiều hơn khi bạn có một phần mở rộng. Trong Java, sử dụng mở rộng một cách tiết kiệm.
Được thông báo vào

ChangeListbiên dịch sẽ phá vỡ tại extends, bởi vì Danh sách là một giao diện, yêu cầu implements. @randomA những gì bạn tưởng tượng bị mất điểm vì lỗi này
gnat

@gnat Không thiếu điểm nào, tôi giả sử rằng anh ta đang mở rộng một implementationnghĩa HashMaphay TreeMapvà những gì anh ta có là một lỗi đánh máy.
Được thông báo vào

4
Đây là một thực hành BAD. XẤU XẤU XẤU. Đừng làm điều này. Mọi người đều biết Bản đồ <Chuỗi, Tiện ích> là gì. Nhưng một WidgetCache? Bây giờ tôi cần mở WidgetCache.java, tôi cần nhớ rằng WidgetCache chỉ là một bản đồ. Tôi phải kiểm tra mỗi khi phiên bản mới xuất hiện rằng bạn chưa thêm thứ gì mới vào WidgetCache. Chúa không, không bao giờ làm điều này.
Miles Rout

"Nếu bạn thấy một ArrayList <ArrayList <? >> được truyền xung quanh trong mã, bạn sẽ chạy đi la hét ..?" Không, tôi khá thoải mái với các bộ sưu tập chung lồng nhau. Và bạn cũng nên như thế.
Kevin Krumwiede

Câu trả lời:


75

Độ trễ / Độ trễ? Tôi gọi cho BS về điều đó. Cần có chính xác không chi phí từ thực tế này. ( Chỉnh sửa: Trên thực tế, các ý kiến ​​đã chỉ ra rằng điều này có thể ức chế tối ưu hóa được thực hiện bởi HotSpot VM. Tôi không biết đủ về việc triển khai VM để xác nhận hoặc từ chối điều này. Tôi đã dựa vào nhận xét của mình về C ++ thực hiện các chức năng ảo.)

Có một số mã trên đầu. Bạn phải tạo tất cả các hàm tạo từ lớp cơ sở mà bạn muốn, chuyển tiếp các tham số của chúng.

Tôi cũng không thấy nó là một mô hình chống đối. Tuy nhiên, tôi thấy đó là một cơ hội bị bỏ lỡ. Thay vì tạo một lớp xuất phát từ lớp cơ sở chỉ vì mục đích đổi tên, bạn thay vào đó tạo một lớp có chứa bộ sưu tập và cung cấp giao diện được cải tiến theo từng trường hợp cụ thể? Bộ nhớ cache widget của bạn có thực sự cung cấp giao diện đầy đủ của bản đồ không? Hoặc thay vào đó nên cung cấp một giao diện chuyên dụng?

Hơn nữa, trong trường hợp các bộ sưu tập, mẫu đơn giản là không hoạt động cùng với quy tắc chung là sử dụng giao diện, không phải triển khai - nghĩa là, trong mã bộ sưu tập đơn giản, bạn sẽ tạo một HashMap<String, Widget>, sau đó gán nó cho một biến loại Map<String, Widget>. Bạn WidgetCachekhông thể mở rộng Map<String, Widget>, vì đó là một giao diện. Nó không thể là một giao diện mở rộng giao diện cơ sở, bởi vì HashMap<String, Widget>không thực hiện giao diện đó và cũng không có bất kỳ bộ sưu tập tiêu chuẩn nào khác. Và trong khi bạn có thể biến nó thành một lớp mở rộng HashMap<String, Widget>, thì bạn phải khai báo các biến là WidgetCachehoặc Map<String, Widget>, đầu tiên bạn mất tính linh hoạt để thay thế một bộ sưu tập khác (có thể là một bộ sưu tập tải lười biếng của ORM), trong khi loại thứ hai đánh bại điểm có lớp học

Một số trong những điểm đối ứng này cũng áp dụng cho lớp chuyên ngành đề xuất của tôi.

Đây là tất cả các điểm để xem xét. Nó có thể hoặc không thể là lựa chọn đúng đắn. Trong cả hai trường hợp, các đối số được cung cấp của đồng nghiệp của bạn không hợp lệ. Nếu anh ta nghĩ rằng đó là một mô hình chống, anh ta nên đặt tên cho nó.


1
Lưu ý rằng mặc dù hoàn toàn có thể có một số tác động hiệu suất, nhưng nó sẽ không khủng khiếp như vậy (thiếu một số tối ưu hóa nội tuyến và nhận thêm một vcall trong trường hợp xấu nhất - không tuyệt vời trong vòng lặp trong cùng của bạn nhưng có lẽ vẫn ổn).
Voo

5
Câu trả lời thực sự tốt này nói với mọi người "đừng phán xét quyết định bằng chi phí hoạt động" - và cuộc thảo luận duy nhất dưới đây là "HotSpot xử lý vấn đề này như thế nào, có một số (trong 99,9% tất cả các trường hợp) tác động hiệu suất không liên quan" - các bạn, bạn thậm chí còn bận tâm để đọc nhiều hơn câu đầu tiên?
Doc Brown

16
+1 cho "Thay vì tạo một lớp xuất phát từ lớp cơ sở chỉ vì mục đích đổi tên, bạn thay vào đó tạo một lớp có chứa bộ sưu tập và cung cấp giao diện được cải tiến theo từng trường hợp cụ thể?"
dùng11153

6
Ví dụ thực tế minh họa điểm chính trong câu trả lời này: Các tài liệu cho public class Properties extends Hashtable<Object,Object>trong java.utilnói "Bởi vì tính kế thừa từ Hashtable, giao dịch thỏa thuận và phương pháp putAll có thể được áp dụng cho một đối tượng Properties sử dụng của họ không được khuyến khích mạnh mẽ vì chúng cho phép người gọi để chèn mục mà phím. hoặc các giá trị không phải là Chuỗi. ". Thành phần sẽ được sạch sẽ hơn nhiều.
Patricia Shanahan

3
@DocBrown Không, câu trả lời này khẳng định rằng không có tác động hiệu suất nào cả. Nếu bạn đưa ra những tuyên bố mạnh mẽ, bạn tốt hơn có thể hỗ trợ họ, nếu không mọi người có thể sẽ gọi bạn ra khỏi đó. Toàn bộ ý kiến ​​(về câu trả lời) là chỉ ra những sự thật hoặc giả định không chính xác hoặc thêm ghi chú hữu ích, nhưng chắc chắn không chúc mừng mọi người, đó là những gì hệ thống bỏ phiếu dành cho.
Voo

25

Theo IBM đây thực sự là một mô hình chống. Các lớp giống như 'typedef' này được gọi là các kiểu psuedo.

Bài viết giải thích nó tốt hơn tôi rất nhiều, nhưng tôi sẽ cố gắng tóm tắt nó trong trường hợp liên kết bị hỏng:

  • Bất kỳ mã nào mong đợi WidgetCachekhông thể xử lý mộtMap<String, Widget>
  • Các kiểu giả này là 'virus' khi sử dụng nhiều gói chúng dẫn đến sự không tương thích trong khi loại cơ sở (chỉ là Bản đồ ngớ ngẩn <...>) sẽ hoạt động trong tất cả các trường hợp trong tất cả các gói.
  • Các loại giả thường là cụ thể, chúng không triển khai các giao diện cụ thể vì các lớp cơ sở của chúng chỉ thực hiện phiên bản chung.

Trong bài viết họ đề xuất thủ thuật sau đây để làm cho cuộc sống dễ dàng hơn mà không cần sử dụng các loại giả:

public static <K,V> Map<K,V> newHashMap() {
    return new HashMap<K,V>(); 
}

Map<Socket, Future<String>> socketOwner = Util.newHashMap();

Mà hoạt động do suy luận loại tự động.

(Tôi đã đi đến câu trả lời này thông qua câu hỏi tràn ngăn xếp liên quan này )


13
newHashMapBây giờ không phải là cần giải quyết bởi các nhà điều hành kim cương?
Svick

Hoàn toàn đúng, quên mất điều đó. Tôi thường không làm việc trong Java.
Roy T.

Tôi ước các ngôn ngữ sẽ cho phép một vũ trụ gồm các loại biến chứa các tham chiếu đến các thể hiện của các loại khác, nhưng vẫn có thể hỗ trợ kiểm tra thời gian biên dịch, sao cho giá trị của loại WidgetCachecó thể được gán cho một biến loại WidgetCachehoặc Map<String,Widget>không có kiểu đúc, nhưng ở đó là một phương tiện mà một phương thức tĩnh WidgetCachecó thể thực hiện tham chiếu Map<String,Widget>và trả về nó dưới dạng WidgetCachesau khi thực hiện bất kỳ xác nhận mong muốn nào. Một tính năng như vậy có thể đặc biệt hữu ích với thuốc generic, không cần phải xóa kiểu (kể từ ...
supercat

... các loại sẽ chỉ tồn tại trong tâm trí của trình biên dịch). Đối với nhiều loại có thể thay đổi, có hai loại trường tham chiếu phổ biến: một loại giữ tham chiếu duy nhất đến một thể hiện có thể bị đột biến hoặc một loại có tham chiếu có thể chia sẻ đến một thể hiện không ai được phép đột biến. Sẽ rất hữu ích khi có thể đặt tên khác nhau cho hai loại trường đó, vì chúng yêu cầu các kiểu sử dụng khác nhau, nhưng cả hai nên giữ các tham chiếu đến cùng loại đối tượng.
supercat

5
Đây là một đối số tuyệt vời cho lý do tại sao Java cần các bí danh loại.
GlenPeterson

13

Lượt truy cập hiệu suất sẽ bị giới hạn tối đa là một tra cứu vtable, mà bạn rất có thể đã phát sinh. Đó không phải là một lý do hợp lệ để phản đối nó.

Tình huống này đủ phổ biến đến mức hầu hết tất cả các ngôn ngữ lập trình gõ tĩnh đều có cú pháp đặc biệt cho các kiểu răng cưa, thường được gọi là a typedef. Java có thể đã không sao chép chúng vì ban đầu nó không có các loại tham số hóa. Mở rộng một lớp học không lý tưởng, vì những lý do mà Sebastian trình bày rất tốt trong câu trả lời của anh ta, nhưng nó có thể là một cách giải quyết hợp lý cho cú pháp giới hạn của Java.

Typedefs có một số lợi thế. Chúng thể hiện ý định của lập trình viên rõ ràng hơn, với một cái tên tốt hơn, ở mức độ trừu tượng phù hợp hơn. Chúng dễ dàng hơn để tìm kiếm các mục đích gỡ lỗi hoặc tái cấu trúc. Xem xét việc tìm kiếm mọi nơi a WidgetCacheđược sử dụng so với việc tìm những cách sử dụng cụ thể đó của a Map. Chúng dễ dàng thay đổi hơn, ví dụ nếu sau này bạn thấy bạn cần một cái LinkedHashMapthay thế, hoặc thậm chí là thùng chứa tùy chỉnh của riêng bạn.


Ngoài ra còn có một chi phí rất nhỏ trong các nhà xây dựng.
Stephen C

11

Tôi đề nghị bạn, như những người khác đã đề cập, sử dụng thành phần trên kế thừa, vì vậy bạn chỉ có thể hiển thị các phương thức thực sự cần thiết, với các tên khớp với trường hợp sử dụng dự định. Người dùng của lớp bạn có thực sự cần biết đó WidgetCachelà bản đồ không? Và có thể làm gì với nó bất cứ điều gì họ muốn? Hoặc họ chỉ cần biết đó là bộ đệm cho các widget?

Lớp ví dụ từ cơ sở mã của tôi, với giải pháp cho vấn đề tương tự mà bạn mong muốn:

public class Translations {

    private Map<Locale, Properties> translations = new HashMap<>();

    public void appendMessage(Locale locale, String code, String message) {
        /* code */
    }

    public void addMessages(Locale locale, Properties messages) {
        /* code */
    }

    public String getMessage(Locale locale, String code) {
        /* code */
    }

    public boolean localeExists(Locale locale) {
        /* code */
    }
}

Bạn có thể thấy rằng bên trong nó "chỉ là một bản đồ", nhưng giao diện công cộng không hiển thị điều này. Và nó có các phương pháp "thân thiện với lập trình viên" như appendMessage(Locale locale, String code, String message)cách chèn các mục mới dễ dàng và có ý nghĩa hơn. Và người dùng của lớp không thể làm, ví dụ translations.clear(), bởi vì Translationskhông mở rộng Map.

Tùy chọn, bạn luôn có thể ủy quyền một số phương thức cần thiết cho bản đồ được sử dụng nội bộ.


6

Tôi thấy đây là một ví dụ về một sự trừu tượng có ý nghĩa. Một sự trừu tượng tốt có một vài đặc điểm:

  1. Nó ẩn các chi tiết triển khai không liên quan đến mã tiêu thụ nó.

  2. Nó chỉ phức tạp như nó cần phải được.

Bằng cách mở rộng, bạn sẽ hiển thị toàn bộ giao diện của cha mẹ, nhưng trong nhiều trường hợp, phần lớn có thể được ẩn tốt hơn, vì vậy bạn muốn làm những gì Sebastian Redl gợi ý và ưu tiên bố cục hơn là kế thừa và thêm một ví dụ của cha mẹ là một thành viên riêng của lớp tùy chỉnh của bạn. Bất kỳ phương thức giao diện nào có ý nghĩa cho sự trừu tượng của bạn đều có thể dễ dàng được ủy quyền cho (trong trường hợp của bạn) bộ sưu tập bên trong.

Đối với tác động hiệu suất, trước tiên, luôn luôn là một ý tưởng tốt để tối ưu hóa mã cho khả năng đọc và nếu nghi ngờ tác động hiệu suất, hãy lập hồ sơ mã để so sánh hai triển khai.


4

+1 cho các câu trả lời khác ở đây. Tôi cũng sẽ nói thêm rằng nó thực sự được coi là thực tiễn rất tốt bởi cộng đồng Thiết kế hướng miền (DDD). Họ ủng hộ rằng tên miền của bạn và các tương tác với nó phải có ý nghĩa miền ngữ nghĩa trái ngược với cấu trúc dữ liệu cơ bản. A Map<String, Widget>có thể là bộ đệm, nhưng cũng có thể là một thứ khác, những gì bạn đã thực hiện chính xác trong Ý kiến ​​không khiêm tốn của tôi (IMNSHO) là mô hình hóa những gì bộ sưu tập đại diện, trong trường hợp này là bộ đệm.

Tôi sẽ thêm một chỉnh sửa quan trọng trong đó trình bao bọc lớp miền xung quanh cấu trúc dữ liệu cơ bản có lẽ cũng có các biến hoặc hàm thành viên khác thực sự biến nó thành một lớp miền với các tương tác chứ không phải chỉ là cấu trúc dữ liệu (nếu chỉ Java có các loại Giá trị , chúng tôi sẽ nhận được chúng trong Java 10 - xin hứa!)

Thật thú vị khi xem các luồng của Java 8 sẽ ảnh hưởng như thế nào đến tất cả những điều này, tôi có thể tưởng tượng rằng có lẽ một số giao diện công cộng sẽ thích xử lý Luồng (chèn nguyên hàm Java hoặc Chuỗi chung) so với đối tượng Java.

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.