Tại sao nên tránh Java Kế thừa


40

Jame Gosling nói

Bạn nên tránh kế thừa thực hiện bất cứ khi nào có thể.

và thay vào đó, sử dụng kế thừa giao diện.

Nhưng tại sao? Làm thế nào chúng ta có thể tránh kế thừa cấu trúc của một đối tượng bằng cách sử dụng từ khóa "extends", đồng thời làm cho mã của chúng ta hướng đối tượng?

Ai đó có thể vui lòng đưa ra một ví dụ hướng đối tượng minh họa khái niệm này trong một kịch bản như "đặt mua sách trong hiệu sách không?"


4
bản sao của các lập trình
Steven A. Lowe

Vấn đề là bạn chỉ có thể mở rộng một lớp.

Câu trả lời:


38

Gosling đã không nói để tránh việc sử dụng từ khóa mở rộng ... ông chỉ nói không sử dụng tính kế thừa như một phương tiện để đạt được việc sử dụng lại mã.

Kế thừa không phải là xấu mỗi se và là một công cụ rất mạnh (cần thiết) để sử dụng trong việc tạo cấu trúc OO. Tuy nhiên, khi không được sử dụng đúng cách (đó là khi được sử dụng cho mục đích khác ngoài việc tạo cấu trúc Object), nó tạo ra mã được ghép rất chặt chẽ và rất khó bảo trì.

Bởi vì kế thừa nên được sử dụng để tạo các cấu trúc đa hình, nó có thể được thực hiện dễ dàng với các giao diện, do đó câu lệnh sử dụng các giao diện thay vì các lớp khi thiết kế Cấu trúc đối tượng của bạn. Nếu bạn thấy mình sử dụng extends như một phương tiện để tránh việc sao chép mã từ đối tượng này sang đối tượng khác thì có lẽ bạn đang sử dụng sai và sẽ được phục vụ tốt hơn với một lớp chuyên biệt để xử lý chức năng này và chia sẻ mã đó thông qua thành phần.

Đọc về Nguyên tắc thay thế Liskov và hiểu nó. Bất cứ khi nào bạn sắp mở rộng một lớp (hoặc một giao diện cho vấn đề đó) hãy tự hỏi mình xem bạn có vi phạm nguyên tắc không, nếu có thì rất có thể (ab) sử dụng sự kế thừa theo những cách không tự nhiên. Nó rất dễ làm và thường có vẻ như đúng nhưng nó sẽ làm cho mã của bạn khó bảo trì, kiểm tra và phát triển hơn.

--- Chỉnh sửa Câu trả lời này tạo ra một lập luận khá hay để trả lời câu hỏi bằng những thuật ngữ chung chung hơn.


3
(Thực hiện) kế thừa không cần thiết để tạo cấu trúc OO. Bạn có thể có một ngôn ngữ OO mà không cần nó.
Andres F.

Bạn có thể cung cấp một ví dụ? Tôi chưa bao giờ thấy OO mà không có sự kế thừa.
Newtopian

1
Vấn đề là không có định nghĩa được chấp nhận về OOP là gì. (Như một bên, thậm chí Alan Kay đã hối hận về cách giải thích chung về định nghĩa của mình, với sự nhấn mạnh hiện tại của nó vào "đối tượng" thay vì "thông điệp"). Đây là một nỗ lực tại một câu trả lời cho câu hỏi của bạn . Tôi đồng ý rằng không phổ biến khi thấy OOP mà không cần thừa kế; do đó lập luận của tôi chỉ đơn thuần là nó không cần thiết ;)
Andres F.

9

Trong những ngày đầu lập trình hướng đối tượng, kế thừa triển khai là cái búa vàng của việc sử dụng lại mã. Nếu có một phần chức năng trong một số lớp mà bạn cần, thì việc cần làm là kế thừa công khai từ lớp đó (đó là những ngày mà C ++ phổ biến hơn Java) và bạn có chức năng đó "miễn phí".

Ngoại trừ, nó không thực sự miễn phí. Kết quả là hệ thống phân cấp thừa kế cực kỳ phức tạp mà gần như không thể hiểu được, bao gồm cả những cây thừa kế hình kim cương rất khó xử lý. Đây là một phần lý do các nhà thiết kế của Java đã chọn không hỗ trợ nhiều kế thừa của các lớp.

Lời khuyên của Gosling (và các tuyên bố khác) giống như đó là liều thuốc mạnh cho một căn bệnh khá nghiêm trọng và có thể một số em bé đã bị ném ra ngoài với nước tắm. Không có gì sai khi sử dụng tính kế thừa để mô hình hóa mối quan hệ giữa các lớp thực sự is-a. Nhưng nếu bạn chỉ muốn sử dụng một phần chức năng từ một lớp khác, trước tiên bạn nên tìm hiểu các tùy chọn khác.


6

Kế thừa đã thu thập khá nhiều danh tiếng đáng sợ gần đây. Một lý do để tránh nó đi cùng với nguyên tắc thiết kế "sáng tác trên thừa kế", về cơ bản khuyến khích mọi người không sử dụng thừa kế khi đó không phải là mối quan hệ thực sự, chỉ để lưu một số cách viết mã. Kiểu kế thừa này có thể gây ra một số lỗi khó tìm và khó sửa. Lý do bạn của bạn có thể đề nghị sử dụng một giao diện thay vì là vì các giao diện cung cấp một giải pháp linh hoạt và dễ bảo trì hơn cho các api phân tán. Chúng thường cho phép thay đổi được thực hiện thành api mà không vi phạm mã của người dùng, trong đó việc phơi bày các lớp thường phá vỡ mã của người dùng. Ví dụ tốt nhất về điều này trong .Net là sự khác biệt về sử dụng giữa Danh sách và IList.


5

Bởi vì bạn chỉ có thể mở rộng một lớp, trong khi bạn có thể thực hiện nhiều giao diện.

Tôi đã từng nghe rằng kế thừa xác định bạn là ai, nhưng giao diện xác định vai trò bạn có thể đóng.

Các giao diện cũng cho phép sử dụng đa hình tốt hơn.

Đó có thể là một phần của lý do.


Tại sao các downvote?
Mahmoud Hossam

6
Câu trả lời đặt giỏ hàng trước ngựa. Java chỉ cho phép bạn mở rộng một lớp vì trình tự tiên đoán của Gosling (người thiết kế Java) đã khớp nối. Nó giống như nói rằng trẻ em nên đội mũ bảo hiểm trong khi đi xe đạp vì đó là luật. Điều đó đúng, nhưng cả luật pháp và lời khuyên đều xuất phát từ việc tránh chấn thương đầu.
JohnMcG

@JohnMcG Tôi không giải thích về sự lựa chọn thiết kế mà Gosling đưa ra, tôi chỉ đưa ra lời khuyên cho một lập trình viên, các lập trình viên thường không bận tâm đến thiết kế ngôn ngữ.
Mahmoud Hossam

1

Tôi cũng đã được dạy điều này và tôi thích giao diện hơn nếu có thể (tất nhiên tôi vẫn sử dụng tính kế thừa ở nơi có ý nghĩa).

Một điều tôi nghĩ nó làm là tách mã của bạn khỏi các triển khai cụ thể. Giả sử tôi có một lớp được gọi là ConsoleWriter và điều đó được truyền vào một phương thức để viết một cái gì đó ra và nó ghi nó vào bàn điều khiển. Bây giờ hãy nói rằng tôi muốn chuyển sang in ra một cửa sổ GUI. Bây giờ tôi phải sửa đổi phương thức hoặc viết một phương thức mới lấy GUIWriter làm tham số. Nếu tôi bắt đầu bằng cách xác định giao diện IWriter và có phương thức trong IWriter, tôi có thể bắt đầu với ConsoleWriter (sẽ triển khai giao diện IWriter) và sau đó viết một lớp mới gọi là GUIWriter (cũng thực hiện giao diện IWriter) sẽ chỉ phải chuyển lớp đang được thông qua.

Một điều nữa (đúng với C #, không chắc chắn về Java) là bạn chỉ có thể mở rộng 1 lớp nhưng thực hiện nhiều giao diện. Hãy nói rằng tôi có một lớp có tên là Giáo viên, MathTeacher và HistoryTeacher. Bây giờ MathTeacher và HistoryTeacher mở rộng từ Giáo viên, nhưng điều gì sẽ xảy ra nếu chúng ta muốn một lớp đại diện cho một người vừa là MathTeacher và HistoryTeacher. Nó có thể trở nên khá lộn xộn khi cố gắng kế thừa từ nhiều lớp khi bạn chỉ có thể thực hiện từng lớp một (có nhiều cách nhưng chúng không chính xác tối ưu). Với các giao diện, bạn có thể có 2 giao diện được gọi là IMathTeacher và IHistoryTeacher và sau đó có một lớp mở rộng từ Giáo viên và thực hiện 2 giao diện đó.

Một nhược điểm của việc sử dụng giao diện là đôi khi tôi thấy mọi người trùng lặp mã (vì bạn phải tạo triển khai cho mỗi lớp thực hiện giao diện) tuy nhiên có một giải pháp rõ ràng cho vấn đề này, ví dụ như sử dụng những thứ như đại biểu (không chắc chắn Java tương đương với cái gì).

Suy nghĩ lý do lớn nhất để sử dụng các giao diện trên các thừa kế là việc tách mã thực thi nhưng đừng nghĩ rằng thừa kế là xấu vì nó vẫn rất hữu ích.


2
"Bạn chỉ có thể mở rộng 1 lớp nhưng triển khai nhiều giao diện" Điều đó đúng với Java cũng như C #.
Thất vọngWithFormsDesigner

Một giải pháp khả thi cho vấn đề sao chép mã là tạo một lớp trừu tượng thực hiện giao diện và mở rộng điều đó. Sử dụng cẩn thận, mặc dù; chỉ có một vài trường hợp tôi thấy rằng nó có ý nghĩa.
Michael K

0

Nếu bạn thừa kế từ một lớp, bạn không chỉ phụ thuộc vào lớp đó mà còn phải tôn trọng bất kỳ hợp đồng ngầm nào đã được tạo bởi các khách hàng của lớp đó. Chú Bob cung cấp một ví dụ tuyệt vời về việc dễ dàng vi phạm Nguyên tắc thay thế Liskov bằng cách sử dụng Quảng trường cơ bản kéo dài tình huống khó xử Hình chữ nhật . Các giao diện, vì chúng không có triển khai nên cũng không có hợp đồng ẩn.


2
Một vấn đề nan giải: vấn đề hình tròn-elip vẫn sẽ xảy ra ngay cả khi chỉ sử dụng các giao diện thuần túy, nếu các đối tượng có thể thay đổi được.
rwong
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.