Thành phần ưu tiên hơn thừa kế - Có phải là lý do duy nhất để chống lại sự thay đổi chữ ký?


13

Trang này ủng hộ thành phần trên sự kế thừa với đối số sau đây (viết lại theo cách nói của tôi):

Một sự thay đổi trong chữ ký của một phương thức của siêu lớp (chưa bị ghi đè trong lớp con) gây ra những thay đổi bổ sung ở nhiều nơi khi chúng ta sử dụng Kế thừa. Tuy nhiên, khi chúng tôi sử dụng Thành phần, thay đổi bổ sung bắt buộc chỉ ở một nơi duy nhất: Lớp con.

Đó thực sự là lý do duy nhất để ủng hộ thành phần hơn thừa kế? Bởi vì nếu đó là trường hợp, vấn đề này có thể được giảm bớt một cách dễ dàng bằng cách thực thi một kiểu mã hóa ủng hộ việc ghi đè tất cả các phương thức của siêu lớp, ngay cả khi lớp con không thay đổi cách thực hiện (nghĩa là đặt phần tử giả lên lớp con). Am i thiếu cái gì ở đây?




2
Các trích dẫn không nói đó là lý do "duy nhất".
Tulains Córdova

3
Thành phần hầu như luôn luôn tốt hơn, bởi vì tính kế thừa thường làm tăng thêm độ phức tạp, khớp nối, đọc mã và giảm tính linh hoạt. Nhưng có những trường hợp ngoại lệ đáng chú ý, đôi khi một hoặc hai lớp cơ sở không bị tổn thương. Đó là lý do tại sao ' thích ' hơn là ' luôn luôn '.
Mark Rogers

Câu trả lời:


27

Tôi thích sự tương tự vì vậy đây là một: Bạn đã bao giờ thấy một trong những TV có trình phát VCR tích hợp chưa? Còn cái có VCR và đầu DVD thì sao? Hoặc một cái có Blu-ray, DVD và VCR. Ồ và bây giờ mọi người đang phát trực tuyến vì vậy chúng tôi cần thiết kế một TV mới ...

Rất có thể, nếu bạn sở hữu một TV, bạn không có một cái như trên. Có lẽ hầu hết những người thậm chí chưa từng tồn tại. Rất có thể bạn có một màn hình hoặc bộ có nhiều đầu vào để bạn không cần một bộ mới mỗi khi có một loại thiết bị đầu vào mới.

Kế thừa giống như TV với VCR tích hợp. Nếu bạn có 3 loại hành vi khác nhau mà bạn muốn sử dụng cùng nhau và mỗi loại có 2 tùy chọn để thực hiện, bạn cần 8 lớp khác nhau để thể hiện tất cả các hành vi đó. Khi bạn thêm nhiều tùy chọn, các con số sẽ nổ tung. Nếu bạn sử dụng thành phần thay thế, bạn tránh được vấn đề tổ hợp đó và thiết kế sẽ có xu hướng mở rộng hơn.


6
Có rất nhiều TV với đầu phát VCR và DVD được xây dựng vào cuối những năm 90 và đầu những năm 2000.
JAB

@JAB và nhiều TV (hầu hết?) Được bán ngày hôm nay hỗ trợ phát trực tuyến, cho vấn đề đó.
Casey

4
@JAB Và không ai trong số họ có thể bán đầu đĩa DVD của họ để giúp mua một đầu phát Bluray
Alexander - Tái lập Monica

1
@JAB Phải. Câu nói "Bạn đã bao giờ thấy một trong những TV có trình phát VCR tích hợp chưa?" ngụ ý họ tồn tại.
JimmyJames

4
@Casey Bởi vì rõ ràng, sẽ không bao giờ có một công nghệ khác sẽ thay thế công nghệ này, phải không? Tôi cá rằng bất kỳ TV nào trong số đó cũng hỗ trợ đầu vào từ các thiết bị bên ngoài chỉ trong trường hợp ai đó muốn sử dụng thứ khác mà không cần mua TV mới. Hay đúng hơn, ít người mua có giáo dục sẵn sàng mua TV thiếu đầu vào như vậy. Tiền đề của tôi không phải là những cách tiếp cận này không tồn tại và tôi cũng sẽ không tuyên bố thừa kế không tồn tại. Vấn đề là cách tiếp cận như vậy vốn đã kém linh hoạt hơn so với thành phần.
JimmyJames

4

Không có nó không phải là. Trong thành phần kinh nghiệm của tôi qua thừa kế là kết quả của suy nghĩ về phần mềm của bạn như một loạt các đối tượng với các hành vinói chuyện với nhau / các đối tượng cung cấp dịch vụ với nhau thay vì các lớp họcdữ liệu .

Bằng cách này, bạn có một số đối tượng cung cấp các dịch vụ. Bạn sử dụng chúng như các khối xây dựng (thực sự khá giống Lego) để kích hoạt các hành vi khác (ví dụ: UserRegistrationService sử dụng các dịch vụ được cung cấp bởi EmailerServiceUserRep repository ).

Khi xây dựng các hệ thống lớn hơn với cách tiếp cận này, tôi sử dụng sự kế thừa một cách tự nhiên trong rất ít trường hợp. Vào cuối ngày, thừa kế là một công cụ rất mạnh nên được sử dụng một cách thận trọng và chỉ khi thích hợp.


Tôi thấy tâm lý Java ở đây. OOP là về dữ liệu được đóng gói cùng với các chức năng hoạt động trên nó - những gì bạn mô tả được gọi là "mô-đun" tốt hơn. Bạn đúng khi tránh việc thừa kế là một ý tưởng tốt khi bạn tái sử dụng các đối tượng dưới dạng các mô-đun, nhưng tôi thấy phiền khi bạn dường như khuyên bạn nên sử dụng các đối tượng đó.
Brilliand

1
@Brilliand Nếu bạn chỉ biết bao nhiêu mã java được viết theo phong cách như bạn mô tả và mức độ kinh dị này thì ... Smalltalk đã giới thiệu thuật ngữ OOP và nó được thiết kế xung quanh các đối tượng nhận tin nhắn. : "Máy tính nên được xem như một khả năng nội tại của các đối tượng có thể được gọi một cách thống nhất bằng cách gửi tin nhắn." Không có đề cập đến dữ liệu và chức năng hoạt động trên nó. Xin lỗi, nhưng theo tôi thì bạn đang lầm tưởng. Nếu đó chỉ là về dữ liệu và chức năng, người ta có thể vui vẻ tiếp tục viết các cấu trúc.
marstato

Thật không may @Tsar, mặc dù Java hỗ trợ các phương thức tĩnh, văn hóa Java không
k_g

@Brilliand Ai quan tâm OOP là gì "về"? Điều quan trọng là làm thế nào bạn có thể sử dụng nó và kết quả của việc sử dụng nó theo những cách đó.
dùng253751

1
Sau khi thảo luận với @Tsar: Các lớp Java không cần phải được khởi tạo và thực tế, các lớp như UserRegistrationServiceEmailService thường không được khởi tạo - các lớp không có dữ liệu liên quan hoạt động tốt nhất như các lớp tĩnh (và như vậy, không liên quan đến bản gốc câu hỏi). Tôi sẽ xóa một số bình luận trước đây của tôi, nhưng những lời chỉ trích ban đầu của tôi về câu trả lời của marsato.
Brilliand

4

"Thích thành phần hơn thừa kế" chỉ là một heuristic tốt

Bạn nên xem xét bối cảnh, vì đây không phải là một quy tắc phổ quát. Đừng coi đó là việc không bao giờ sử dụng tính kế thừa khi bạn có thể sáng tác. Nếu đó là trường hợp, bạn sẽ sửa nó bằng cách cấm thừa kế.

Tôi hy vọng sẽ làm cho điểm này được xóa dọc theo bài này.

Tôi sẽ không cố gắng bảo vệ giá trị của sáng tác. Mà tôi coi là lạc đề. Thay vào đó, tôi sẽ nói về một số tình huống khi nhà phát triển có thể xem xét kế thừa sẽ tốt hơn khi sử dụng bố cục. Trên lưu ý đó, thừa kế có giá trị riêng của nó, mà tôi cũng xem xét ngoài chủ đề.


Ví dụ xe

Tôi đang viết về các nhà phát triển, những người cố gắng làm mọi thứ theo cách ngu ngốc, cho mục đích tường thuật

Chúng ta hãy đi cho một biến thể của các ví dụ cổ điển rằng một số các khóa học OOP sử dụng ... Chúng tôi có một Vehiclelớp, sau đó chúng tôi lấy được Car, Airplane, BalloonShip.

Lưu ý : Nếu bạn cần căn cứ vào ví dụ này, giả vờ đây là những loại đối tượng trong trò chơi video.

Sau đó CarAirplanecó thể có một số mã phổ biến, bởi vì cả hai đều có thể lăn trên bánh xe trên đất liền. Các nhà phát triển có thể xem xét việc tạo một lớp trung gian trong chuỗi thừa kế cho điều đó. Tuy nhiên, thực sự cũng có một số mã được chia sẻ giữa AirplaneBalloon. Họ có thể xem xét việc tạo một lớp trung gian khác trong chuỗi thừa kế cho điều đó.

Do đó, nhà phát triển sẽ tìm kiếm nhiều kế thừa. Tại thời điểm mà các nhà phát triển đang tìm kiếm nhiều kế thừa, thiết kế đã sai.

Tốt hơn là mô hình hóa hành vi này như các giao diện và thành phần, vì vậy chúng ta có thể sử dụng lại nó mà không cần phải chạy vào kế thừa nhiều lớp. Nếu các nhà phát triển, ví dụ, tạo FlyingVehiculelớp. Họ sẽ nói rằng đó Airplanelà một FlyingVehicule(kế thừa lớp), nhưng thay vào đó chúng ta có thể nói rằng nó Airplanecó một Flyingthành phần (thành phần) và Airplanelà một IFlyingVehicule(kế thừa giao diện).

Sử dụng các giao diện, nếu cần thiết, chúng ta có thể có nhiều kế thừa (của các giao diện). Ngoài ra, bạn không khớp với một triển khai cụ thể. Tăng khả năng sử dụng lại và kiểm tra mã của bạn.

Hãy nhớ rằng sự kế thừa là một công cụ cho đa hình. Ngoài ra, đa hình là một công cụ để tái sử dụng. Nếu bạn có thể tăng khả năng sử dụng lại mã của mình bằng cách sử dụng thành phần, thì hãy làm như vậy. Nếu bạn không chắc chắn bất cứ điều gì hoặc không thành phần cung cấp khả năng tái sử dụng tốt hơn, "Ưu tiên thành phần hơn thừa kế" là một heuristic tốt.

Tất cả những điều đó mà không đề cập đến Amphibious.

Trong thực tế, chúng ta có thể không cần những thứ đi lên khỏi mặt đất. Stephen Hurn có một ví dụ hùng hồn hơn trong các bài viết của mình về Ưu đãi Thành phần qua quyền thừa kế Phần 1phần 2 .


Thay thế và đóng gói

Nên Akế thừa hay sáng tác B?

Nếu ALà một chuyên môn về Bđiều đó phải đáp ứng nguyên tắc thay thế Liskov , sự kế thừa là khả thi, thậm chí là mong muốn. Nếu có những tình huống Akhông phải là sự thay thế hợp lệ Bthì chúng ta không nên sử dụng tính kế thừa.

Chúng ta có thể quan tâm đến sáng tác như một hình thức lập trình phòng thủ, để bảo vệ lớp dẫn xuất . Đặc biệt, một khi bạn bắt đầu sử dụng Bcho các mục đích khác nhau, có thể có áp lực phải thay đổi hoặc mở rộng nó để phù hợp hơn cho các mục đích đó. Nếu có rủi ro Bcó thể làm lộ ra các phương pháp có thể dẫn đến trạng thái không hợp lệ, Achúng ta nên sử dụng thành phần thay vì kế thừa. Ngay cả khi chúng tôi là tác giả của cả hai BA, đó là một điều ít phải lo lắng hơn, do đó thành phần làm giảm khả năng tái sử dụng B.

Chúng tôi thậm chí có thể lập luận rằng nếu có những tính năng trong Bđó Akhông cần (và chúng tôi không biết nếu những tính năng có thể dẫn đến một trạng thái không hợp lệ cho A, hoặc là trong việc thực hiện hiện tại hay trong tương lai), nó là một ý tưởng tốt để thành phần sử dụng thay vì thừa kế.

Thành phần cũng có những lợi thế của việc cho phép thực hiện chuyển đổi và giảm bớt chế độ chế nhạo.

Lưu ý : có những tình huống chúng tôi muốn sử dụng thành phần mặc dù sự thay thế là hợp lệ. Chúng tôi lưu trữ tính thay thế đó bằng cách sử dụng các giao diện hoặc các lớp trừu tượng (cái nào sẽ sử dụng khi là chủ đề khác) và sau đó sử dụng thành phần với nội dung phụ thuộc của việc triển khai thực tế.

Cuối cùng, tất nhiên, có lập luận rằng chúng ta nên sử dụng thành phần để bảo vệ lớp cha vì sự kế thừa phá vỡ sự đóng gói của lớp cha:

Kế thừa cho thấy một lớp con với các chi tiết về việc thực hiện của cha mẹ nó, người ta thường nói rằng 'thừa kế phá vỡ đóng gói'

- Các mẫu thiết kế: Các yếu tố của phần mềm hướng đối tượng có thể tái sử dụng, Gang of Four

Vâng, đó là một lớp cha mẹ được thiết kế kém. Đó là lý do tại sao bạn nên:

Thiết kế để thừa kế, hoặc cấm nó.

- Java hiệu quả, Josh Bloch


Vấn đề Yo-Yo

Một trường hợp khác mà thành phần giúp là vấn đề Yo-Yo . Đây là một trích dẫn từ Wikipedia:

Trong phát triển phần mềm, vấn đề yo-yo là một kiểu chống xảy ra khi lập trình viên phải đọc và hiểu một chương trình có biểu đồ kế thừa quá dài và phức tạp đến nỗi lập trình viên phải tiếp tục lật giữa nhiều định nghĩa lớp khác nhau để tuân theo dòng điều khiển của chương trình.

Bạn có thể giải quyết, ví dụ: Lớp của bạn Csẽ không kế thừa từ lớp B. Thay vào đó, lớp của bạn Csẽ có một thành viên của loại A, có thể có hoặc không (hoặc có) một đối tượng của loại B. Bằng cách này, bạn sẽ không được lập trình dựa trên chi tiết triển khai B, mà trái với hợp đồng mà giao diện (của) Acung cấp.


Ví dụ truy cập

Nhiều khung ủng hộ sự kế thừa so với thành phần (điều này trái ngược với những gì chúng ta đã tranh luận). Nhà phát triển có thể làm điều này bởi vì họ đặt rất nhiều công việc vào lớp cơ sở của họ rằng việc triển khai nó với thành phần sẽ làm tăng kích thước của mã máy khách. Đôi khi điều này là do những hạn chế của ngôn ngữ.

Ví dụ, khung công tác ORM của PHP có thể tạo một lớp cơ sở sử dụng các phương thức ma thuật để cho phép viết mã như thể đối tượng có các thuộc tính thực. Thay vào đó, mã được xử lý bởi các phương thức ma thuật sẽ được chuyển đến cơ sở dữ liệu, truy vấn cho trường được yêu cầu cụ thể (có thể lưu vào bộ đệm cho yêu cầu trong tương lai) và trả lại. Làm điều đó với thành phần sẽ yêu cầu khách hàng tạo các thuộc tính cho từng trường hoặc viết một số phiên bản của mã phương thức ma thuật.

Phụ lục : Có một số cách khác người ta có thể cho phép mở rộng các đối tượng ORM. Vì vậy, tôi không nghĩ rằng thừa kế là cần thiết trong trường hợp này. Nó rẻ hơn.

Đối với một ví dụ khác, công cụ trò chơi video có thể tạo một lớp cơ sở sử dụng mã gốc tùy thuộc vào nền tảng đích để thực hiện kết xuất 3D và xử lý sự kiện. Mã này là phức tạp và nền tảng cụ thể. Nó sẽ là tốn kém và dễ bị lỗi cho người sử dụng nhà phát triển của công cụ để xử lý mã này, trên thực tế, đó là một phần lý do của việc sử dụng công cụ.

Hơn nữa, không có phần kết xuất 3D, đây là cách hoạt động của nhiều khung công cụ. Điều này giúp bạn không phải lo lắng về việc xử lý các thông báo hệ điều hành trên thực tế, trong nhiều ngôn ngữ, bạn không thể viết mã như vậy mà không có một số hình thức tạm thời riêng. Hơn nữa, nếu bạn làm điều đó, nó sẽ thắt chặt tính di động của bạn. Thay vào đó, với sự kế thừa, miễn là nhà phát triển không phá vỡ tính tương thích (quá nhiều); bạn sẽ có thể dễ dàng chuyển mã của mình sang bất kỳ nền tảng mới nào mà họ hỗ trợ trong tương lai.

Ngoài ra, hãy xem xét rằng nhiều lần chúng tôi chỉ muốn ghi đè một vài phương thức và để mọi thứ khác với các cài đặt mặc định. Nếu chúng ta đang sử dụng thành phần, chúng ta sẽ phải tạo ra tất cả các phương thức đó, ngay cả khi chỉ ủy thác cho đối tượng được bao bọc.

Theo lập luận này, có một điểm mà thành phần có thể tệ nhất đối với khả năng bảo trì hơn là kế thừa (khi lớp cơ sở quá phức tạp). Tuy nhiên, hãy nhớ rằng khả năng duy trì của thừa kế có thể tồi tệ hơn so với thành phần (khi cây thừa kế quá phức tạp), đó là những gì tôi đề cập đến trong vấn đề yo-yo.

Trong các ví dụ được trình bày, các nhà phát triển hiếm khi có ý định sử dụng lại mã được tạo thông qua kế thừa trong các dự án khác. Điều đó làm giảm khả năng tái sử dụng giảm dần của việc sử dụng thừa kế thay vì thành phần. Ngoài ra, bằng cách sử dụng tính kế thừa, các nhà phát triển khung có thể cung cấp rất nhiều mã dễ sử dụng và dễ khám phá.


Suy nghĩ cuối cùng

Như bạn có thể thấy, sáng tác có một số lợi thế so với thừa kế trong một số tình huống, không phải lúc nào cũng vậy. Điều quan trọng là phải xem xét bối cảnh và các yếu tố khác nhau có liên quan (như khả năng sử dụng lại, khả năng bảo trì, khả năng kiểm tra, vv.) Để đưa ra quyết định. Về điểm thứ nhất: "thích thành phần trên thừa kế" là một chỉ heuristic tốt.

Bạn cũng có thể có thông báo rằng nhiều tình huống tôi mô tả có thể được giải quyết bằng Traits hoặc Mixins ở một mức độ nào đó. Đáng buồn thay, đây không phải là những tính năng phổ biến trong danh sách các ngôn ngữ tuyệt vời và chúng thường đi kèm với một số chi phí hiệu suất. Rất may, Phương pháp mở rộng anh em họ phổ biến của họ và Triển khai mặc định giảm thiểu một số tình huống.

Tôi có một bài đăng gần đây nơi tôi nói về một số lợi thế của giao diện tại sao chúng tôi yêu cầu giao diện giữa truy cập UI, Business và Data trong C # . Nó giúp tách rời và giảm bớt khả năng sử dụng lại và kiểm tra, bạn có thể quan tâm.


Uh ... .Net framework sử dụng một số loại luồng được kế thừa để thực hiện công việc liên quan đến luồng đó. Nó hoạt động rất tốt và nó rất dễ sử dụng và mở rộng. Ví dụ của bạn không phải là một ví dụ rất hay ...
T. Sar - Phục hồi lại

1
@TSar "nó rất dễ sử dụng và mở rộng" Bạn đã bao giờ thử phát trực tiếp luồng ra Debug chưa? Đó là kinh nghiệm duy nhất của tôi khi cố gắng mở rộng luồng của họ. Nó không dễ dàng Đó là một cơn ác mộng kết thúc trong tôi ghi đè lên mọi phương thức trong lớp một cách rõ ràng. Vô cùng lặp đi lặp lại. Và nếu bạn nhìn vào mã nguồn .NET, ghi đè mọi thứ rõ ràng là cách họ làm điều đó. Vì vậy, tôi không tin rằng nó dễ dàng mở rộng.
jpmc26

Đọc và viết một cấu trúc từ một luồng được thực hiện tốt hơn với các hàm miễn phí hoặc đa phương thức.
dùng253751

@ jpmc26 Tôi xin lỗi, trải nghiệm của bạn không được tốt lắm. Tuy nhiên, tôi không có vấn đề gì khi sử dụng hoặc triển khai các công cụ liên quan đến luồng cho những thứ khác nhau.
T. Sar - Phục hồi Monica

3

Thông thường, ý tưởng lái xe của Thành phần thừa kế là cung cấp tính linh hoạt thiết kế lớn hơn và không làm giảm số lượng mã bạn cần thay đổi để tuyên truyền một thay đổi (mà tôi thấy có vấn đề).

Ví dụ trên wikipedia đưa ra một ví dụ tốt hơn về sức mạnh của phương pháp của mình:

Xác định giao diện cơ sở cho từng "tác phẩm thành phần", bạn có thể dễ dàng tạo ra "đối tượng sáng tác" khác nhau với nhiều loại có thể khó tiếp cận chỉ với sự kế thừa.


Vì vậy, này phần đưa ra một ví dụ phần, phải không? Tôi đang hỏi bởi vì nó không rõ ràng trong bài viết.
Utku

1
Đúng, "Thành phần thừa kế" không có nghĩa là bạn không có giao diện, nếu bạn nhìn vào đối tượng Người chơi, bạn có thể thấy điều đó phù hợp độc đáo trong một chữ tượng hình, nhưng ở mã (xin lỗi tàn bạo) không đến từ thừa kế, nhưng từ thành phần với một số lớp thực hiện công việc
lbenini

2

Dòng: "Thích thành phần hơn thừa kế" không có gì ngoài một cú huých đúng hướng. Nó sẽ không cứu bạn khỏi sự ngu ngốc của chính bạn và thực sự mang đến cho bạn cơ hội để khiến mọi thứ tồi tệ hơn nhiều. Đó là bởi vì có rất nhiều sức mạnh ở đây.

Lý do chính điều này thậm chí cần phải được nói là bởi vì các lập trình viên lười biếng. Vì vậy, lười biếng họ sẽ thực hiện bất kỳ giải pháp có nghĩa là gõ bàn phím ít hơn. Đó là điểm bán chính của thừa kế. Tôi gõ ít hơn hôm nay và một số người khác bị lừa vào ngày mai. Vì vậy, những gì tôi muốn về nhà.

Ngày hôm sau ông chủ khăng khăng tôi dùng sáng tác. Không giải thích tại sao nhưng cứ khăng khăng như vậy. Vì vậy, tôi lấy một ví dụ về những gì tôi được thừa hưởng từ đó, để lộ một giao diện giống hệt với những gì tôi được thừa hưởng và thực hiện giao diện đó bằng cách ủy thác tất cả công việc cho thứ mà tôi đang kế thừa. Đó là tất cả các kiểu gõ não có thể được thực hiện với một công cụ tái cấu trúc và dường như vô nghĩa đối với tôi nhưng ông chủ muốn nó nên tôi làm điều đó.

Ngày hôm sau tôi hỏi nếu đây là những gì đã muốn. Bạn nghĩ ông chủ nói gì?

Trừ khi có một số nhu cầu để có thể linh hoạt (trong thời gian chạy) thay đổi những gì được thừa hưởng từ (xem mẫu trạng thái), đây là một sự lãng phí rất lớn thời gian. Làm thế nào ông chủ có thể nói điều đó và vẫn có lợi cho sáng tác hơn thừa kế?

Ngoài ra, việc này không làm gì để ngăn ngừa vỡ do thay đổi chữ ký phương thức, điều này hoàn toàn bỏ lỡ lợi thế lớn nhất của thành phần. Không giống như thừa kế, bạn có thể tự do tạo một giao diện khác . Một cách phù hợp hơn với cách nó sẽ được sử dụng ở cấp độ này. Bạn biết đấy, sự trừu tượng!

Có một số lợi thế nhỏ để tự động thay thế thừa kế bằng thành phần và ủy quyền (và một số nhược điểm) nhưng việc giữ cho bộ não của bạn bị tắt trong quá trình này là một cơ hội lớn.

Cảm ứng rất mạnh mẽ. Sử dụng nó một cách rộng rãi.


0

Đây là một lý do khác: trong nhiều ngôn ngữ OO (tôi đang nghĩ về những ngôn ngữ như C ++, C # hoặc Java ở đây), không có cách nào để thay đổi lớp của một đối tượng một khi bạn đã tạo nó.

Giả sử chúng ta đang tạo một cơ sở dữ liệu nhân viên. Chúng tôi có một lớp Nhân viên cơ sở trừu tượng và bắt đầu rút ra các lớp mới cho các vai trò cụ thể - Kỹ sư, Bảo vệ An ninh, Tài xế xe tải, v.v. Mỗi lớp có hành vi chuyên biệt cho vai trò đó. Mọi thứ đều tốt.

Rồi một ngày, một Kỹ sư được thăng chức Quản lý Kỹ thuật. Bạn không thể phân loại lại một Kỹ sư hiện có. Thay vào đó, bạn phải viết một hàm tạo Trình quản lý kỹ thuật, sao chép tất cả dữ liệu từ Kỹ sư, xóa Kỹ sư khỏi cơ sở dữ liệu, sau đó thêm Trình quản lý kỹ thuật mới. Và bạn phải viết một hàm như vậy cho mỗi thay đổi vai trò có thể.

Tệ hơn nữa, giả sử Tài xế xe tải nghỉ ốm dài hạn và Nhân viên bảo vệ đề nghị làm Lái xe tải bán thời gian để điền vào. Bây giờ bạn phải phát minh ra một lớp Bảo vệ và Trình điều khiển xe tải mới chỉ dành cho họ.

Nó làm cho cuộc sống dễ dàng hơn nhiều để tạo ra một lớp Nhân viên cụ thể và coi nó như một thùng chứa mà bạn có thể thêm các thuộc tính, chẳng hạn như vai trò công việc.


Hoặc bạn tạo một lớp Nhân viên với một con trỏ tới Danh sách các Vai trò và mỗi công việc thực tế sẽ mở rộng Vai trò. Ví dụ của bạn có thể được thực hiện để sử dụng sự kế thừa một cách rất tốt và hợp lý mà không cần quá nhiều công việc.
T. Sar - Tái lập Monica

0

Kế thừa cung cấp hai điều:

1) Tái sử dụng mã: Có thể được thực hiện dễ dàng thông qua thành phần. Và trên thực tế, đạt được tốt hơn thông qua thành phần vì nó bảo quản tốt hơn việc đóng gói.

2) Đa hình: Có thể được thực hiện thông qua "giao diện" (các lớp trong đó tất cả các phương thức là thuần ảo).

Một lập luận mạnh mẽ cho việc sử dụng kế thừa phi giao diện, là khi số lượng phương thức cần thiết là lớn và lớp con của bạn chỉ muốn thay đổi một tập hợp con nhỏ của chúng. Tuy nhiên, nói chung, giao diện của bạn nên có dấu chân rất nhỏ nếu bạn đăng ký theo nguyên tắc "một trách nhiệm" (bạn nên), vì vậy những trường hợp này rất hiếm.

Có kiểu mã hóa yêu cầu ghi đè tất cả các phương thức của lớp cha mẹ không tránh được việc viết một số lượng lớn các phương thức chuyển qua, vậy tại sao không sử dụng lại mã thông qua thành phần và nhận được lợi ích bổ sung của việc đóng gói? Thêm vào đó, nếu siêu lớp được mở rộng bằng cách thêm một phương thức khác, kiểu mã hóa sẽ yêu cầu thêm phương thức đó vào tất cả các lớp con (và rất có thể chúng ta đã vi phạm "phân biệt giao diện" ). Vấn đề này phát triển nhanh chóng khi bạn bắt đầu có nhiều cấp độ trong thừa kế.

Cuối cùng, trong nhiều ngôn ngữ, các đối tượng dựa trên "thành phần" thử nghiệm đơn vị dễ dàng hơn nhiều so với các đối tượng dựa trên thử nghiệm "kế thừa" đơn vị bằng cách thay thế đối tượng thành viên bằng đối tượng giả / thử nghiệm / giả.


0

Đó thực sự là lý do duy nhất để ủng hộ thành phần hơn thừa kế?

Không.

Có nhiều lý do để ủng hộ thành phần hơn thừa kế. Không có một câu trả lời đúng. Nhưng tôi sẽ đưa ra một lý do khác để ưu tiên thành phần hơn sự kế thừa vào hỗn hợp:

Thành phần ưu tiên hơn tính kế thừa giúp cải thiện khả năng đọc mã của bạn , điều này rất quan trọng khi làm việc trên một dự án lớn có nhiều người.

Đây là cách tôi nghĩ về nó: khi tôi đọc mã của người khác, nếu tôi thấy rằng họ đã mở rộng một lớp, tôi sẽ hỏi hành vi nào trong siêu lớp họ đang thay đổi?

Và sau đó tôi sẽ duyệt mã của họ, tìm kiếm một hàm overriden. Nếu tôi không thấy bất kỳ, thì tôi phân loại đó là lạm dụng quyền thừa kế. Họ nên sử dụng thành phần thay thế, nếu chỉ để cứu tôi (và mọi người khác trong nhóm) sau 5 phút đọc qua mã của họ!

Đây là một ví dụ cụ thể: trong Java JFramelớp đại diện cho một cửa sổ. Để tạo một cửa sổ, bạn tạo một thể hiện JFramevà sau đó gọi các hàm trên thể hiện đó để thêm nội dung vào đó và hiển thị nó.

Nhiều hướng dẫn (thậm chí là chính thức) khuyên bạn JFramenên mở rộng để bạn có thể coi các thể hiện của lớp mình là các thể hiện của JFrame. Nhưng imho (và ý kiến ​​của nhiều người khác) cho rằng đây là một thói quen khá xấu để mắc phải! Thay vào đó, bạn chỉ nên tạo một thể hiện JFramevà gọi các hàm trên thể hiện đó. Hoàn toàn không cần phải gia hạn JFrametrong trường hợp này, vì bạn không thay đổi bất kỳ hành vi mặc định nào của lớp cha.

Mặt khác, một cách để thực hiện vẽ tùy chỉnh là mở rộng JPanellớp và ghi đè paintComponent()chức năng. Đây là một sử dụng tốt của thừa kế. Nếu tôi đang xem qua mã của bạn và tôi thấy rằng bạn đã mở rộng JPanelvà ghi đè paintComponent()hàm, tôi sẽ biết chính xác hành vi bạn đang thay đổi.

Nếu chỉ là bạn tự viết mã, thì có thể sử dụng tính kế thừa cũng dễ như sử dụng bố cục. Nhưng giả vờ bạn đang ở trong môi trường nhóm và những người khác sẽ đọc mã của bạn. Thành phần ưa thích hơn kế thừa làm cho mã của bạn dễ đọc hơn.


Thêm toàn bộ lớp nhà máy với một phương thức duy nhất trả về một thể hiện của một đối tượng để bạn có thể sử dụng thành phần thay vì kế thừa không thực sự làm cho mã của bạn dễ đọc hơn ... (Vâng, bạn cần điều này nếu bạn cần một phiên bản mới mỗi lần một phương thức cụ thể được gọi là.) Điều đó không có nghĩa là thừa kế là tốt, nhưng thành phần không phải lúc nào cũng cải thiện khả năng đọc.
jpmc26

1
@ jpmc26 ... tại sao bạn cần một lớp học chỉ để tạo một thể hiện mới của một lớp? Và làm thế nào để kế thừa cải thiện khả năng đọc của những gì bạn đang mô tả?
Kevin Workman

Tôi đã không rõ ràng cung cấp cho bạn một lý do cho một nhà máy như vậy trong các bình luận ở trên? Nó dễ đọc hơn vì nó dẫn đến ít mã hơn để đọc . Bạn có thể thực hiện hành vi tương tự với một phương thức trừu tượng duy nhất trong lớp cơ sở và một vài lớp con. (Dù sao bạn cũng sẽ có một cách triển khai khác nhau cho lớp tổng hợp của mình cho mỗi lớp.) Nếu các chi tiết xây dựng đối tượng được liên kết chặt chẽ với lớp cơ sở, nó sẽ không được sử dụng nhiều ở bất kỳ nơi nào khác trong mã của bạn, giảm thêm những lợi ích của việc tạo ra một lớp riêng biệt. Sau đó, nó giữ các phần liên quan chặt chẽ của mã gần nhau.
jpmc26

Như tôi đã nói, điều này không nhất thiết có nghĩa là thừa kế là tốt, nhưng nó minh họa cách thức sáng tác không cải thiện khả năng đọc trong một số tình huống.
jpmc26

1
Không thấy một ví dụ cụ thể, thật khó để bình luận. Nhưng những gì bạn đã mô tả nghe có vẻ như là một trường hợp quá kỹ thuật đối với tôi. Cần một ví dụ của một lớp học? Các newtừ khóa cung cấp cho bạn một. Dù sao cũng cảm ơn vì cuộc thảo luận.
Kevin Workman
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.