Khái niệm về thành phần ưu tiên của người Viking trên sự kế thừa là gì?


143

Trong vài tháng qua, câu thần chú "thành phần ủng hộ thừa kế" dường như đã xuất hiện từ hư không và trở thành một loại meme trong cộng đồng lập trình. Và mỗi khi tôi nhìn thấy nó, tôi có một chút bí ẩn. Nó giống như ai đó đã nói "ủng hộ cuộc tập trận trên búa." Theo kinh nghiệm của tôi, thành phần và tính kế thừa là hai công cụ khác nhau với các trường hợp sử dụng khác nhau và đối xử với chúng như thể chúng có thể hoán đổi cho nhau và một công cụ vốn đã vượt trội so với công cụ kia không có ý nghĩa gì.

Ngoài ra, tôi không bao giờ thấy một lời giải thích thực sự về lý do tại sao thừa kế là xấu và thành phần là tốt, điều đó chỉ khiến tôi nghi ngờ hơn. Có phải nó chỉ được chấp nhận trên đức tin? Sự thay thế và đa hình của Liskov có những lợi ích nổi tiếng, rõ ràng và IMO bao gồm toàn bộ quan điểm sử dụng lập trình hướng đối tượng và không ai giải thích được tại sao chúng nên bị loại bỏ theo hướng có lợi cho sáng tác.

Có ai biết khái niệm này đến từ đâu không, và lý do đằng sau nó là gì?


45
Như những người khác đã chỉ ra, nó đã xuất hiện từ lâu - tôi ngạc nhiên khi bạn vừa nghe về nó. Nó trực quan cho bất cứ ai đã và đang xây dựng các hệ thống lớn bằng các ngôn ngữ như Java trong bất kỳ khoảng thời gian nào. Đó là cốt lõi của bất kỳ cuộc phỏng vấn nào tôi từng đưa ra và khi một ứng viên bắt đầu nói về sự kế thừa, tôi bắt đầu nghi ngờ mức độ kỹ năng và lượng kinh nghiệm của họ. Đây là một giới thiệu tốt về lý do tại sao thừa kế là một giải pháp dễ vỡ (có nhiều giải pháp khác): artima.com/lejava/articles/designprinciples4.html
Janx

6
@Janx: Có lẽ vậy. Tôi không xây dựng các hệ thống lớn bằng các ngôn ngữ như Java; Tôi xây dựng chúng ở Delphi, và không có sự thay thế và đa hình của Liskov, chúng tôi sẽ không bao giờ hoàn thành được bất cứ điều gì. Mô hình đối tượng của nó khác nhau theo một số cách nhất định so với Java hoặc C ++ và rất nhiều vấn đề mà câu châm ngôn này dường như có nghĩa là để giải quyết không thực sự tồn tại, hoặc ít có vấn đề hơn, trong Delphi. Quan điểm khác nhau từ các quan điểm khác nhau, tôi đoán.
Mason Wheeler

14
Tôi đã dành vài năm cho một nhóm xây dựng các hệ thống tương đối lớn ở Delphi và những cây thừa kế cao chắc chắn cắn nát đội của chúng tôi và gây cho chúng tôi nỗi đau đáng kể. Tôi nghi ngờ rằng sự chú ý của bạn đến các nguyên tắc RẮN đang giúp bạn tránh các khu vực có vấn đề, chứ không phải việc bạn sử dụng Delphi.
Bevan

8
Vài tháng trước?!?
Jas

6
IMHO khái niệm đó chưa bao giờ được điều chỉnh hoàn toàn cho nhiều ngôn ngữ hỗ trợ cả kế thừa giao diện (nghĩa là phân nhóm với giao diện thuần túy) và kế thừa triển khai. Quá nhiều người theo câu thần chú này và không sử dụng đủ giao diện.
Uri

Câu trả lời:


136

Mặc dù tôi nghĩ rằng tôi đã nghe các cuộc thảo luận về thành phần so với kế thừa từ lâu trước GoF, tôi không thể đặt ngón tay của mình vào một nguồn cụ thể. Dù sao cũng có thể là Booch.

<rant>

Ah nhưng giống như nhiều câu thần chú, cái này đã bị thoái hóa theo các dòng điển hình:

  1. nó được giới thiệu với một lời giải thích và lập luận chi tiết bởi một nguồn được kính trọng, người đồng ý với cụm từ bắt như một lời nhắc nhở về cuộc thảo luận phức tạp ban đầu
  2. nó được chia sẻ với một cái nháy mắt của một phần của câu lạc bộ bởi một vài người hiểu biết trong một thời gian, thường là khi bình luận về những sai lầm của n00b
  3. chẳng mấy chốc, nó được lặp đi lặp lại một cách vô thức bởi hàng ngàn trên hàng ngàn người không bao giờ đọc lời giải thích, nhưng thích sử dụng nó như một cái cớ để không suy nghĩ , và như một cách rẻ tiền và dễ dàng để cảm thấy vượt trội so với những người khác
  4. cuối cùng, không có sự gỡ rối hợp lý nào có thể dẫn đến thủy triều "meme" - và mô hình suy thoái thành tôn giáo và giáo điều.

Các meme, ban đầu dự định dẫn n00bs đến giác ngộ, bây giờ được sử dụng như một câu lạc bộ để làm cho họ bất tỉnh.

Thành phần và kế thừa là những thứ rất khác nhau, và không nên nhầm lẫn với nhau. Mặc dù sự thật là thành phần có thể được sử dụng để mô phỏng thừa kế với rất nhiều công việc làm thêm , nhưng điều này không làm cho thừa kế trở thành công dân hạng hai, cũng không làm cho tác phẩm trở thành con trai yêu thích. Việc nhiều n00bs cố gắng sử dụng tính kế thừa như một phím tắt không làm mất hiệu lực cơ chế và hầu như tất cả các n00bs đều học được từ sai lầm và do đó cải thiện.

Hãy suy nghĩ về thiết kế của bạn, và ngừng phun ra các khẩu hiệu.

</ rant>


16
Booch lập luận rằng kế thừa thực hiện giới thiệu sự kết hợp cao giữa các lớp. Mặt khác, ông coi sự kế thừa là khái niệm chính giúp phân biệt OOP với lập trình thủ tục.
Nemanja Trifunovic

13
Nên có một từ cho loại điều này. Hồi sinh sớm , có thể?
Jörg W Mittag

8
@ Jörg: Bạn có biết điều gì sẽ xảy ra với một thuật ngữ như Sự hồi sinh sớm? Chính xác những gì được giải thích ở trên. :) (Btw. Khi nào thì sự hồi sinh không bao giờ sớm?)
Bjarke Freund-Hansen

6
@Nemanja: Phải. Câu hỏi là liệu khớp nối này có thực sự xấu. Nếu các lớp được liên kết mạnh mẽ về mặt khái niệm và hình thành một mối quan hệ siêu kiểu phụ, và không bao giờ có thể được tách rời ngay cả khi chúng chính thức tách rời ở cấp độ ngôn ngữ, thì khớp nối mạnh là tốt.
dsimcha

5
Câu thần chú rất đơn giản, "chỉ vì bạn có thể định hình play-doh để trông giống như bất cứ điều gì không có nghĩa là bạn nên làm mọi thứ ra khỏi play-doh." ;)
Evan Plaice

73

Kinh nghiệm.

Giống như bạn nói rằng chúng là công cụ cho các công việc khác nhau, nhưng cụm từ xuất hiện bởi vì mọi người không sử dụng nó theo cách đó.

Kế thừa chủ yếu là một công cụ đa hình, nhưng một số người, trong tình trạng nguy hiểm sau này, cố gắng sử dụng nó như một cách để sử dụng lại / chia sẻ mã. Lý do là "tốt nếu tôi thừa kế thì tôi nhận được tất cả các phương thức miễn phí", nhưng bỏ qua thực tế là hai lớp này có khả năng không có mối quan hệ đa hình.

Vì vậy, tại sao ủng hộ thành phần hơn thừa kế - đơn giản là vì thường xuyên hơn không phải mối quan hệ giữa các lớp không phải là một đa hình. Nó tồn tại đơn giản là để giúp nhắc nhở mọi người không giật đầu gối bằng cách kế thừa.


7
Vì vậy, về cơ bản, bạn không thể nói "bạn không nên sử dụng OOP ở nơi đầu tiên nếu bạn không hiểu về sự thay thế Liskov", vì vậy, bạn nói "ủng hộ thành phần hơn thừa kế" thay vào đó là một nỗ lực nhằm hạn chế thiệt hại do bất tài lập trình viên?
Mason Wheeler

13
@Mason: Giống như bất kỳ câu thần chú nào, "thành phần ủng hộ kế thừa" được nhắm mục tiêu vào các lập trình viên mới bắt đầu. Nếu bạn đã biết khi nào nên sử dụng thừa kế và khi nào nên sử dụng bố cục thì việc lặp lại một câu thần chú như thế là vô nghĩa.
Dean Harding

7
@Dean - Tôi không chắc người mới bắt đầu giống như người không hiểu các thực tiễn tốt nhất về thừa kế. Tôi nghĩ có nhiều hơn thế. Kế thừa kém là nguyên nhân của nhiều, rất nhiều vấn đề đau đầu trong công việc của tôi và nó không phải là mã được viết bởi các lập trình viên, những người sẽ được coi là "người mới bắt đầu".
Nicole

6
@Renesis: Điều đầu tiên tôi nghĩ đến khi tôi nghe đó là "một số người có mười năm kinh nghiệm và một số người có một năm kinh nghiệm lặp lại mười lần".
Mason Wheeler

8
Tôi đã thiết kế một dự án khá lớn ngay sau lần đầu tiên "Bắt" OO và dựa vào nó rất nhiều. Mặc dù nó hoạt động tốt, tôi liên tục thấy thiết kế dễ vỡ - gây ra những khó chịu nhỏ ở đây và đôi khi làm cho một số nhà tái cấu trúc nhất định trở thành một con chó cái hoàn chỉnh. Thật khó để giải thích toàn bộ trải nghiệm, nhưng cụm từ "Sự ủng hộ ưu tiên đối với sự kế thừa" mô tả nó CHÍNH XÁC. Lưu ý rằng nó không nói hoặc thậm chí ngụ ý "Tránh kế thừa", nó chỉ đơn giản mang lại cho bạn một chút nũng nịu khi sự lựa chọn không rõ ràng.
Bill K

43

Đó không phải là một ý tưởng mới, tôi tin rằng nó thực sự đã được giới thiệu trong cuốn sách mẫu thiết kế của GoF, được xuất bản năm 1994.

Vấn đề chính với sự kế thừa là hộp trắng. Theo định nghĩa, bạn cần biết chi tiết triển khai của lớp mà bạn đang kế thừa. Mặt khác, với bố cục, bạn chỉ quan tâm đến giao diện chung của lớp bạn đang sáng tác.

Từ cuốn sách của GoF:

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'

Bài viết trên wikipedia về cuốn sách GoF có phần giới thiệu khá hay.


14
Tôi không đồng ý với điều đó. Bạn không cần phải biết chi tiết triển khai của lớp mà bạn đang kế thừa; chỉ có các thành viên công cộngđược bảo vệ tiếp xúc với các lớp. Nếu bạn phải biết các chi tiết triển khai, thì bạn hoặc bất kỳ ai đã viết lớp cơ sở đang làm gì đó sai và nếu lỗ hổng nằm trong lớp cơ sở, thì thành phần sẽ không giúp bạn khắc phục / làm việc xung quanh nó.
Mason Wheeler

18
Làm thế nào bạn có thể không đồng ý với những gì bạn chưa đọc? Có một trang vững chắc và một nửa cuộc thảo luận từ GoF mà bạn đang nhận được chỉ là một cái nhìn nhỏ bé.
philosodad

7
@pholosodad: Tôi không đồng ý với những gì tôi chưa đọc. Tôi không đồng ý với những gì Dean viết, rằng "Theo định nghĩa, bạn cần biết chi tiết triển khai của lớp mà bạn đang kế thừa", mà tôi đã đọc.
Mason Wheeler

10
Những gì tôi viết chỉ là một bản tóm tắt những gì được mô tả trong cuốn sách của GoF. Tôi có thể đã nói một chút mạnh mẽ (bạn không cần phải biết tất cả các chi tiết triển khai) nhưng đó là lý do chung tại sao GoF nói ủng hộ sáng tác hơn kế thừa.
Dean Harding

2
Sửa lỗi cho tôi nếu tôi sai nhưng tôi thấy điều đó là "ủng hộ các mối quan hệ lớp rõ ràng (tức là Giao diện) hơn ẩn (nghĩa là kế thừa)." Các cựu cho bạn biết những gì bạn cần mà không cho bạn biết làm thế nào để làm điều đó. Điều thứ hai không chỉ cho bạn biết làm thế nào mà còn khiến bạn hối hận.
Evan Plaice

26

Để trả lời một phần câu hỏi của bạn, tôi tin rằng khái niệm này xuất hiện lần đầu tiên trong cuốn sách Các mẫu thiết kế GOF : Các yếu tố của cuốn sách Phần mềm hướng đối tượng tái sử dụng , được xuất bản lần đầu năm 1994. Cụm từ xuất hiện ở đầu trang 20, trong phần giới thiệu:

Thành phần đối tượng ủng hộ thừa kế

Họ mở đầu cho tuyên bố này với một so sánh ngắn gọn về thừa kế với thành phần. Họ không nói "không bao giờ sử dụng thừa kế".


23

"Thành phần trên thừa kế" là một cách nói ngắn gọn (và có vẻ sai lệch) "Khi cảm thấy rằng dữ liệu (hoặc hành vi) của một lớp nên được kết hợp vào một lớp khác, luôn luôn cân nhắc sử dụng thành phần trước khi áp dụng kế thừa một cách mù quáng".

Tại sao điều này lại đúng? Bởi vì sự kế thừa tạo ra sự khớp nối chặt chẽ, biên dịch thời gian giữa 2 lớp. Thành phần ngược lại là khớp nối lỏng lẻo, trong số những người khác cho phép phân tách rõ ràng các mối quan tâm, khả năng chuyển đổi các phụ thuộc trong thời gian chạy và khả năng kiểm tra phụ thuộc dễ dàng hơn, tách biệt hơn.

Điều đó chỉ có nghĩa là sự thừa kế nên được xử lý cẩn thận vì nó có chi phí, không phải là nó không hữu ích. Trên thực tế, "Thành phần trên thừa kế" thường kết thúc là "Thành phần + thừa kế so với thừa kế" vì bạn thường muốn sự phụ thuộc sáng tác của mình là một siêu lớp trừu tượng hơn là chính lớp con cụ thể. Nó cho phép bạn chuyển đổi giữa các triển khai cụ thể khác nhau của sự phụ thuộc của bạn trong thời gian chạy.

Vì lý do đó (trong số những người khác), có lẽ bạn sẽ thấy sự kế thừa được sử dụng thường xuyên hơn dưới dạng triển khai giao diện hoặc các lớp trừu tượng hơn là kế thừa vanilla.

Một ví dụ (ẩn dụ) có thể là:

"Tôi có một lớp Rắn và tôi muốn đưa vào một phần của lớp đó những gì xảy ra khi Rắn cắn. Tôi rất muốn có Rắn thừa hưởng một lớp BiterAnimal có phương thức Bite () và ghi đè lên phương thức đó để phản ánh vết cắn độc hại Nhưng Nhưng Thành phần thừa kế cảnh báo tôi rằng tôi nên thử sử dụng thành phần thay thế ... Trong trường hợp của tôi, điều này có thể chuyển thành Rắn có thành viên Bite. Lớp Bite có thể trừu tượng (hoặc giao diện) với một vài lớp con. Điều này sẽ cho phép đối với tôi những điều tốt đẹp như có các lớp con VenomousBite và DryBite và có thể thay đổi vết cắn trên cùng một trường hợp Rắn khi con rắn lớn lên. Cộng với việc xử lý tất cả các hiệu ứng của Bite trong lớp riêng của nó có thể cho phép tôi sử dụng lại nó trong lớp Frost đó , bởi vì sương giá nhưng không phải là BiterAnimal, v.v. "


4
Ví dụ rất hay với Snake. Tôi đã gặp một trường hợp tương tự gần đây với các lớp học của riêng tôi
Maksee

22

Một số đối số có thể cho thành phần:

Thành phần là ngôn ngữ
thừa kế / ngôn ngữ không rõ ràng hơn một chút và những gì nó thi hành / yêu cầu / kích hoạt sẽ khác nhau giữa các ngôn ngữ về những gì mà lớp phụ / siêu lớp có quyền truy cập và hiệu suất của nó có thể có phương thức ảo wrt, v.v ... Thành phần khá cơ bản và đòi hỏi hỗ trợ ngôn ngữ rất ít và do đó việc triển khai trên các nền tảng / khung khác nhau có thể chia sẻ các mẫu thành phần dễ dàng hơn.

Thành phần là một cách rất đơn giản và khéo léo để xây dựng các đối tượng
Kế thừa là tương đối dễ hiểu, nhưng vẫn không dễ dàng chứng minh trong cuộc sống thực. Nhiều đối tượng trong cuộc sống thực có thể được chia thành các phần và sáng tác. Giả sử một chiếc xe đạp có thể được chế tạo bằng cách sử dụng hai bánh xe, khung, ghế ngồi, xích, v.v ... Dễ dàng giải thích bằng bố cục. Trong khi đó trong một phép ẩn dụ thừa kế, bạn có thể nói rằng một chiếc xe đạp kéo dài một bánh xe đạp, hơi khả thi nhưng vẫn khác xa so với hình ảnh thực tế so với bố cục (rõ ràng đây không phải là một ví dụ thừa kế rất tốt, nhưng điểm vẫn giữ nguyên). Ngay cả từ kế thừa (ít nhất là hầu hết những người nói tiếng Anh Mỹ mà tôi mong đợi) sẽ tự động gọi một ý nghĩa dọc theo dòng chữ "Cái gì đó được truyền lại từ người thân đã qua đời" có một số tương quan với ý nghĩa của nó trong phần mềm, nhưng vẫn chỉ phù hợp một cách lỏng lẻo.

Thành phần hầu như luôn linh hoạt hơn
Sử dụng bố cục, bạn luôn có thể chọn xác định hành vi của riêng mình hoặc chỉ đơn giản là phơi bày phần đó trong các phần sáng tác của bạn. Bằng cách này, bạn không phải đối mặt với bất kỳ hạn chế nào có thể được áp đặt bởi hệ thống phân cấp thừa kế (ảo so với không ảo, v.v.)

Vì vậy, nó có thể là do Thành phần tự nhiên là một phép ẩn dụ đơn giản hơn, có ít ràng buộc về mặt lý thuyết hơn là sự kế thừa. Hơn nữa, những lý do cụ thể này có thể rõ ràng hơn trong thời gian thiết kế, hoặc có thể dính ra khi xử lý một số điểm đau của thừa kế.

Disclaimer:
Rõ ràng nó không phải là đường cắt / đường một chiều rõ ràng này. Mỗi giá trị thiết kế đánh giá của một số mẫu / công cụ. Kế thừa được sử dụng rộng rãi, có rất nhiều lợi ích và nhiều lần thanh lịch hơn so với thành phần. Đây chỉ là một số lý do có thể người ta có thể sử dụng khi ủng hộ sáng tác.


1
"Rõ ràng nó không phải là đường cắt / đường một cách rõ ràng này." Khi nào thành phần không linh hoạt (hoặc bằng nhau) linh hoạt hơn thừa kế? Tôi sẽ tranh luận rằng nó rất nhiều một chiều. Kế thừa chỉ là đường cú pháp cho một trường hợp đặc biệt của thành phần.
weberc2

Thành phần thường không linh hoạt khi sử dụng ngôn ngữ triển khai bố cục bằng các giao diện được triển khai rõ ràng , vì các giao diện đó không được phép phát triển theo cách tương thích ngược theo thời gian. #jmtcw
MikeSchinkel

11

Có lẽ bạn chỉ nhận thấy mọi người nói điều này trong vài tháng qua, nhưng nó đã được các lập trình viên giỏi biết đến lâu hơn thế rất nhiều. Tôi chắc chắn đã nói nó thích hợp trong khoảng một thập kỷ.

Điểm của khái niệm là có một chi phí khái niệm lớn để kế thừa. Khi bạn đang sử dụng tính kế thừa, thì mỗi cuộc gọi phương thức đơn lẻ có một công văn ngầm trong đó. Nếu bạn có cây thừa kế sâu, hoặc nhiều công văn, hoặc (thậm chí tệ hơn) cả hai, thì hãy tìm ra phương thức cụ thể sẽ gửi đến trong bất kỳ cuộc gọi cụ thể nào có thể trở thành Pita hoàng gia. Nó làm cho lý luận chính xác về mã phức tạp hơn và làm cho việc gỡ lỗi khó hơn.

Hãy để tôi đưa ra một ví dụ đơn giản để minh họa. Giả sử rằng sâu trong một cây thừa kế, ai đó đã đặt tên cho một phương thức foo. Sau đó, một người khác đến và thêm foovào ngọn cây, nhưng làm điều gì đó khác biệt. (Trường hợp này phổ biến hơn với nhiều thừa kế.) Bây giờ người đó làm việc ở lớp gốc đã phá vỡ lớp con tối nghĩa và có lẽ không nhận ra điều đó. Bạn có thể có phạm vi bảo hiểm 100% với các bài kiểm tra đơn vị và không nhận thấy sự phá vỡ này bởi vì người đứng đầu sẽ không nghĩ đến việc kiểm tra lớp con và các bài kiểm tra cho lớp con không nghĩ đến việc thử nghiệm các phương thức mới được tạo ở đầu . (Phải thừa nhận rằng có nhiều cách để viết bài kiểm tra đơn vị sẽ nắm bắt được điều này, nhưng cũng có những trường hợp bạn không thể dễ dàng viết bài kiểm tra theo cách đó.)

Ngược lại khi bạn sử dụng bố cục, ở mỗi cuộc gọi, thông thường bạn sẽ gửi cuộc gọi đến rõ ràng hơn. (OK, nếu bạn đang sử dụng đảo ngược điều khiển, ví dụ như tiêm phụ thuộc, thì việc tìm ra cuộc gọi đi đâu cũng có thể gặp vấn đề. Nhưng thường thì việc tìm hiểu sẽ đơn giản hơn.) Điều này giúp cho việc suy luận về nó dễ dàng hơn. Như một phần thưởng, thành phần dẫn đến việc có các phương thức tách biệt với nhau. Ví dụ trên không nên xảy ra ở đó bởi vì lớp con sẽ chuyển sang một thành phần tối nghĩa nào đó và không bao giờ có câu hỏi về việc cuộc gọi đến foođược dành cho thành phần tối nghĩa hay đối tượng chính.

Bây giờ bạn hoàn toàn đúng khi kế thừa và thành phần là hai công cụ rất khác nhau phục vụ hai loại khác nhau. Kế thừa chắc chắn mang chi phí khái niệm, nhưng khi nó là công cụ phù hợp cho công việc, nó mang ít chi phí khái niệm hơn là cố gắng không sử dụng nó và làm bằng tay những gì nó làm cho bạn. Không ai biết những gì họ đang làm sẽ nói rằng bạn không bao giờ nên sử dụng quyền thừa kế. Nhưng hãy chắc chắn rằng đó là điều đúng đắn để làm.

Thật không may, nhiều nhà phát triển tìm hiểu về phần mềm hướng đối tượng, tìm hiểu về kế thừa và sau đó ra ngoài để sử dụng rìu mới của họ thường xuyên nhất có thể. Điều đó có nghĩa là họ cố gắng sử dụng tính kế thừa trong đó thành phần là công cụ phù hợp. Hy vọng rằng họ sẽ học tốt hơn trong thời gian, nhưng thường thì điều này không xảy ra cho đến khi một vài chi bị loại bỏ, v.v. Nói với họ trước rằng đó là một ý tưởng tồi có xu hướng tăng tốc quá trình học tập và giảm chấn thương.


3
Được rồi, tôi đoán điều đó có ý nghĩa từ góc độ C ++. Đó là điều tôi chưa bao giờ nghĩ tới, bởi vì đó không phải là vấn đề ở Delphi, đó là điều tôi sử dụng hầu hết thời gian. (Không có nhiều kế thừa và nếu bạn có một phương thức trong lớp cơ sở và một phương thức khác có cùng tên trong một lớp dẫn xuất không ghi đè phương thức cơ sở, trình biên dịch sẽ đưa ra cảnh báo, vì vậy bạn không vô tình kết thúc với loại vấn đề này.)
Mason Wheeler

1
@Mason: Phiên bản Object Pascal của Ander (hay còn gọi là Delphi) vượt trội hơn C ++ và Java khi nói đến vấn đề kế thừa lớp cơ sở mong manh. Không giống như C ++ và Java, quá tải một phương thức ảo được kế thừa không tiềm ẩn.
bit-twiddler

2
@ bit-twiddler, những gì bạn nói về C ++ và Java có thể nói về Smalltalk, Perl, Python, Ruby, JavaScript, Common Lisp, Objective-C và mọi ngôn ngữ khác mà tôi đã học cung cấp bất kỳ hình thức hỗ trợ OO nào. Điều đó nói rằng, googling gợi ý rằng C # đi theo sự dẫn dắt của Object Pascal.
btilly

3
Đó là bởi vì Anders Hejlsberg đã thiết kế hương vị của Object Pascal (còn gọi là Delphi) và C # của Borland.
bit-twiddler

8

Đó là một phản ứng đối với quan sát rằng những người mới bắt đầu OO có xu hướng sử dụng tính kế thừa khi họ không cần. Kế thừa chắc chắn không phải là một điều xấu, nhưng nó có thể bị lạm dụng. Nếu một lớp chỉ cần chức năng từ lớp khác, thì thành phần có thể sẽ hoạt động. Trong các trường hợp khác, thừa kế sẽ hoạt động và thành phần sẽ không.

Kế thừa từ một lớp học bao hàm rất nhiều điều. Nó ngụ ý rằng Derogen là một loại Base (xem nguyên tắc Thay thế Liskov để biết chi tiết về tin đồn), trong đó bất cứ khi nào bạn sử dụng Base đều có ý nghĩa khi sử dụng Derogen. Nó cung cấp quyền truy cập Derogen cho các thành viên được bảo vệ và các chức năng thành viên của Base. Đó là một mối quan hệ chặt chẽ, có nghĩa là nó có tính khớp nối cao và các thay đổi đối với một mối quan hệ có nhiều khả năng yêu cầu thay đổi đối với người khác.

Khớp nối là một điều xấu. Nó làm cho các chương trình khó hiểu và sửa đổi hơn. Những thứ khác bằng nhau, bạn nên luôn luôn chọn tùy chọn có ít khớp nối hơn.

Do đó, nếu thành phần hoặc kế thừa sẽ thực hiện công việc một cách hiệu quả, hãy chọn thành phần, vì đó là khớp nối thấp hơn. Nếu thành phần sẽ không thực hiện công việc một cách hiệu quả và kế thừa sẽ, chọn kế thừa, bởi vì bạn phải làm.


"Trong các trường hợp khác, thừa kế sẽ hoạt động và thành phần sẽ không." Khi nào? Kế thừa có thể được mô hình hóa bằng cách sử dụng đa hình thành phần và giao diện, vì vậy tôi sẽ ngạc nhiên nếu có bất kỳ trường hợp nào mà bố cục không hoạt động.
weberc2

8

Đây là hai xu của tôi (ngoài tất cả các điểm xuất sắc đã được nâng lên):

IMHO, thực tế là hầu hết các lập trình viên không thực sự có được sự kế thừa, và cuối cùng họ đã làm quá mức, một phần là do cách mà khái niệm này được dạy. Khái niệm này tồn tại như một cách để cố gắng ngăn cản họ làm việc quá sức, thay vì tập trung vào việc dạy họ làm thế nào cho đúng.

Bất cứ ai đã dành thời gian giảng dạy hoặc cố vấn đều biết rằng đây là điều thường xảy ra, đặc biệt là với các nhà phát triển mới có kinh nghiệm trong các mô hình khác:

Những nhà phát triển ban đầu cảm thấy rằng sự kế thừa là khái niệm đáng sợ này. Vì vậy, cuối cùng họ tạo ra các kiểu với sự chồng chéo giao diện (ví dụ, cùng một hành vi được chỉ định mà không có phân nhóm chung) và với các phần chung để thực hiện các phần chức năng chung.

Sau đó (thường là kết quả của việc dạy quá nhiệt tình), họ tìm hiểu về lợi ích của việc thừa kế, nhưng nó thường được dạy như là giải pháp bắt tất cả để tái sử dụng. Họ kết thúc với một nhận thức rằng bất kỳ hành vi chia sẻ phải được chia sẻ thông qua thừa kế. Điều này là do trọng tâm thường tập trung vào kế thừa thực hiện hơn là phân nhóm.

Trong 80% các trường hợp đó là đủ tốt. Nhưng 20% ​​còn lại là nơi vấn đề bắt đầu. Để tránh việc viết lại và để đảm bảo rằng họ đã tận dụng lợi thế của việc chia sẻ triển khai, họ bắt đầu thiết kế hệ thống phân cấp của họ xung quanh việc thực hiện dự định thay vì trừu tượng hóa cơ bản. "Stack kế thừa từ danh sách liên kết đôi" là một ví dụ tốt về điều này.

Hầu hết các giáo viên không khăng khăng giới thiệu khái niệm giao diện vào thời điểm này, bởi vì đó là một khái niệm khác hoặc bởi vì (trong C ++) bạn phải giả mạo chúng bằng cách sử dụng các lớp trừu tượng và nhiều kế thừa không được dạy trong giai đoạn này. Trong Java, nhiều giáo viên không phân biệt "không thừa kế nhiều lần" hay "thừa kế nhiều lần là xấu" với tầm quan trọng của các giao diện.

Tất cả điều này được kết hợp bởi thực tế là chúng ta đã học được rất nhiều vẻ đẹp của việc không phải viết mã thừa với kế thừa triển khai, hàng tấn mã ủy nhiệm đơn giản trông không tự nhiên. Vì vậy, bố cục có vẻ lộn xộn, đó là lý do tại sao chúng ta cần những quy tắc này để thúc đẩy các lập trình viên mới sử dụng chúng (dù sao họ cũng làm quá).


Đó là một phần thực sự của giáo dục. Bạn phải làm các khóa học cao hơn để có được, tức là 20% còn lại, nhưng bạn, với tư cách là sinh viên, chỉ được dạy các khóa học cơ bản và có lẽ là trung cấp. Cuối cùng, bạn cảm thấy được dạy tốt bởi vì bạn đã làm tốt các khóa học của mình (và đơn giản là không thấy nó chỉ là một cái ghim trên thang). Đó chỉ là một phần của thực tế và đặt cược tốt nhất của chúng tôi là hiểu tác dụng phụ của nó, chứ không phải tấn công các lập trình viên.
Độc lập

8

Trong một bình luận, Mason đã đề cập rằng một ngày nào đó chúng ta sẽ nói về Thừa kế được coi là có hại .

Tôi cũng mong là như vậy.

Vấn đề với thừa kế ngay lập tức đơn giản và chết người, nó không tôn trọng ý tưởng rằng một khái niệm nên có một chức năng.

Trong hầu hết các ngôn ngữ OO, khi kế thừa từ một lớp cơ sở, bạn:

  • kế thừa từ giao diện của nó
  • kế thừa từ việc thực hiện nó (cả dữ liệu và phương pháp)

Và ở đây trở thành rắc rối.

Đây không phải là cách tiếp cận duy nhất, mặc dù 00 ngôn ngữ chủ yếu bị mắc kẹt với nó. May mắn là giao diện / lớp trừu tượng tồn tại trong những.

Ngoài ra, việc không dễ dàng thực hiện các cách khác góp phần làm cho nó được sử dụng phần lớn: thẳng thắn, ngay cả khi biết điều này, bạn sẽ thừa hưởng từ một giao diện và nhúng lớp cơ sở theo thành phần, ủy thác hầu hết các lệnh gọi phương thức? Mặc dù vậy, nó sẽ tốt hơn đáng kể, thậm chí bạn sẽ được cảnh báo nếu đột nhiên một phương thức mới xuất hiện trong giao diện và sẽ phải chọn, một cách có ý thức, cách thực hiện nó.

Là một điểm đối lập, Haskellchỉ cho phép sử dụng Nguyên tắc Liskov khi "xuất phát" từ các giao diện thuần túy (được gọi là các khái niệm) (1). Bạn không thể xuất phát từ một lớp hiện có, chỉ có thành phần cho phép bạn nhúng dữ liệu của nó.

(1) các khái niệm có thể cung cấp một mặc định hợp lý cho việc triển khai, nhưng vì chúng không có dữ liệu, nên mặc định này chỉ có thể được định nghĩa theo các phương thức khác được đề xuất bởi khái niệm hoặc theo thuật ngữ của hằng số.


7

Câu trả lời đơn giản là: thừa kế có khớp nối lớn hơn thành phần. Đưa ra hai lựa chọn, với những phẩm chất tương đương khác, hãy chọn một lựa chọn ít kết hợp hơn.


2
Nhưng đó là toàn bộ vấn đề. Chúng không "tương đương." Kế thừa cho phép thay thế và đa hình Liskov, đó là toàn bộ điểm sử dụng OOP. Thành phần không.
Mason Wheeler

4
Đa hình / LSP không phải là "toàn bộ điểm" của OOP, chúng là một tính năng. Ngoài ra còn có sự đóng gói, trừu tượng, v.v ... Kế thừa ngụ ý một mối quan hệ "là một", mô hình liên kết một mối quan hệ "có một".
Steve

@Steve: Bạn có thể thực hiện trừu tượng hóa và đóng gói trong bất kỳ mô hình nào hỗ trợ việc tạo cấu trúc dữ liệu và quy trình / hàm. Nhưng tính đa hình (đặc biệt đề cập đến việc gửi phương thức ảo trong ngữ cảnh này) và sự thay thế Liskov là duy nhất cho lập trình hướng đối tượng. Đó là những gì tôi muốn nói.
Mason Wheeler

3
@Mason: Hướng dẫn là "ủng hộ thành phần hơn thừa kế." Điều này không có nghĩa là không sử dụng hoặc thậm chí tránh thừa kế. Tất cả nó nói rằng khi tất cả những thứ khác bằng nhau, chọn thành phần. Nhưng nếu bạn cần thừa kế, hãy sử dụng nó!
Jeffrey Faust

7

Tôi nghĩ loại lời khuyên này giống như nói "thích lái xe hơn bay". Đó là, máy bay có tất cả các loại lợi thế so với ô tô, nhưng điều đó đi kèm với một sự phức tạp nhất định. Vì vậy, nếu nhiều người cố gắng bay từ trung tâm thành phố đến vùng ngoại ô, lời khuyên họ thực sự cần thực sự là họ không cần phải bay, và thực tế, việc bay sẽ khiến nó trở nên phức tạp hơn trong thời gian dài, ngay cả khi nó có vẻ mát mẻ / hiệu quả / dễ dàng trong ngắn hạn. Trong khi đó, khi bạn làm cần để bay, nó thường được coi là hiển nhiên.

Tương tự như vậy, sự kế thừa có thể làm những thứ mà bố cục không thể, nhưng bạn nên sử dụng nó khi bạn cần chứ không phải khi bạn không. Vì vậy, nếu bạn không bao giờ bị cám dỗ chỉ cho rằng bạn cần sự kế thừa khi bạn không, thì bạn không cần lời khuyên về "thích sáng tác". Nhưng nhiều người làm , và làm cần lời khuyên đó.

Điều này được cho là ngầm hiểu rằng khi bạn thực sự cần thừa kế, điều đó là hiển nhiên và sau đó bạn nên sử dụng nó.

Ngoài ra, câu trả lời của Steven Lowe. Thực sự, thực sự đó.


1
Tôi thích sự tương tự của bạn
barrylloyd

2

Kế thừa không phải là xấu, và thành phần không phải là tốt. Chúng chỉ là các công cụ mà một lập trình viên OO có thể sử dụng để thiết kế phần mềm.

Khi bạn nhìn vào một lớp học, nó có làm được nhiều hơn mức cần thiết ( SRP ) không? Là nó sao chép chức năng không cần thiết ( DRY ), hay nó quá quan tâm đến các thuộc tính hoặc phương thức của các lớp khác ( Envy Feature )?. Nếu lớp vi phạm tất cả các khái niệm này (và có thể nhiều hơn), thì nó có cố gắng trở thành Lớp Chúa không . Đây là một số vấn đề có thể xảy ra khi thiết kế phần mềm, không có vấn đề nào nhất thiết phải là vấn đề thừa kế, nhưng có thể nhanh chóng tạo ra những cơn đau đầu lớn và sự phụ thuộc dễ vỡ trong đó tính đa hình cũng được áp dụng.

Căn nguyên của vấn đề có thể là sự thiếu hiểu biết về thừa kế và một trong những lựa chọn kém về thiết kế, hoặc có thể không nhận ra "mùi mã" liên quan đến các lớp không tuân thủ Nguyên tắc Trách nhiệm duy nhất . Đa hìnhLiskov thay thế không cần phải được loại bỏ để ủng hộ thành phần. Đa hình tự nó có thể được áp dụng mà không cần dựa vào sự kế thừa, đây đều là những khái niệm khá miễn phí. Nếu áp dụng chu đáo. Bí quyết là nhằm mục đích giữ cho mã của bạn đơn giản và sạch sẽ, và không chịu khuất phục vì quá quan tâm đến số lượng các lớp mà bạn cần tạo để tạo ra một thiết kế hệ thống mạnh mẽ.

Đối với vấn đề ưu tiên sáng tác so với thừa kế, đây thực sự chỉ là một trường hợp khác về ứng dụng chu đáo của các yếu tố thiết kế có ý nghĩa nhất về vấn đề đang được giải quyết. Nếu bạn không cần kế thừa hành vi, thì có lẽ bạn không nên vì sáng tác sẽ giúp tránh sự không tương thích và các nỗ lực tái cấu trúc lớn sau này. Mặt khác, nếu bạn thấy rằng bạn đang lặp lại rất nhiều mã sao cho tất cả các bản sao được tập trung vào một nhóm các lớp tương tự, thì có thể việc tạo một tổ tiên chung sẽ giúp giảm số lượng các cuộc gọi và các lớp giống hệt nhau bạn có thể cần lặp lại giữa mỗi lớp. Do đó, bạn ủng hộ sáng tác, nhưng bạn không cho rằng kế thừa không bao giờ được áp dụng.


-1

Trích dẫn thực tế, tôi tin, từ "Java hiệu quả" của Joshua Bloch, trong đó nó là tiêu đề của một trong các chương. Ông tuyên bố rằng sự kế thừa là an toàn trong một gói và bất cứ nơi nào một lớp được thiết kế và ghi lại cụ thể để gia hạn. Ông tuyên bố, như những người khác đã lưu ý, rằng sự kế thừa phá vỡ sự đóng gói bởi vì một lớp con phụ thuộc vào các chi tiết thực hiện của siêu lớp của nó. Điều này dẫn đến sự mong manh.

Mặc dù ông không đề cập đến nó, nhưng đối với tôi, có vẻ như thừa kế duy nhất trong Java có nghĩa là thành phần mang lại cho bạn sự linh hoạt hơn nhiều so với kế thừa.

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.