Có bất kỳ lý do thực tế nào mà người Viking bị ghét không?


123

Tôi luôn thích ý tưởng có nhiều sự kế thừa được hỗ trợ trong một ngôn ngữ. Thông thường, mặc dù nó cố tình tha thứ và "sự thay thế" được cho là giao diện. Các giao diện đơn giản là không bao gồm tất cả các kế thừa nhiều mặt đất, và hạn chế này đôi khi có thể dẫn đến nhiều mã soạn sẵn hơn.

Lý do cơ bản duy nhất tôi từng nghe cho điều này là vấn đề kim cương với các lớp cơ sở. Tôi không thể chấp nhận điều đó. Đối với tôi, nó phát ra rất nhiều điều tồi tệ như, "Chà, có thể làm hỏng nó lên, vì vậy nó tự động là một ý tưởng tồi." Bạn có thể làm hỏng bất cứ điều gì trong một ngôn ngữ lập trình, và tôi có nghĩa là bất cứ điều gì. Tôi chỉ không thể thực hiện điều này một cách nghiêm túc, ít nhất là không có lời giải thích kỹ lưỡng hơn.

Chỉ cần nhận thức được vấn đề này là 90% trận chiến. Hơn nữa, tôi nghĩ rằng tôi đã nghe một cái gì đó nhiều năm trước về một công việc có mục đích chung liên quan đến thuật toán "phong bì" hoặc một cái gì đó tương tự (điều này có rung chuông không, bất cứ ai?).

Liên quan đến vấn đề kim cương, vấn đề có khả năng thực sự duy nhất tôi có thể nghĩ đến là nếu bạn đang cố gắng sử dụng thư viện của bên thứ ba và không thể thấy rằng hai lớp dường như không liên quan trong thư viện đó có một lớp cơ sở chung, nhưng ngoài tài liệu, một tính năng ngôn ngữ đơn giản có thể, giả sử, yêu cầu bạn tuyên bố cụ thể ý định tạo ra một viên kim cương trước khi nó thực sự biên dịch một viên kim cương cho bạn. Với tính năng như vậy, bất kỳ việc tạo ra một viên kim cương đều là cố ý, liều lĩnh hoặc bởi vì người ta không biết về cạm bẫy này.

Vì vậy, tất cả những gì được nói ... Có bất kỳ lý do thực sự nào mà hầu hết mọi người ghét nhiều di sản, hoặc tất cả chỉ là một loạt các hiềm khích gây ra nhiều tác hại hơn là tốt? Có một cái gì đó mà tôi không nhìn thấy ở đây? Cảm ơn bạn.

Thí dụ

Xe kéo dài WheeledVehicle, KIASpectra kéo dài Xe và Điện tử, KIASpectra chứa Radio. Tại sao KIASpectra không chứa Điện tử?

  1. Bởi vì nó một điện tử. Kế thừa so với thành phần phải luôn luôn là mối quan hệ is-a và mối quan hệ có-có.

  2. Bởi vì nó một điện tử. Có dây, bảng mạch, công tắc, vv tất cả lên và xuống điều đó.

  3. Bởi vì nó một điện tử. Nếu pin của bạn hết vào mùa đông, bạn sẽ gặp nhiều rắc rối như thể tất cả các bánh xe của bạn đột nhiên bị mất.

Tại sao không sử dụng giao diện? Lấy số 3 chẳng hạn. Tôi không muốn viết đi viết lại nhiều lần và tôi thực sự không muốn tạo ra một lớp người trợ giúp proxy kỳ quái để làm điều này:

private void runOrDont()
{
    if (this.battery)
    {
        if (this.battery.working && this.switchedOn)
        {
            this.run();
            return;
        }
    }
    this.dontRun();
}

(Chúng tôi không quan tâm đến việc triển khai đó là tốt hay xấu.) Bạn có thể tưởng tượng làm thế nào có thể có một số chức năng này được liên kết với Điện tử không liên quan đến bất cứ điều gì trong WheeledVehicle và ngược lại.

Tôi không chắc có nên giải quyết ví dụ đó hay không, vì có chỗ để giải thích ở đó. Bạn cũng có thể nghĩ về mặt mở rộng Máy bay và FlyingObject và Bird kéo dài Animal và FlyingObject, hoặc về một ví dụ thuần túy hơn nhiều.


24
nó cũng khuyến khích sự kế thừa so với thành phần ... (khi nó phải theo cách khác)
ratchet freak

34
"Bạn có thể làm hỏng nó" là một lý do hoàn toàn hợp lệ để xóa một tính năng. Thiết kế ngôn ngữ hiện đại có phần bị phân tách ở điểm đó, nhưng bạn thường có thể phân loại các ngôn ngữ thành "Sức mạnh từ sự hạn chế" và "Sức mạnh từ sự linh hoạt". Cả hai điều này đều "đúng", tôi không nghĩ, cả hai đều có những điểm mạnh để thực hiện. MI là một trong những thứ được sử dụng cho Evil thường xuyên hơn không, và do đó, các ngôn ngữ hạn chế sẽ loại bỏ nó. Những người linh hoạt thì không, bởi vì "thường xuyên hơn không" không phải là "luôn luôn theo nghĩa đen". Điều đó nói rằng, mixins / đặc điểm là một giải pháp tốt hơn cho trường hợp chung, tôi nghĩ vậy.
Phoshi

8
Có những lựa chọn thay thế an toàn hơn cho nhiều kế thừa trong một số ngôn ngữ, vượt ra ngoài giao diện. Hãy xem Scala Traits- chúng hoạt động như các giao diện với việc triển khai tùy chọn, nhưng có một số hạn chế giúp ngăn ngừa các vấn đề như vấn đề kim cương phát sinh.
KChaloux

5
Xem thêm câu hỏi đã đóng trước đây: lập trình
viên.stackexchange.com /

19
KiaSpectrakhông phải là một Electronic ; nó Điện tử, và có thể là một ElectronicCar(sẽ kéo dài Car...)
Brian S

Câu trả lời:


68

Trong nhiều trường hợp, mọi người sử dụng tính kế thừa để cung cấp một đặc điểm cho một lớp. Ví dụ, hãy nghĩ về một chiếc Pegasus. Với nhiều gia tài, bạn có thể muốn nói rằng Pegasus mở rộng Ngựa và Chim vì bạn đã phân loại Chim là một loài động vật có cánh.

Tuy nhiên, Chim có những đặc điểm khác mà Pegasi không có. Chẳng hạn, chim đẻ trứng, Pegasi sinh con. Nếu thừa kế là phương tiện duy nhất của bạn để vượt qua các đặc điểm chia sẻ thì không có cách nào để loại trừ tính trạng đẻ trứng ra khỏi loài Pegasus.

Một số ngôn ngữ đã chọn để biến các đặc điểm thành một cấu trúc rõ ràng trong ngôn ngữ. Khác nhẹ nhàng hướng dẫn bạn theo hướng đó bằng cách loại bỏ MI khỏi ngôn ngữ. Dù bằng cách nào, tôi không thể nghĩ về một trường hợp duy nhất mà tôi nghĩ rằng "Người đàn ông tôi thực sự cần MI để làm điều này đúng cách".

Ngoài ra, hãy thảo luận về sự kế thừa THỰC SỰ là gì. Khi bạn kế thừa từ một lớp, bạn có một sự phụ thuộc vào lớp đó, nhưng bạn cũng phải hỗ trợ các hợp đồng mà lớp đó hỗ trợ, cả ngầm và rõ ràng.

Lấy ví dụ cổ điển về hình vuông kế thừa từ hình chữ nhật. Hình chữ nhật hiển thị thuộc tính chiều dài và chiều rộng và phương thức getPerimet và getArea. Hình vuông sẽ ghi đè chiều dài và chiều rộng để khi một cái được đặt, cái kia được đặt khớp với getPerimet và getArea sẽ hoạt động như nhau (2 * length + 2 * width cho chu vi và chiều rộng * chiều rộng cho diện tích).

Có một trường hợp thử nghiệm duy nhất bị phá vỡ nếu bạn thay thế việc thực hiện hình vuông này cho hình chữ nhật.

var rectangle = new Square();
rectangle.length= 5;
rectangle.width= 6;
Assert.AreEqual(30, rectangle.GetArea()); 
//Square returns 36 because setting the width clobbers the length

Nó đủ khó để làm mọi thứ đúng với một chuỗi thừa kế duy nhất. Nó thậm chí còn tồi tệ hơn khi bạn thêm một thứ khác vào hỗn hợp.


Những cạm bẫy mà tôi đã đề cập với Pegasus trong mối quan hệ MI và Hình chữ nhật / Hình vuông đều là kết quả của một thiết kế thiếu kinh nghiệm cho các lớp. Về cơ bản tránh nhiều kế thừa là một cách giúp các nhà phát triển bắt đầu tránh tự bắn vào chân mình. Giống như tất cả các nguyên tắc thiết kế, có kỷ luật và đào tạo dựa trên chúng cho phép bạn kịp thời khám phá khi nào thoát khỏi chúng. Xem Mô hình tiếp thu kỹ năng của Dreyfus , ở cấp độ Chuyên gia, kiến ​​thức nội tại của bạn vượt qua sự phụ thuộc vào các câu châm ngôn / nguyên tắc. Bạn có thể "cảm thấy" khi một quy tắc không được áp dụng.

Và tôi đồng ý rằng tôi phần nào bị lừa với một ví dụ "thế giới thực" về lý do MI được tán thành.

Chúng ta hãy nhìn vào một khung UI. Cụ thể, hãy nhìn vào một vài vật dụng có thể thoạt nhìn giống như chúng chỉ đơn giản là sự kết hợp của hai vật dụng khác. Giống như một ComboBox. ComboBox là một TextBox có DropDownList hỗ trợ. Tức là tôi có thể nhập một giá trị hoặc tôi có thể chọn từ danh sách giá trị được quy định trước. Một cách tiếp cận ngây thơ sẽ là kế thừa ComboBox từ TextBox và DropDownList.

Nhưng Textbox của bạn lấy được giá trị của nó từ những gì người dùng đã gõ. Trong khi DDL nhận được giá trị của nó từ những gì người dùng chọn. Ai có tiền lệ? DDL có thể đã được thiết kế để xác minh và từ chối mọi đầu vào không có trong danh sách giá trị ban đầu của nó. Chúng ta có ghi đè logic đó không? Điều đó có nghĩa là chúng ta phải phơi bày logic bên trong để người thừa kế ghi đè. Hoặc tệ hơn, thêm logic vào lớp cơ sở chỉ có ở đó để hỗ trợ lớp con (vi phạm Nguyên tắc đảo ngược phụ thuộc ).

Tránh MI giúp bạn vượt qua hoàn toàn cạm bẫy này. Và có thể dẫn đến việc bạn trích xuất các đặc điểm chung, có thể sử dụng lại của các widget UI để chúng có thể được áp dụng khi cần. Một ví dụ tuyệt vời về điều này là Thuộc tính đính kèm WPF cho phép một thành phần khung trong WPF cung cấp một thuộc tính mà một thành phần khung khác có thể sử dụng mà không cần kế thừa từ thành phần khung chính.

Ví dụ, Grid là một bảng bố trí trong WPF và nó có các thuộc tính được gắn với Cột và Hàng chỉ định vị trí của một phần tử con trong sắp xếp của lưới. Nếu không có thuộc tính đính kèm, nếu tôi muốn sắp xếp một Nút trong Lưới, Nút sẽ phải xuất phát từ Lưới để có thể có quyền truy cập vào các thuộc tính Cột và Hàng.

Các nhà phát triển đã đưa khái niệm này đi xa hơn và sử dụng các thuộc tính đính kèm như một cách để cấu thành hành vi (ví dụ ở đây là bài viết của tôi về việc tạo GridView có thể sắp xếp bằng các thuộc tính đính kèm được viết trước khi WPF bao gồm DataGrid). Cách tiếp cận đã được công nhận là Mẫu thiết kế XAML có tên Hành vi đính kèm .

Hy vọng rằng điều này cung cấp một cái nhìn sâu sắc hơn một chút về lý do tại sao Nhiều kế thừa thường được tán thành.


14
"Người đàn ông tôi thực sự cần MI để làm điều này đúng cách" - xem câu trả lời của tôi cho một bản đồ. Tóm lại, các khái niệm trực giao thỏa mãn mối quan hệ is-a có ý nghĩa đối với MI. Chúng rất hiếm, nhưng chúng tồn tại.
MrFox

13
Man, tôi ghét ví dụ hình chữ nhật vuông. Có rất nhiều ví dụ rõ ràng hơn không yêu cầu tính đột biến.
asmeker

9
Tôi thích câu trả lời cho "cung cấp một ví dụ thực tế" là thảo luận về một sinh vật hư cấu. Trớ trêu tuyệt vời +1
jjathman

3
Vì vậy, bạn đang nói rằng nhiều kế thừa là xấu, bởi vì thừa kế là xấu (ví dụ của bạn là tất cả về kế thừa giao diện duy nhất). Do đó, chỉ dựa trên câu trả lời này, một ngôn ngữ nên được loại bỏ tính kế thừa và giao diện hoặc có nhiều kế thừa.
ctrl-alt-delor

12
Tôi không nghĩ những ví dụ này thực sự có tác dụng ... không ai nói rằng một con chó đốm là một con chim và một con ngựa ... bạn nói đó là một con chim cánh cụt và một con chó săn. Ví dụ hình vuông và hình chữ nhật có ý nghĩa hơn một chút bởi vì bạn thấy mình có một đối tượng hành xử khác với bạn nghĩ dựa trên định nghĩa đã được khẳng định của nó. Tuy nhiên, tôi nghĩ rằng tình huống này sẽ là lỗi của các lập trình viên vì đã không nắm bắt được điều này (nếu thực sự điều này đã chứng minh là một vấn đề).
Richard

60

Có một cái gì đó mà tôi không nhìn thấy ở đây?

Việc cho phép nhiều kế thừa làm cho các quy tắc về quá tải chức năng và công văn ảo trở nên khó khăn hơn, cũng như việc thực hiện ngôn ngữ xung quanh bố trí đối tượng. Những người thiết kế / triển khai ngôn ngữ tác động này khá nhiều, và nâng cao thanh đã cao để hoàn thành một ngôn ngữ, ổn định và được chấp nhận.

Một lập luận phổ biến khác mà tôi đã thấy (và đôi khi được đưa ra) là bằng cách có hai lớp cơ sở +, đối tượng của bạn gần như luôn vi phạm Nguyên tắc Trách nhiệm duy nhất. Cả hai lớp cơ sở + đều là các lớp khép kín tốt đẹp có trách nhiệm riêng (gây ra vi phạm) hoặc chúng là các loại một phần / trừu tượng làm việc với nhau để tạo ra một trách nhiệm gắn kết duy nhất.

Trong trường hợp khác, bạn có 3 kịch bản:

  1. A không biết gì về B - Tuyệt vời, bạn có thể kết hợp các lớp học vì bạn đã may mắn.
  2. A biết về B - Tại sao A không được thừa hưởng từ B?
  3. A và B biết về nhau - Tại sao bạn không học một lớp? Lợi ích gì đến từ việc làm cho những thứ này được ghép nối nhưng có thể thay thế một phần?

Cá nhân, tôi nghĩ rằng nhiều kế thừa có một bản rap tệ và rằng một hệ thống bố cục kiểu đặc điểm được thực hiện tốt sẽ thực sự mạnh mẽ / hữu ích ... nhưng có rất nhiều cách để nó có thể được thực hiện một cách tồi tệ, và rất nhiều lý do không phải là một ý tưởng tốt trong một ngôn ngữ như C ++.

[sửa] về ví dụ của bạn, đó là điều vô lý. Một chiếc Kia thiết bị điện tử. Nó một động cơ. Tương tự như vậy, thiết bị điện tử của nó một nguồn năng lượng, mà chỉ là pin xe hơi. Kế thừa, huống chi nhiều thừa kế không có chỗ ở đó.


8
Một câu hỏi thú vị khác là làm thế nào để các lớp cơ sở + các lớp cơ sở của chúng được khởi tạo. Đóng gói> Kế thừa.
jozefg

"Một hệ thống thành phần phong cách đặc điểm được thực hiện tốt sẽ thực sự mạnh mẽ / hữu ích ..." - Chúng được gọi là mixins , và chúng rất mạnh mẽ / hữu ích. Mixins có thể được thực hiện bằng MI, nhưng không cần nhiều kế thừa cho mixins. Một số ngôn ngữ hỗ trợ mixin vốn có, không có MI.
BlueRaja - Daniel Pflughoeft

Đối với Java nói riêng, chúng tôi đã có nhiều giao diện và HÓA ĐƠN, vì vậy MI sẽ không có bất kỳ ý nghĩa hiệu suất rõ ràng nào.
Daniel Lubarov

Tetastyn, tôi đồng ý rằng ví dụ này rất tệ và không phục vụ câu hỏi của anh ấy. Kế thừa không dành cho tính từ (điện tử), nó dành cho danh từ (phương tiện).
Richard

29

Lý do duy nhất tại sao nó không được phép là vì nó giúp mọi người dễ dàng tự bắn vào chân mình.

Điều thường xảy ra trong cuộc thảo luận này là các cuộc tranh luận về việc liệu tính linh hoạt của việc có các công cụ này quan trọng hơn sự an toàn của việc không bắn vào chân bạn. Không có câu trả lời đúng quyết định cho lập luận đó, bởi vì giống như hầu hết những thứ khác trong lập trình, câu trả lời phụ thuộc vào ngữ cảnh.

Nếu các nhà phát triển của bạn cảm thấy thoải mái với MI và MI có ý nghĩa trong bối cảnh những gì bạn đang làm, thì bạn sẽ rất nhớ nó trong một ngôn ngữ không hỗ trợ nó. Đồng thời nếu nhóm không thoải mái với nó hoặc không có nhu cầu thực sự cho nó và mọi người sử dụng nó 'chỉ vì họ có thể', thì điều đó là phản tác dụng.

Nhưng không, không tồn tại một lý lẽ hoàn toàn thuyết phục hoàn toàn thuyết phục chứng minh nhiều kế thừa là một ý tưởng tồi.

BIÊN TẬP

Câu trả lời cho câu hỏi này dường như được nhất trí. Vì lợi ích của việc trở thành người ủng hộ của quỷ, tôi sẽ cung cấp một ví dụ điển hình về việc thừa kế nhiều lần, khi không thực hiện nó sẽ dẫn đến hack.

Giả sử bạn đang thiết kế một ứng dụng thị trường vốn. Bạn cần một mô hình dữ liệu cho chứng khoán. Một số chứng khoán là sản phẩm vốn cổ phần (cổ phiếu, ủy thác đầu tư bất động sản, v.v.) một số khác là nợ (trái phiếu, trái phiếu doanh nghiệp), một số khác là các công cụ phái sinh (quyền chọn, tương lai). Vì vậy, nếu bạn đang tránh MI, bạn sẽ tạo một cây thừa kế đơn giản, rõ ràng. Một cổ phiếu sẽ kế thừa Vốn chủ sở hữu, Trái phiếu sẽ kế thừa Nợ. Tuyệt vời cho đến nay, nhưng những gì về dẫn xuất? Họ có thể dựa trên các sản phẩm giống như Vốn chủ sở hữu hoặc các sản phẩm giống như Ghi nợ? Ok, tôi đoán chúng ta sẽ làm cho nhánh cây thừa kế của chúng ta ra nhiều hơn. Hãy nhớ rằng, một số công cụ phái sinh dựa trên các sản phẩm vốn chủ sở hữu, sản phẩm nợ hoặc không. Vì vậy, cây thừa kế của chúng tôi đang trở nên phức tạp. Sau đó, đến nhà phân tích kinh doanh và nói với bạn rằng bây giờ chúng tôi hỗ trợ chứng khoán được lập chỉ mục (tùy chọn chỉ mục, tùy chọn chỉ mục trong tương lai). Và những điều này có thể được dựa trên Vốn chủ sở hữu, Nợ hoặc phái sinh. Điều này đang trở nên lộn xộn! Có phải tùy chọn trong tương lai của tôi lấy được Equity-> Stock-> Option-> Index không? Tại sao không phải Vốn chủ sở hữu-> Cổ phiếu-> Chỉ mục-> Tùy chọn? Điều gì sẽ xảy ra nếu một ngày tôi tìm thấy cả hai trong mã của mình (Điều này đã xảy ra; câu chuyện có thật)?

Vấn đề ở đây là các loại cơ bản này có thể được trộn lẫn trong bất kỳ hoán vị nào không tự nhiên lấy được loại này từ loại kia. Các đối tượng được xác định bởi một là một mối quan hệ, vì vậy thành phần không có ý nghĩa gì. Đa kế thừa (hoặc khái niệm tương tự của mixins) là biểu diễn logic duy nhất ở đây.

Giải pháp thực sự cho vấn đề này là có các loại Vốn chủ sở hữu, Nợ, phái sinh, Chỉ mục được xác định và trộn lẫn bằng cách sử dụng nhiều kế thừa để tạo mô hình dữ liệu của bạn. Điều này sẽ tạo ra các đối tượng mà cả hai, có ý nghĩa và dễ dàng cho vay để sử dụng lại mã.


27
Tôi đã làm việc trong lĩnh vực tài chính. Có một lý do tại sao lập trình chức năng được ưa thích hơn OO trong phần mềm tài chính. Và bạn đã chỉ ra điều đó. MI không khắc phục vấn đề này mà nó làm trầm trọng thêm. Hãy tin tôi, tôi không thể nói với bạn bao nhiêu lần tôi đã nghe "Nó giống như thế này ... ngoại trừ" khi giao dịch với khách hàng tài chính Điều đó ngoại trừ trở thành nguyên nhân tồn tại của tôi.
Michael Brown

8
Điều này có vẻ rất thể giải quyết được với các giao diện với tôi ... trong thực tế, có vẻ như tốt hơn phù hợp với giao diện hơn bất cứ điều gì khác. EquityDebtcả hai thực hiện ISecurity. Derivativecó một ISecuritytài sản. Nó có thể là một ISecuritynếu thích hợp (tôi không biết tài chính). IndexedSecuritiesmột lần nữa chứa một thuộc tính giao diện được áp dụng cho các loại mà nó được phép dựa trên. Nếu chúng là tất cả ISecurity, thì tất cả chúng đều có ISecuritythuộc tính và có thể được lồng tùy ý ...
Bobson

11
@Bobson vấn đề xuất phát ở chỗ bạn phải triển khai lại giao diện cho từng người triển khai và trong nhiều trường hợp, nó giống nhau. Bạn có thể sử dụng ủy quyền / thành phần nhưng sau đó bạn mất quyền truy cập vào các thành viên tư nhân. Và vì Java không có đại biểu / lambdas ... nên thực sự không có cách nào tốt để làm điều đó. Ngoại trừ có thể với một công cụ Aspect như Aspect4J
Michael Brown

10
@Bobson chính xác những gì Mike Brown nói. Vâng, bạn có thể thiết kế một giải pháp với các giao diện, nhưng nó sẽ rất cồng kềnh. Trực giác của bạn để sử dụng các giao diện là rất chính xác, đó là một mong muốn tiềm ẩn cho mixins / nhiều kế thừa :).
MrFox

11
Điều này chạm đến một sự kỳ quặc mà các lập trình viên OO thích nghĩ về sự kế thừa và không chấp nhận ủy quyền như một cách tiếp cận OO hợp lệ, một phương pháp thường mang lại một giải pháp mở rộng hơn, dễ bảo trì hơn và có thể mở rộng hơn. Làm việc trong lĩnh vực tài chính và chăm sóc sức khỏe Tôi đã thấy MI lộn xộn không thể quản lý có thể tạo ra, đặc biệt là luật thuế và y tế thay đổi qua từng năm và định nghĩa về một đối tượng LAST năm không hợp lệ trong năm NÀY (vẫn phải thực hiện chức năng của một trong hai năm và phải được hoán đổi cho nhau). Thành phần mang lại mã nạc dễ kiểm tra hơn và chi phí ít hơn theo thời gian.
Shaun Wilson

11

Các câu trả lời khác ở đây dường như chủ yếu đi vào lý thuyết. Vì vậy, đây là một ví dụ Python cụ thể, được đơn giản hóa, mà tôi thực sự đã đập đầu vào, đòi hỏi một số lượng lớn tái cấu trúc:

class Foo(object):
   def zeta(self):
      print "foozeta"

class Bar(object):
   def zeta(self):
      print "barzeta"

   def barstuff(self):
      print "barstuff"
      self.zeta()

class Bang(Foo, Bar):
   def stuff(self):
      self.zeta()
      print "---"
      self.barstuff()

z = Bang()
z.stuff()

Barđã được viết giả định rằng nó đã thực hiện riêng của nó zeta(), mà nói chung là một giả định khá tốt. Một lớp con nên ghi đè lên nó khi thích hợp để nó thực hiện đúng. Thật không may, các tên chỉ trùng hợp ngẫu nhiên - chúng đã làm những việc khác nhau, nhưng Barhiện đang kêu gọi Foothực hiện:

foozeta
---
barstuff
foozeta

Thật là bực bội khi không có lỗi nào được đưa ra, ứng dụng bắt đầu hoạt động hơi sai và mã thay đổi gây ra nó (tạo ra Bar.zeta) dường như không phải là vấn đề.


Làm thế nào là vấn đề kim cương tránh được thông qua các cuộc gọi đến super()?
Panzercrisis

@Panzercrisis Xin lỗi, đừng bận tâm đến phần đó. Tôi đã đánh giá sai vấn đề mà "vấn đề kim cương" thường đề cập đến - Python có một vấn đề riêng gây ra bởi sự thừa kế hình kim cương xảy ra super()xung quanh
Izkata

5
Tôi rất vui vì MI của C ++ được thiết kế tốt hơn nhiều. Các lỗi trên chỉ đơn giản là không thể. Bạn phải định hướng thủ công nó trước khi mã thậm chí sẽ biên dịch.
Thomas Eding

2
Thay đổi thứ tự thừa kế Bangthành Bar, Foocũng sẽ khắc phục vấn đề - nhưng quan điểm của bạn là một điểm tốt, +1.
Sean Vieira

1
@ThomasEding Bạn có thể định hướng thủ công tĩnh : Bar.zeta(self).
Tyler Crompton

9

Tôi sẽ lập luận rằng không có bất kỳ vấn đề thực sự nào với MI theo đúng ngôn ngữ . Điều quan trọng là cho phép các cấu trúc kim cương, nhưng yêu cầu các kiểu con cung cấp ghi đè riêng của chúng, thay vì trình biên dịch chọn một trong các triển khai dựa trên một số quy tắc.

Tôi làm điều này ở Guava , một ngôn ngữ tôi đang làm việc. Một đặc điểm của Guava là chúng ta có thể yêu cầu thực hiện một phương thức siêu kiểu cụ thể. Vì vậy, thật dễ dàng để chỉ ra việc triển khai siêu kiểu nào sẽ được "kế thừa", mà không có bất kỳ cú pháp đặc biệt nào:

type Sequence[+A] {
  String toString() {
    return "[" + ... + "]";
  }
}

type Set[+A] {
  String toString() {
    return "{" + ... + "}";
  }
}

type OrderedSet[+A] extends Sequence[A], Set[A] {
  String toString() {
    // This is Guava's syntax for statically invoking instance methods
    return Set.toString(this);
  }
}

Nếu chúng tôi không cho OrderedSetriêng mình toString, chúng tôi sẽ nhận được một lỗi biên dịch. Không có bất ngờ.

Tôi thấy MI đặc biệt hữu ích với các bộ sưu tập. Ví dụ, tôi thích sử dụng một RandomlyEnumerableSequenceloại để tránh khai báo getEnumeratorcho mảng, deques, v.v.

type Enumerable[+A] {
  Source[A] getEnumerator();
}

type Sequence[+A] extends Enumerable[A] {
  A get(Int index);
}

type RandomlyEnumerableSequence[+A] extends Sequence[A] {
  Source[A] getEnumerator() {
    ...
  }
}

type DynamicArray[A] extends MutableStack[A],
                             RandomlyEnumerableSequence[A] {
  // No need to define getEnumerator.
}

Nếu chúng tôi không có MI, chúng tôi có thể viết một RandomAccessEnumeratorvài bộ sưu tập để sử dụng, nhưng phải viết một getEnumeratorphương pháp ngắn gọn vẫn thêm phần soạn sẵn.

Tương tự như vậy, MI là hữu ích cho kế thừa triển khai tiêu chuẩn equals, hashCodetoStringcác bộ sưu tập.


1
@AndyzSmith, tôi thấy quan điểm của bạn, nhưng không phải tất cả các xung đột thừa kế đều là vấn đề ngữ nghĩa thực tế. Hãy xem xét ví dụ của tôi - không có loại nào hứa hẹn về toStringhành vi của họ, vì vậy việc ghi đè hành vi của nó không vi phạm nguyên tắc thay thế. Đôi khi bạn cũng nhận được "xung đột" giữa các phương thức có cùng hành vi nhưng thuật toán khác nhau, đặc biệt là với các bộ sưu tập.
Daniel Lubarov

1
@Asmageddon đồng ý, ví dụ của tôi chỉ liên quan đến chức năng. Bạn thấy lợi thế của mixins là gì? Ít nhất là trong Scala, các đặc điểm về cơ bản chỉ là các lớp trừu tượng cho phép MI - chúng vẫn có thể được sử dụng để nhận dạng và vẫn mang lại vấn đề về kim cương. Có ngôn ngữ nào khác mà mixin có sự khác biệt thú vị hơn không?
Daniel Lubarov

1
Đối với tôi, các lớp trừu tượng về mặt kỹ thuật có thể được sử dụng như các giao diện, lợi ích đơn giản là thực tế của chính cấu trúc là một "mẹo sử dụng", nghĩa là tôi biết những gì sẽ xảy ra khi tôi nhìn thấy mã. Điều đó nói rằng, tôi hiện đang mã hóa trong Lua, không có mô hình OOP vốn có - các hệ thống lớp hiện có thường cho phép bao gồm các bảng dưới dạng mixin và hệ thống tôi đang mã hóa sử dụng các lớp làm mixin. Sự khác biệt duy nhất là bạn chỉ có thể sử dụng kế thừa (đơn) cho danh tính, mixins cho chức năng (không có thông tin nhận dạng) và giao diện để kiểm tra thêm. Có thể nó không bằng cách nào đó tốt hơn, nhưng nó có ý nghĩa với tôi.
Llamageddon

2
Tại sao bạn gọi là Ordered extends Set and Sequencea Diamond? Nó chỉ là một tham gia. Nó thiếu hatđỉnh. Tại sao bạn gọi nó là kim cương? Tôi hỏi ở đây nhưng có vẻ như một câu hỏi cấm kỵ. Làm thế nào bạn biết rằng bạn cần gọi cấu trúc tam giác này là Kim cương chứ không phải Tham gia?
Val

1
@RecognizeEvilasWaste rằng ngôn ngữ có một Toploại ngầm định khai báo toString, vì vậy thực sự có cấu trúc kim cương. Nhưng tôi nghĩ rằng bạn có một điểm - "tham gia" mà không có cấu trúc kim cương sẽ tạo ra một vấn đề tương tự và hầu hết các ngôn ngữ đều xử lý cả hai trường hợp theo cùng một cách.
Daniel Lubarov

7

Kế thừa, nhiều hay không, không quan trọng. Nếu hai đối tượng thuộc loại khác nhau có thể thay thế, đó là điều quan trọng, ngay cả khi chúng không được liên kết bởi sự kế thừa.

Một danh sách được liên kết và một chuỗi ký tự có ít điểm chung và không cần phải được liên kết bởi sự kế thừa, nhưng thật hữu ích nếu tôi có thể sử dụng một lengthhàm để lấy số lượng phần tử trong một.

Kế thừa là một mẹo để tránh việc thực thi mã lặp đi lặp lại. Nếu thừa kế giúp bạn tiết kiệm công việc và nhiều kế thừa giúp bạn tiết kiệm được nhiều công việc hơn so với thừa kế đơn lẻ, thì đó là tất cả những lý do cần thiết.

Tôi nghi ngờ rằng một số ngôn ngữ không thực hiện nhiều kế thừa rất tốt, và đối với những người thực hành các ngôn ngữ đó, đó là ý nghĩa của nhiều kế thừa. Đề cập nhiều kế thừa cho một lập trình viên C ++ và điều gây chú ý là vấn đề khi một lớp kết thúc với hai bản sao của một cơ sở thông qua hai đường dẫn thừa kế khác nhau và liệu có nên sử dụng virtualtrên lớp cơ sở hay không và có nhầm lẫn về cách gọi các hàm hủy không , v.v.

Trong nhiều ngôn ngữ, sự kế thừa của lớp được kết hợp với sự kế thừa các ký hiệu. Khi bạn lấy được một lớp D từ một lớp B, bạn không chỉ tạo mối quan hệ kiểu mà bởi vì các lớp này cũng đóng vai trò là không gian tên từ vựng, bạn đang xử lý việc nhập các ký hiệu từ không gian tên B vào không gian tên D, ngoài ra ngữ nghĩa của những gì đang xảy ra với chính loại B và D. Do đó, nhiều kế thừa mang lại các vấn đề về xung đột biểu tượng. Nếu chúng ta kế thừa từ card_deckgraphiccả hai drawphương thức "có" , thì nó có ý nghĩa gì drawvới đối tượng kết quả? Một hệ thống đối tượng không có vấn đề này là hệ thống trong Common Lisp. Có lẽ không phải ngẫu nhiên, nhiều kế thừa được sử dụng trong các chương trình Lisp.

Thực hiện kém, bất tiện bất cứ điều gì (chẳng hạn như nhiều thừa kế) nên bị ghét.


2
Ví dụ của bạn với phương thức length () của danh sách và chuỗi chứa là xấu, vì hai cái này đang làm những việc hoàn toàn khác nhau. Mục đích của kế thừa không phải là để giảm sự lặp lại mã. Nếu bạn muốn giảm sự lặp lại mã, bạn cấu trúc lại các phần chung thành một lớp mới.
BЈовић

1
@ BЈовић Cả hai không làm những việc "hoàn toàn" khác nhau chút nào.
Kaz

1
Kết hợp chuỗidanh sách , người ta có thể thấy rất nhiều sự lặp lại. Sử dụng kế thừa để thực hiện các phương pháp phổ biến này đơn giản là sai.
BЈовић

1
@ BЈовић Trong câu trả lời không nói rằng một chuỗi và danh sách nên được liên kết bằng kế thừa; hoàn toàn ngược lại. Hành vi có thể thay thế danh sách hoặc chuỗi trong lengthhoạt động là hữu ích độc lập với khái niệm thừa kế (trong trường hợp này không giúp ích gì: chúng tôi không có khả năng đạt được bất cứ điều gì bằng cách cố gắng chia sẻ việc thực hiện giữa hai phương thức của lengthhàm). Tuy nhiên, có thể có một số kế thừa trừu tượng: ví dụ: cả danh sách và chuỗi đều thuộc chuỗi loại (nhưng không cung cấp bất kỳ triển khai nào).
Kaz

2
Ngoài ra, thừa kế một cách sắp xếp các bộ phận để một lớp thông thường: cụ thể là lớp cơ sở.
Kaz

1

Theo như tôi có thể nói, một phần của vấn đề (ngoài việc làm cho thiết kế của bạn khó hiểu hơn một chút (tuy nhiên dễ viết mã hơn)) là trình biên dịch sẽ tiết kiệm đủ không gian cho dữ liệu lớp của bạn, cho phép số lượng lớn này lãng phí bộ nhớ trong trường hợp sau:

(Ví dụ của tôi có thể không phải là tốt nhất, nhưng hãy thử lấy ý chính về nhiều không gian bộ nhớ cho cùng một mục đích, đó là điều đầu tiên xuất hiện trong đầu tôi: P)

Quan tâm đến một DDD trong đó con chó lớp kéo dài từ caninus và thú cưng, một caninus có một biến chỉ ra lượng thức ăn mà nó nên ăn (một số nguyên) dưới tên dietKg, nhưng thú cưng cũng có một biến khác cho mục đích đó thường theo cùng một mục đích tên (trừ khi bạn đặt tên biến khác, sau đó bạn sẽ mã hóa thêm mã, đây là vấn đề ban đầu mà bạn muốn tránh, để xử lý và giữ tính toàn vẹn của các biến bouth), thì chính xác bạn sẽ có hai không gian bộ nhớ Cùng một mục đích, để tránh điều này, bạn sẽ phải sửa đổi trình biên dịch của mình để nhận ra tên đó trong cùng một không gian tên và chỉ gán một không gian bộ nhớ duy nhất cho dữ liệu đó, điều không may là có thể xác định được trong thời gian biên dịch.

Tất nhiên, bạn có thể thiết kế một languaje để xác định rằng biến đó có thể đã có một không gian được xác định ở một nơi khác, nhưng cuối cùng, lập trình viên nên xác định không gian bộ nhớ mà biến này đang tham chiếu ở đâu (và lại thêm mã).

Tin tôi đi, những người thực hiện suy nghĩ này thực sự khó khăn về tất cả những điều này, nhưng tôi rất vui vì bạn đã hỏi, quan điểm tử tế của bạn là điều thay đổi mô hình;), và nói về điều này, tôi không nói là không thể (nhưng nhiều giả thuyết và một trình biên dịch đa biến phải được triển khai và một trình biên dịch thực sự phức tạp), tôi chỉ nói rằng nó chưa tồn tại, nếu bạn bắt đầu một dự án cho trình biên dịch của riêng bạn có khả năng thực hiện "điều này" (nhiều kế thừa), hãy cho tôi biết, tôi Sẽ vui mừng được tham gia vào nhóm của bạn.


1
Tại thời điểm nào một trình biên dịch có thể giả định rằng các biến có cùng tên từ các lớp cha khác nhau có an toàn để kết hợp không? Điều gì đảm bảo cho bạn rằng caninus.diet và pet.diet thực sự phục vụ cùng một mục đích? Bạn sẽ làm gì với hàm / phương thức có cùng tên?
Mr.Mindor

hahaha Tôi hoàn toàn đồng ý với bạn @ Mr.Mindor đó chính xác là những gì tôi đang nói trong câu trả lời của mình, cách duy nhất tôi nghĩ đến gần đó là lập trình định hướng nguyên mẫu, nhưng tôi không nói là không thể , và đó chính xác là lý do tại sao tôi nói rằng nhiều giả định phải được thực hiện trong thời gian biên dịch và một số "dữ liệu" / thông tin bổ sung phải được viết bởi lập trình viên (tuy nhiên đó là mã hóa nhiều hơn, đó là vấn đề ban đầu)
Ordiel

-1

Trong một thời gian khá lâu, nó chưa bao giờ thực sự xảy ra với tôi một số nhiệm vụ lập trình hoàn toàn khác với các nhiệm vụ khác - và nó giúp ích được bao nhiêu nếu ngôn ngữ và các mẫu được sử dụng phù hợp với không gian vấn đề.

Khi bạn đang làm việc một mình hoặc chủ yếu bị cô lập về mã mà bạn đã viết, đó là một không gian vấn đề hoàn toàn khác với việc thừa kế một cơ sở mã từ 40 người ở Ấn Độ, người đã làm việc với nó trong một năm trước khi giao nó cho bạn mà không cần bất kỳ sự trợ giúp chuyển tiếp nào.

Hãy tưởng tượng bạn chỉ được thuê bởi công ty mơ ước của bạn và sau đó được thừa hưởng một cơ sở mã như vậy. Hơn nữa, hãy tưởng tượng rằng các chuyên gia tư vấn đã học về (và do đó say mê) sự kế thừa và đa thừa kế ... Bạn có thể hình dung những gì bạn có thể đang làm việc không.

Khi bạn kế thừa mã, tính năng quan trọng nhất là nó có thể hiểu được và các phần được tách ra để chúng có thể được làm việc độc lập. Chắc chắn khi bạn lần đầu tiên viết các cấu trúc mã như nhiều kế thừa có thể giúp bạn tiết kiệm được một chút trùng lặp và có vẻ phù hợp với tâm trạng logic của bạn vào thời điểm đó, nhưng anh chàng tiếp theo chỉ có nhiều thứ để gỡ rối.

Mọi kết nối trong mã của bạn cũng làm cho việc hiểu và sửa đổi các phần độc lập trở nên khó khăn hơn, gấp đôi với nhiều kế thừa.

Khi bạn đang làm việc như một phần của nhóm, bạn muốn nhắm mục tiêu mã đơn giản nhất có thể mang lại cho bạn hoàn toàn không có logic thừa (Đó là ý nghĩa thực sự của DRY, không phải là bạn không nên nhập nhiều mã mà bạn không bao giờ phải thay đổi mã của mình trong 2 nơi để giải quyết vấn đề!)

Có nhiều cách đơn giản hơn để đạt được mã DRY so với Đa kế thừa, do đó, việc đưa nó vào một ngôn ngữ chỉ có thể mở ra cho bạn các vấn đề được chèn bởi những người khác có thể không ở cùng mức độ hiểu biết như bạn. Nó thậm chí chỉ hấp dẫn nếu ngôn ngữ của bạn không có khả năng cung cấp cho bạn một cách đơn giản / ít phức tạp hơn để giữ mã DRY của bạn.


Hơn nữa, hãy tưởng tượng rằng các chuyên gia tư vấn đã tìm hiểu về - tôi đã thấy rất nhiều ngôn ngữ không phải MI (ví dụ .net) trong đó các chuyên gia tư vấn phát điên với DI và IoC dựa trên giao diện để biến toàn bộ sự lộn xộn. MI không làm cho điều này tồi tệ hơn, có lẽ làm cho nó tốt hơn.
gbjbaanb

@gbjbaanb Bạn nói đúng, thật dễ dàng để một người chưa bị đốt cháy làm hỏng bất kỳ tính năng nào - thực tế đó gần như là một yêu cầu để tìm hiểu một tính năng mà bạn làm hỏng nó để xem cách sử dụng nó tốt nhất. Tôi hơi tò mò vì dường như bạn đang nói rằng không có MI buộc thêm DI / IoC mà tôi chưa từng thấy - Tôi sẽ không nghĩ rằng mã tư vấn mà bạn có với tất cả các giao diện tào lao / DI sẽ là tốt hơn là có cả một cây thừa kế nhiều-nhiều-điên, nhưng đó có thể là sự thiếu kinh nghiệm của tôi với MI. Bạn có trang nào tôi có thể đọc để thay thế DI / IoC bằng MI không?
Bill K

-3

Lập luận lớn nhất chống lại nhiều kế thừa là một số khả năng hữu ích có thể được cung cấp và một số tiên đề hữu ích có thể nắm giữ, trong một khuôn khổ hạn chế nghiêm ngặt nó (*), nhưng không thể được cung cấp và / hoặc không có những hạn chế đó. Trong số đó:

  • Khả năng có nhiều mô-đun được biên dịch riêng biệt bao gồm các lớp kế thừa từ các lớp của mô-đun khác và biên dịch lại một mô-đun chứa một lớp cơ sở mà không phải biên dịch lại mọi mô-đun kế thừa từ lớp cơ sở đó.

  • Khả năng cho một kiểu kế thừa một triển khai thành viên từ lớp cha mà không có kiểu dẫn xuất phải thực hiện lại nó

  • Tiên đề rằng bất kỳ trường hợp đối tượng nào cũng có thể bị u ám hoặc hạ trực tiếp lên chính nó hoặc bất kỳ loại cơ sở nào của nó, và các phát sóng và phát sóng đó, trong bất kỳ kết hợp hoặc chuỗi nào, luôn được giữ nguyên danh tính

  • Tiên đề rằng nếu một lớp dẫn xuất ghi đè và chuỗi thành viên của lớp cơ sở, thì thành viên cơ sở sẽ được gọi trực tiếp bằng mã chuỗi và sẽ không được gọi ở bất kỳ nơi nào khác.

(*) Thông thường bằng cách yêu cầu các loại hỗ trợ nhiều kế thừa được khai báo là "giao diện" thay vì các lớp và không cho phép các giao diện thực hiện mọi thứ mà các lớp bình thường có thể làm.

Nếu một người muốn cho phép tổng quát hóa nhiều kế thừa, một cái gì đó khác phải nhường đường. Nếu X và Y đều thừa hưởng từ B, cả hai đều ghi đè cùng một thành viên M và chuỗi vào triển khai cơ sở và nếu D thừa hưởng từ X và Y nhưng không ghi đè lên M, thì sẽ đưa ra một thể hiện q loại D, nên ( B) q) .M () làm gì? Không cho phép một diễn viên như vậy sẽ vi phạm tiên đề nói rằng bất kỳ đối tượng nào cũng có thể bị u ám với bất kỳ loại cơ sở nào, nhưng bất kỳ hành vi có thể nào của diễn viên và lời mời thành viên sẽ vi phạm tiên đề về xâu chuỗi phương pháp. Người ta có thể yêu cầu các lớp chỉ được tải kết hợp với phiên bản cụ thể của lớp cơ sở mà chúng được biên dịch, nhưng điều đó thường rất khó xử. Có thời gian chạy từ chối tải bất kỳ loại nào trong đó bất kỳ tổ tiên nào có thể đạt được bằng nhiều hơn một tuyến đường có thể hoạt động được, nhưng sẽ hạn chế rất nhiều tính hữu ích của nhiều kế thừa. Chỉ cho phép các đường dẫn thừa kế được chia sẻ khi không có xung đột sẽ tạo ra tình huống trong đó phiên bản X cũ sẽ tương thích với Y cũ hoặc mới, và Y cũ sẽ tương thích với X cũ hoặc mới, nhưng X mới và Y mới tương thích, ngay cả khi không làm bất cứ điều gì mà bản thân nó sẽ được coi là một sự thay đổi đột phá.

Một số ngôn ngữ và khung cho phép nhiều sự kế thừa, theo lý thuyết rằng những gì thu được từ MI quan trọng hơn những gì phải từ bỏ để cho phép nó. Tuy nhiên, chi phí của MI rất đáng kể và trong nhiều trường hợp, giao diện cung cấp 90% lợi ích của MI với một phần nhỏ chi phí.


Cử tri xuống sẽ yêu cầu bình luận? Tôi tin rằng câu trả lời của tôi cung cấp thông tin không có trong các ngôn ngữ khác, liên quan đến lợi thế ngữ nghĩa có thể nhận được bằng cách không cho phép thừa kế nhiều. Việc cho phép nhiều thừa kế sẽ yêu cầu từ bỏ những thứ hữu ích khác sẽ là lý do chính đáng cho bất kỳ ai coi trọng những thứ khác hơn là thừa kế để chống lại MI.
supercat

2
Các vấn đề với MI có thể dễ dàng được giải quyết hoặc tránh hoàn toàn bằng cách sử dụng các kỹ thuật tương tự mà bạn sử dụng với một kế thừa duy nhất. Không có khả năng / tiên đề nào mà bạn đề cập vốn bị cản trở bởi MI ngoại trừ thứ hai ... và nếu bạn thừa hưởng từ hai lớp cung cấp các hành vi khác nhau cho cùng một phương thức, thì dù sao bạn cũng sẽ giải quyết được sự mơ hồ đó. Nhưng nếu bạn thấy mình phải làm điều đó, có lẽ bạn đã lạm dụng quyền thừa kế.
cHao

@cHao: Nếu một người yêu cầu các lớp dẫn xuất phải ghi đè các phương thức cha mẹ và không xâu chuỗi chúng (cùng quy tắc với giao diện), người ta có thể tránh được các vấn đề, nhưng đó là một hạn chế khá nghiêm trọng. Nếu một người sử dụng một mẫu mà FirstDerivedFooghi đè của Barkhông làm gì ngoài việc xâu chuỗi thành một chuỗi không ảo được bảo vệ FirstDerivedFoo_BarSecondDerivedFoocác chuỗi ghi đè không được bảo vệ SecondDerivedBarvà nếu mã muốn truy cập phương thức cơ sở thì sử dụng các phương thức không ảo được bảo vệ đó, đó là có thể là một cách tiếp cận khả thi ...
supercat

... (tránh sử dụng cú pháp base.VirtualMethod) nhưng tôi không biết về bất kỳ sự hỗ trợ ngôn ngữ nào để đặc biệt tạo điều kiện cho cách tiếp cận như vậy. Tôi ước có một cách rõ ràng để đính kèm một khối mã vào cả phương thức được bảo vệ không ảo và phương thức ảo có cùng chữ ký mà không yêu cầu phương thức ảo "một lớp" (kết thúc bằng nhiều hơn một dòng trong mã nguồn).
supercat

Không có yêu cầu cố hữu trong MI mà các lớp không gọi các phương thức cơ sở và không cần một lý do cụ thể nào. Có vẻ hơi kỳ quặc khi đổ lỗi cho MI, vì vấn đề đó cũng ảnh hưởng đến SI.
cHao
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.