Tại sao các bộ sưu tập Java được triển khai với các phương thức tùy chọn của Wap trong giao diện?


69

Trong lần triển khai đầu tiên mở rộng khung công tác bộ sưu tập Java, tôi khá ngạc nhiên khi thấy giao diện bộ sưu tập chứa các phương thức được khai báo là tùy chọn. Người triển khai dự kiến ​​sẽ ném UnsupportedOperationExceptions nếu không được hỗ trợ. Điều này ngay lập tức đánh tôi là một lựa chọn thiết kế API kém.

Sau khi đọc nhiều cuốn sách "Java hiệu quả" xuất sắc của Joshua Bloch, và sau đó học được rằng anh ta có thể chịu trách nhiệm cho những quyết định này, dường như nó không phù hợp với các nguyên tắc được tán thành trong cuốn sách. Tôi nghĩ rằng việc khai báo hai giao diện: Bộ sưu tập và MutableCollection mở rộng Bộ sưu tập với các phương thức "tùy chọn" sẽ dẫn đến mã máy khách dễ bảo trì hơn nhiều.

Có một bản tóm tắt tuyệt vời về các vấn đề ở đây .

Có lý do chính đáng tại sao các phương pháp tùy chọn được chọn thay vì thực hiện hai giao diện không?


IMO, không có lý do chính đáng. C ++ STL được tạo bởi Stepanov vào đầu những năm 80. Mặc dù khả năng sử dụng của nó bị giới hạn bởi cú pháp khuôn mẫu C ++ vụng về, nhưng nó là một nhóm tương đương về tính nhất quán và khả năng sử dụng khi so sánh với các lớp bộ sưu tập Java.
kevin cline

Đã có C ++ STL 'chuyển' sang Java. Nhưng nó lớn hơn gấp 5 lần trong lớp học. Với ý nghĩ này, tôi không mua rằng cái lớn hơn thì phù hợp và có thể sử dụng hơn. docs.oracle.com/javase/6/docs/technotes/guides/collections/iêu
m3th0dman

@ m3th0dman Nó lớn hơn nhiều trong Java vì Java không có bất cứ thứ gì có sức mạnh tương đương với các mẫu C ++.
kevin cline

Có thể đó chỉ là một phong cách kỳ lạ mà tôi đã phát triển, nhưng mã của tôi có xu hướng coi tất cả các bộ sưu tập là "chỉ đọc", (chính xác hơn, chỉ là thứ mà bạn đếm hoặc qua đó bạn lặp đi lặp lại) ngoại trừ một vài phương thức thực sự tạo ra các bộ sưu tập. Có lẽ thực tế tốt dù sao (đặc biệt cho đồng thời). Và các phương pháp tùy chọn mà rất nhiều người phàn nàn chưa bao giờ là vấn đề thực sự đối với tôi. Cũng không có những cơn ác mộng Generics với "siêu" và "kéo dài" từng là (nhiều) vấn đề. Chỉ tự hỏi nếu người khác sử dụng thực hành chung này?
user949300

Câu trả lời:


28

Câu hỏi thường gặp cung cấp câu trả lời. Nói tóm lại, họ đã thấy một sự bùng nổ kết hợp tiềm năng của các giao diện cần thiết với chế độ xem có thể sửa đổi, không thể thay đổi, chỉ xóa, chỉ thêm, độ dài cố định, không thay đổi (đối với luồng), v.v. cho từng bộ phương thức tùy chọn có thể thực hiện.


6
Tất cả điều này sẽ tránh được nếu Java có một consttừ khóa như C ++.
Etienne de Martel

@Etienne: Tốt hơn sẽ là siêu dữ liệu như Python. Sau đó, bạn có thể lập trình xây dựng số lượng giao diện kết hợp. Vấn đề với const là nó chỉ cung cấp cho bạn một hoặc hai chiều : vector<int>, const vector<int>, vector<const int>, const vector<const int>. Cho đến nay vẫn tốt, nhưng sau đó bạn thử triển khai biểu đồ và bạn muốn làm cho cấu trúc biểu đồ không đổi, nhưng các thuộc tính nút có thể sửa đổi, v.v.
Neil G

2
@Etienne, còn một lý do nữa để tìm hiểu "Tìm hiểu Scala" trong danh sách việc cần làm của tôi!
glenviewjeff

2
Điều tôi không hiểu là tại sao họ không thực hiện một canphương pháp sẽ kiểm tra nếu một hoạt động có thể thực hiện được? Nó sẽ giữ cho giao diện đơn giản và nhanh chóng.
Mehrdad

3
@Etienne de Martel Vô nghĩa. Làm thế nào điều đó sẽ giúp trong vụ nổ tổ hợp?
Tom Hawtin - tackline

10

Nghe có vẻ như tôi Interface Segregation Principlekhông được khám phá trước đó như bây giờ; cách thức thực hiện đó (tức là giao diện của bạn bao gồm tất cả các hoạt động có thể và bạn có các phương thức "suy biến" đưa ra ngoại lệ cho những thứ bạn không cần) đã phổ biến trước khi SOLID và ISP trở thành tiêu chuẩn thực tế cho mã chất lượng.


2
Tại sao một downvote? Ai đó không thích ISP?
Wayne Molina

Cũng đáng lưu ý rằng trong các khung hỗ trợ phương sai, có một lợi thế lớn để tách biệt các khía cạnh của giao diện có thể là biến đổi hoặc chống lại từ những điểm bất biến về cơ bản. Ngay cả khi không có sự hỗ trợ như vậy, trong các khung không sử dụng loại xóa sẽ có giá trị trong việc tách biệt các khía cạnh của giao diện độc lập với loại (ví dụ: cho phép một người có được Countbộ sưu tập mà không phải lo lắng về loại vật phẩm nào chứa), nhưng trong các khung công tác dựa trên kiểu xóa như Java không phải là vấn đề như vậy.
supercat

4

Mặc dù một số người có thể không ưa "các phương thức tùy chọn", nhưng trong nhiều trường hợp, họ có thể cung cấp ngữ nghĩa tốt hơn so với các giao diện được phân tách cao. Trong số những thứ khác, chúng cho phép các khả năng mà một đối tượng có thể đạt được các khả năng hoặc đặc điểm trong vòng đời của nó, hoặc một đối tượng (đặc biệt là một đối tượng bao bọc) có thể không biết khi nào nó được xây dựng những khả năng chính xác cần báo cáo.

Mặc dù tôi sẽ hầu như không gọi các lớp bộ sưu tập Java là các thiết kế tốt, tôi sẽ đề xuất rằng một khung bộ sưu tập tốt nên bao gồm một số lượng lớn các phương thức tùy chọn cùng với các cách hỏi một bộ sưu tập về các đặc điểm và khả năng của nó . Thiết kế như vậy sẽ cho phép một lớp trình bao bọc duy nhất được sử dụng với nhiều bộ sưu tập lớn mà không vô tình che khuất khả năng mà bộ sưu tập cơ bản có thể sở hữu. Nếu các phương thức không phải là tùy chọn, thì cần phải có một lớp trình bao bọc khác nhau cho mọi kết hợp các tính năng mà các bộ sưu tập có thể hỗ trợ hoặc nếu không, một số trình bao bọc không thể sử dụng được trong một số trường hợp.

Ví dụ: nếu một bộ sưu tập hỗ trợ viết một mục theo chỉ mục hoặc nối thêm các mục ở cuối, nhưng không hỗ trợ chèn các mục ở giữa, thì mã muốn đóng gói nó trong trình bao bọc sẽ ghi lại tất cả các hành động được thực hiện trên đó sẽ cần một phiên bản của trình bao bọc ghi nhật ký được cung cấp cho sự kết hợp chính xác của các khả năng được hỗ trợ hoặc nếu không có sẵn thì sẽ phải sử dụng trình bao bọc hỗ trợ thêm hoặc ghi theo chỉ mục nhưng không phải cả hai. Tuy nhiên, nếu giao diện bộ sưu tập hợp nhất cung cấp cả ba phương thức là "tùy chọn", nhưng sau đó bao gồm các phương thức để chỉ ra phương thức tùy chọn nào có thể sử dụng được, thì một lớp trình bao bọc duy nhất có thể xử lý các bộ sưu tập thực hiện bất kỳ kết hợp tính năng nào. Khi được hỏi những tính năng nào nó hỗ trợ, trình bao bọc có thể chỉ báo cáo bất cứ điều gì bộ sưu tập được đóng gói hỗ trợ.

Lưu ý rằng trong một số trường hợp, sự tồn tại của "các khả năng tùy chọn" có thể cho phép các bộ sưu tập tổng hợp thực hiện các chức năng nhất định theo cách hiệu quả hơn nhiều so với khả năng nếu các khả năng được xác định bởi sự tồn tại của việc triển khai. Ví dụ: giả sử một concatenatephương thức được sử dụng để tạo thành một bộ sưu tập tổng hợp từ hai phương thức khác, trong đó phương thức đầu tiên là ArrayList với 1.000.000 phần tử và phần cuối là bộ sưu tập hai mươi phần tử chỉ có thể được lặp lại từ đầu. Nếu bộ sưu tập tổng hợp được yêu cầu cho phần tử thứ 1.000.013 (chỉ số 1.000.012), nó có thể hỏi ArrayList có bao nhiêu mục (ví dụ 1.000.000), trừ đi chỉ số được yêu cầu (đạt 12), đọc và bỏ qua mười hai phần tử từ phần tử thứ hai bộ sưu tập, và sau đó trả về phần tử tiếp theo.

Trong tình huống như vậy, mặc dù bộ sưu tập tổng hợp sẽ không có cách trả lại một mục ngay lập tức theo chỉ mục, yêu cầu bộ sưu tập tổng hợp cho mục thứ 1.000.013 vẫn sẽ nhanh hơn nhiều so với việc đọc riêng lẻ 1.000.013 và bỏ qua tất cả trừ mục cuối cùng một.


@kevincline: Bạn có không đồng ý với từ ngữ được trích dẫn không? Tôi sẽ xem xét việc thiết kế các phương tiện để triển khai giao diện có thể mô tả các đặc điểm và khả năng thiết yếu của chúng là một trong những khía cạnh quan trọng nhất của thiết kế giao diện, mặc dù nó thường không được chú ý nhiều. Nếu các triển khai khác nhau của giao diện sẽ có những cách tối ưu khác nhau để hoàn thành một số tác vụ chung nhất định, thì nên có một phương tiện cho khách hàng quan tâm đến hiệu suất để chọn cách tiếp cận tốt nhất trong các tình huống có vấn đề.
supercat

1
xin lỗi, nhận xét không đầy đủ. Tôi đã định nói rằng "'cách hỏi về bộ sưu tập' ..." chuyển những gì nên là kiểm tra thời gian biên dịch sang thời gian chạy.
kevin cline

@kevincline: Trong trường hợp khách hàng cần có một khả năng nhất định và không thể sống thiếu nó, kiểm tra thời gian biên dịch có thể hữu ích. Mặt khác, có nhiều tình huống mà các bộ sưu tập có thể có những khả năng vượt ra ngoài những tình huống mà chúng có thể được biên dịch - đảm bảo thời gian có. Trong một số trường hợp, có thể có một giao diện dẫn xuất có hợp đồng xác định rằng tất cả các cài đặt hợp pháp của giao diện dẫn xuất phải hỗ trợ các phương thức cụ thể tùy chọn trong giao diện cơ sở, nhưng trong trường hợp mã sẽ có thể xử lý một cái gì đó dù có hay không nó có một số tính năng ...
supercat

... nhưng sẽ được hưởng lợi từ tính năng của nó tồn tại, tốt hơn là nên mã hỏi đối tượng xem nó có hỗ trợ tính năng đó không, hỏi hệ thống loại liệu loại đối tượng có hứa hỗ trợ tính năng đó không. Nếu một giao diện "cụ thể" cụ thể sẽ được sử dụng rộng rãi, người ta có thể bao gồm một AsXXXphương thức trong giao diện cơ sở sẽ trả về đối tượng mà nó được gọi nếu nó thực hiện giao diện đó, trả về một đối tượng trình bao hỗ trợ giao diện đó nếu có thể hoặc ném một ngoại lệ nếu không. Ví dụ: một ImmutableCollectiongiao diện có thể yêu cầu theo hợp đồng ...
supercat

2
Có một vấn đề với đề xuất của bạn: một mẫu "hỏi rồi làm" có vấn đề tương tranh nếu khả năng của một đối tượng có thể thay đổi trong suốt vòng đời của nó. (Nếu bạn sẽ phải mạo hiểm với một ngoại lệ nào đó, bạn cũng có thể không bận tâm hỏi ...)
jhominal

-1

Tôi sẽ gán nó cho các nhà phát triển ban đầu chỉ là không biết tốt hơn sau đó. Chúng ta đã đi một chặng đường dài trong thiết kế OO từ năm 1998 hoặc lâu hơn khi Java 2 và Bộ sưu tập được phát hành lần đầu tiên. Những gì có vẻ như thiết kế xấu rõ ràng bây giờ không quá rõ ràng trong những ngày đầu của OOP.

Nhưng nó có thể đã được thực hiện để ngăn chặn đúc thêm. Nếu đó là giao diện thứ hai, bạn phải sử dụng các thể hiện bộ sưu tập của mình để gọi các phương thức tùy chọn đó cũng là loại xấu. Vì bây giờ bạn sẽ bắt gặp UnsupportedOperationException ngay lập tức và sửa mã của bạn. Nhưng nếu có hai giao diện, bạn sẽ phải sử dụng instanceof và truyền khắp nơi. Có lẽ họ coi đó là một sự đánh đổi hợp lệ. Ngoài ra, trong thời gian 2 ngày đầu Java đã bị cau mày vì hiệu suất chậm, họ có thể đã cố gắng ngăn chặn việc sử dụng nó.

Tất nhiên đây là tất cả suy đoán hoang dã, tôi nghi ngờ chúng ta có thể trả lời điều này cho chắc chắn trừ khi một trong những bộ sưu tập ban đầu mà các kiến ​​trúc sư bấm chuông.


7
Đúc gì? Nếu một phương thức trả về cho bạn một Collectionchứ không phải a MutableCollection, thì đó là một dấu hiệu rõ ràng rằng nó không có nghĩa là phải sửa đổi. Tôi không biết tại sao mọi người sẽ cần phải đúc chúng. Có các giao diện riêng biệt có nghĩa là bạn sẽ gặp các loại lỗi đó trong thời gian biên dịch thay vì gặp ngoại lệ khi chạy. Càng sớm nhận được lỗi, càng tốt.
Etienne de Martel

1
Vì cách đó kém linh hoạt, một trong những lợi ích lớn nhất đối với thư viện bộ sưu tập là bạn có thể trả lại các giao diện cấp cao ở mọi nơi và không phải lo lắng về việc triển khai thực tế. Nếu bạn đang sử dụng hai giao diện, bây giờ bạn sẽ kết nối chặt chẽ hơn. Trong hầu hết các trường hợp, bạn chỉ đơn giản muốn trả về Danh sách chứ không phải Danh sách bất biến vì bạn thường muốn để lại danh sách đó cho lớp gọi để xác định.
Jberg

6
Nếu tôi cung cấp cho bạn một bộ sưu tập chỉ đọc, đó là vì tôi không muốn nó được sửa đổi. Ném một ngoại lệ và dựa vào tài liệu cảm thấy một địa ngục giống như một bản vá. Trong C ++, tôi chỉ cần trả về một constđối tượng, ngay lập tức cho người dùng biết rằng đối tượng không thể được sửa đổi.
Etienne de Martel

7
Năm 1998 không phải là "những ngày đầu của thiết kế OO".
quant_dev
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.