Đây là một vấn đề thiết kế ngôn ngữ khác có vẻ như "rõ ràng là một ý tưởng tốt" cho đến khi bạn bắt đầu đào và bạn nhận ra rằng đó thực sự là một ý tưởng tồi.
Thư này có rất nhiều về chủ đề (và cả các chủ đề khác nữa.) Có một số lực lượng thiết kế đã hội tụ để đưa chúng ta đến thiết kế hiện tại:
- Mong muốn giữ cho mô hình thừa kế đơn giản;
- Thực tế là một khi bạn nhìn qua các ví dụ rõ ràng (ví dụ: chuyển
AbstractList
sang giao diện), bạn nhận ra rằng việc kế thừa bằng / hashCode / toString được liên kết chặt chẽ với kế thừa và trạng thái duy nhất, và các giao diện được nhân rộng và không trạng thái;
- Rằng nó có khả năng mở ra cánh cửa cho một số hành vi đáng ngạc nhiên.
Bạn đã chạm vào mục tiêu "giữ cho nó đơn giản"; các quy tắc kế thừa và giải quyết xung đột được thiết kế rất đơn giản (các lớp thắng giao diện, giao diện dẫn xuất thắng trên siêu giao diện và bất kỳ xung đột nào khác được giải quyết bởi lớp triển khai.) Tất nhiên các quy tắc này có thể được điều chỉnh để tạo ngoại lệ, nhưng Tôi nghĩ bạn sẽ tìm thấy khi bạn bắt đầu kéo theo chuỗi đó, rằng độ phức tạp gia tăng không nhỏ như bạn nghĩ.
Tất nhiên, có một số mức độ lợi ích sẽ chứng minh sự phức tạp hơn, nhưng trong trường hợp này nó không có ở đó. Các phương thức mà chúng ta đang nói ở đây là bằng, hashCode và toString. Các phương thức này thực chất là về trạng thái đối tượng và đó là lớp sở hữu trạng thái chứ không phải giao diện, là người có vị trí tốt nhất để xác định ý nghĩa bình đẳng nào cho lớp đó (đặc biệt là hợp đồng cho sự bình đẳng khá mạnh; xem Hiệu quả Java cho một số hậu quả đáng ngạc nhiên); giao diện người viết chỉ là quá xa.
Thật dễ dàng để lấy ra AbstractList
ví dụ; Sẽ thật đáng yêu nếu chúng ta có thể thoát khỏi AbstractList
và đưa hành vi vào List
giao diện. Nhưng một khi bạn vượt ra ngoài ví dụ rõ ràng này, sẽ không có nhiều ví dụ hay khác được tìm thấy. Tại root, AbstractList
được thiết kế cho thừa kế duy nhất. Nhưng giao diện phải được thiết kế cho nhiều kế thừa.
Hơn nữa, hãy tưởng tượng bạn đang viết lớp này:
class Foo implements com.libraryA.Bar, com.libraryB.Moo {
// Implementation of Foo, that does NOT override equals
}
Người Foo
viết nhìn vào các siêu kiểu, thấy không có sự thực thi nào bằng và kết luận rằng để có được đẳng thức tham chiếu, tất cả những gì anh ta cần làm là thừa kế bằng Object
. Sau đó, vào tuần tới, người bảo trì thư viện cho Bar "một cách hữu ích" sẽ thêm một cài đặt mặc định equals
. Ôi trời! Bây giờ các ngữ nghĩa Foo
đã bị phá vỡ bởi một giao diện trong một miền bảo trì khác "một cách hữu ích" thêm một mặc định cho một phương thức phổ biến.
Mặc định được coi là mặc định. Thêm một mặc định vào một giao diện không có (bất kỳ nơi nào trong hệ thống phân cấp) sẽ không ảnh hưởng đến ngữ nghĩa của các lớp triển khai cụ thể. Nhưng nếu mặc định có thể "ghi đè" các phương thức Object, điều đó sẽ không đúng.
Vì vậy, mặc dù có vẻ như là một tính năng vô hại, nhưng thực tế nó lại rất có hại: nó làm tăng thêm sự phức tạp cho một chút biểu cảm gia tăng, và nó làm cho nó trở nên quá dễ dàng đối với những thay đổi có chủ đích, vô hại đối với các giao diện được biên dịch riêng biệt để làm suy yếu ngữ nghĩa dự định của các lớp thực hiện.