Câu hỏi Thích sáng tác hơn thừa kế?


Tại sao thích thành phần hơn thừa kế? Có sự cân bằng nào đối với mỗi cách tiếp cận? Khi nào bạn nên chọn thừa kế trên bố cục?


1349
2017-09-08 01:58


gốc


Xem thêm thiết kế lớp nào tốt hơn - maccullt
Có một bài viết hay về câu hỏi đó đây. Ý kiến ​​cá nhân của tôi là không có nguyên tắc "tốt hơn" hoặc "tệ hơn" để thiết kế. Có thiết kế "thích hợp" và "không đầy đủ" cho công việc cụ thể. Nói cách khác - tôi sử dụng cả thừa kế hoặc bố cục, tùy thuộc vào tình huống. Mục tiêu là tạo ra mã nhỏ hơn, dễ đọc hơn, tái sử dụng và cuối cùng mở rộng hơn nữa. - m_pGladiator
trong một câu thừa kế là công khai nếu bạn có một phương thức công khai và bạn thay đổi nó, nó thay đổi api đã xuất bản. nếu bạn có bố cục và đối tượng sáng tác đã thay đổi, bạn không phải thay đổi api đã xuất bản của mình. - Tomer Ben David
Trên Wikipedia xem Thành phần trên thừa kế. - DavidRR


Các câu trả lời:


Ưu tiên thành phần trên thừa kế vì nó dễ uốn cong hơn / dễ sửa đổi sau, nhưng không sử dụng phương pháp soạn thư luôn. Với bố cục, thật dễ dàng để thay đổi hành vi khi đang bay với Dependency Injection / Setters. Thừa kế là cứng nhắc hơn vì hầu hết các ngôn ngữ không cho phép bạn lấy được từ nhiều loại. Vì vậy, ngỗng là nhiều hơn hoặc ít nấu chín một khi bạn lấy được từ TypeA.

Xét nghiệm axit của tôi cho những điều trên là:

  • Liệu TypeB có muốn hiển thị giao diện hoàn chỉnh (tất cả các phương thức công khai không kém) của TypeA sao cho TypeB có thể được sử dụng khi TypeA được mong đợi? Chỉ định Di sản.

ví dụ. Một cánh kép Cessna sẽ lộ ra giao diện hoàn chỉnh của một chiếc máy bay, nếu không nhiều hơn. Vì vậy, làm cho nó phù hợp để lấy được từ máy bay.

  • Liệu TypeB chỉ muốn một số / một phần của hành vi tiếp xúc bởi TypeA? Cho biết cần Thành phần. 

ví dụ. Một con chim có thể chỉ cần hành vi bay của một chiếc máy bay. Trong trường hợp này, nó có ý nghĩa để giải nén nó ra như một giao diện / lớp / cả hai và làm cho nó trở thành một thành viên của cả hai lớp.

Cập nhật: Chỉ cần quay trở lại câu trả lời của tôi và có vẻ như bây giờ nó chưa hoàn chỉnh mà không đề cập cụ thể đến Barbara Liskov Nguyên lý thay thế Liskov làm thử nghiệm cho 'Tôi có nên kế thừa từ loại này không?'


1013
2017-09-10 03:04



Ví dụ thứ hai là thẳng ra khỏi các mẫu thiết kế đầu tiên (amazon.com/First-Design-Patterns-Elisabeth-Freeman/dp/…) cuốn sách :) Tôi rất muốn giới thiệu cuốn sách đó cho bất kỳ ai googling câu hỏi này. - Jeshurun
Nó rất rõ ràng, nhưng nó có thể bỏ lỡ điều gì đó: "Liệu TypeB có muốn phơi bày giao diện hoàn chỉnh (tất cả các phương thức công khai không kém) của TypeA để TypeB có thể được sử dụng khi TypeA được mong đợi không?" Nhưng nếu điều này là đúng, và TypeB cũng phơi bày giao diện hoàn chỉnh của TypeC? Và nếu TypeC chưa được mô hình hóa thì sao? - Tristan
Bạn ám chỉ đến những gì tôi nghĩ là thử nghiệm cơ bản nhất: "Liệu đối tượng này có thể sử dụng được bằng mã mà hy vọng các đối tượng của (cái gì sẽ là) kiểu cơ sở". Nếu câu trả lời là có, đối tượng phải thừa kế. Nếu không, thì có lẽ không nên. Nếu tôi có druthers của tôi, ngôn ngữ sẽ cung cấp một từ khóa để tham khảo "lớp này", và cung cấp một phương tiện xác định một lớp mà nên cư xử giống như một lớp khác, nhưng không thể thay thế cho nó (một lớp như vậy sẽ có tất cả "điều này lớp "tham chiếu được thay thế bằng chính nó). - supercat
@ Alexey - vấn đề là 'Liệu tôi có thể vượt qua một cánh kép Cessna cho tất cả các khách hàng mong đợi một chiếc máy bay mà không làm họ ngạc nhiên?'. Nếu có, thì rất có thể là bạn muốn thừa kế. - Gishu
Tôi đang thực sự đấu tranh để suy nghĩ về bất kỳ ví dụ mà thừa kế sẽ có được câu trả lời của tôi, tôi thường tìm thấy tập hợp, thành phần và giao diện kết quả trong các giải pháp thanh lịch hơn. Nhiều ví dụ trên có thể được giải thích tốt hơn bằng cách sử dụng các phương pháp tiếp cận đó ... - Stuart Wakefield


Nghĩ về ngăn chặn như một có một mối quan hệ. Một chiếc xe "có một" động cơ, một người "có một" tên, vv

Hãy suy nghĩ về sự kế thừa như một là một mối quan hệ. Một chiếc xe "là một" chiếc xe, một người "là một" động vật có vú, vv

Tôi không có tín dụng cho phương pháp này. Tôi lấy nó trực tiếp từ Phiên bản thứ hai của mã hoàn thành bởi Steve McConnell, Phần 6.3.


349
2017-09-08 02:09



Đây không phải luôn luôn là một cách tiếp cận hoàn hảo, nó chỉ đơn giản là một hướng dẫn tốt. Nguyên tắc thay thế Liskov chính xác hơn nhiều (thất bại ít hơn). - Bill K
"Xe của tôi có một chiếc xe." Nếu bạn coi đó là một câu riêng biệt, không phải trong ngữ cảnh lập trình, điều đó hoàn toàn không có ý nghĩa. Và đó là toàn bộ quan điểm của kỹ thuật này. Nếu nó có vẻ khó xử, nó có lẽ là sai. - Nick Zalutskiy
@Nick Chắc chắn, nhưng "Xe của tôi có một chiếc xe hơi" có ý nghĩa hơn (tôi đoán lớp "Xe" của bạn có thể được đặt tên là "XeBehavior"). Vì vậy, bạn không thể căn cứ quyết định của bạn về "có một" vs "là một" so sánh, bạn phải sử dụng LSP, hoặc bạn sẽ phạm sai lầm - Tristan
Thay vì "là một" suy nghĩ của "cư xử như thế nào." Thừa kế là về hành vi kế thừa, không chỉ là ngữ nghĩa. - ybakos
@ybakos "Hành vi như" có thể đạt được thông qua giao diện mà không cần phải thừa kế. Từ Wikipedia: "Việc thực hiện thành phần trên thừa kế thường bắt đầu bằng việc tạo ra các giao diện khác nhau thể hiện các hành vi mà hệ thống phải triển khai ... Do đó, các hành vi hệ thống được thực hiện mà không cần thừa kế." - DavidRR


Nếu bạn hiểu sự khác biệt, nó dễ dàng hơn để giải thích.

Mã thủ tục

Một ví dụ về điều này là PHP mà không cần sử dụng các lớp (đặc biệt là trước PHP5). Tất cả logic được mã hóa trong một tập hợp các hàm. Bạn có thể bao gồm các tệp khác có chứa các hàm trợ giúp, v.v. và tiến hành logic nghiệp vụ của bạn bằng cách truyền dữ liệu xung quanh trong các hàm. Điều này có thể rất khó quản lý khi ứng dụng phát triển. PHP5 cố gắng khắc phục điều này bằng cách cung cấp nhiều thiết kế hướng đối tượng hơn.

Di sản

Điều này khuyến khích việc sử dụng các lớp học. Thừa kế là một trong ba nguyên lý thiết kế OO (thừa kế, đa hình, đóng gói).

class Person {
   String Title;
   String Name;
   Int Age
}

class Employee : Person {
   Int Salary;
   String Title;
}

Đây là thừa kế tại nơi làm việc. Nhân viên "là" Người hoặc thừa kế từ Người. Tất cả các mối quan hệ thừa kế là "là một" mối quan hệ. Nhân viên cũng làm đổ bóng thuộc tính Title từ Person, có nghĩa là Employee.Title sẽ trả về Title cho Employee chứ không phải là Person.

Thành phần

Thành phần được ưu tiên hơn thừa kế. Để đặt nó rất đơn giản, bạn sẽ có:

class Person {
   String Title;
   String Name;
   Int Age;

   public Person(String title, String name, String age) {
      this.Title = title;
      this.Name = name;
      this.Age = age;
   }

}

class Employee {
   Int Salary;
   private Person person;

   public Employee(Person p, Int salary) {
       this.person = p;
       this.Salary = salary;
   }
}

Person johnny = new Person ("Mr.", "John", 25);
Employee john = new Employee (johnny, 50000);

Thành phần thường có mối quan hệ "có" hoặc "sử dụng". Ở đây lớp Employee có một Person. Nó không kế thừa từ Person mà thay vào đó lấy đối tượng Person được truyền cho nó, đó là lý do tại sao nó "có một" Person.

Thành phần trên Thừa kế

Bây giờ, bạn muốn tạo một loại Trình quản lý để kết thúc với:

class Manager : Person, Employee {
   ...
}

Ví dụ này sẽ hoạt động tốt, tuy nhiên, nếu Người và Nhân viên đều khai báo Title? Manager.Title có nên trả lại "Người quản lý hoạt động" hoặc "Ông" không? Theo thành phần, sự mơ hồ này được xử lý tốt hơn:

Class Manager {
   public Title;
   public Manager(Person p, Employee e)
   {
      this.Title = e.Title;
   }
}

Đối tượng Manager được tạo thành một Employee và một Person. Hành vi Tiêu đề được lấy từ nhân viên. Thành phần rõ ràng này loại bỏ sự mơ hồ trong số những thứ khác và bạn sẽ gặp phải ít lỗi hơn.


175
2018-05-21 07:54



Đối với thừa kế: Không có sự mơ hồ. Bạn đang triển khai lớp Trình quản lý dựa trên các yêu cầu. Vì vậy, bạn sẽ trả về "Quản lý các hoạt động" nếu đó là những gì yêu cầu của bạn được chỉ định, nếu không bạn sẽ chỉ sử dụng thực hiện của lớp cơ sở. Ngoài ra, bạn có thể biến Person thành một lớp trừu tượng và do đó đảm bảo rằng các lớp down-stream thực hiện một thuộc tính Title. - Raj Rao
Điều quan trọng cần nhớ là người ta có thể nói "Thành phần thừa kế" nhưng điều đó không có nghĩa là "Thành phần luôn luôn thừa kế". "Là một" có nghĩa là kế thừa và dẫn đến tái sử dụng mã. Nhân viên là một người (Nhân viên không có người). - Raj Rao
Ví dụ là khó hiểu.Employee là một người, vì vậy nó nên sử dụng thừa kế. Bạn không nên sử dụng bố cục cho ví dụ này, bởi vì nó là mối quan hệ sai trong mô hình miền, ngay cả khi về mặt kỹ thuật bạn có thể khai báo nó trong mã. - Michael Freidgeim
Tôi không đồng ý với ví dụ này. Một nhân viên là một Người, là một trường hợp sách giáo khoa sử dụng thích hợp của thừa kế. Tôi cũng nghĩ rằng "vấn đề" việc định nghĩa lại trường Tiêu đề không có ý nghĩa. Thực tế là Employee.Title đổ bóng Person.Title là một dấu hiệu của lập trình kém. Sau khi tất cả, là "ông" và "Người quản lý hoạt động" thực sự đề cập đến cùng một khía cạnh của một người (chữ thường)? Tôi sẽ đổi tên Employee.Title, và do đó có thể tham khảo các thuộc tính Title và JobTitle của một Employee, cả hai đều có ý nghĩa trong cuộc sống thực. Hơn nữa, không có lý do gì cho người quản lý (tiếp theo ...) - Radon Rosborough
(... tiếp tục) để kế thừa từ cả hai người và nhân viên - sau khi tất cả, nhân viên đã thừa kế từ người. Trong các mô hình phức tạp hơn, nơi một người có thể là Người quản lý và Đại lý, đúng là đa thừa kế có thể được sử dụng (cẩn thận!), Nhưng sẽ thích hợp hơn trong nhiều môi trường để có một lớp Vai trò trừu tượng mà từ đó Người quản lý (chứa Nhân viên s / anh ta quản lý) và Đại lý (chứa Hợp đồng và các thông tin khác) kế thừa. Sau đó, một nhân viên là một người có nhiều vai trò. Do đó, cả bố cục và thừa kế đều được sử dụng đúng cách. - Radon Rosborough


Với tất cả những lợi ích không thể phủ nhận được cung cấp bởi thừa kế, đây là một số nhược điểm của nó.

Nhược điểm của thừa kế:

  1. Bạn không thể thay đổi việc triển khai được thừa hưởng từ các lớp siêu trong thời gian chạy (rõ ràng là vì thừa kế được định nghĩa tại thời gian biên dịch).
  2. Thừa kế cho thấy một lớp con để biết chi tiết về việc thực hiện lớp cha của nó, đó là lý do tại sao người ta thường nói rằng kế thừa phá vỡ đóng gói (theo nghĩa là bạn thực sự cần tập trung vào các giao diện không thực hiện, vì vậy việc sử dụng lại lớp con không phải lúc nào cũng được ưu tiên).
  3. Các khớp nối chặt chẽ được cung cấp bởi thừa kế làm cho việc thực hiện một lớp con rất ràng buộc với việc thực hiện một lớp siêu mà bất kỳ thay đổi nào trong việc thực hiện cha sẽ buộc lớp con thay đổi.
  4. Tái sử dụng quá mức bằng cách phân lớp phụ có thể làm cho ngăn xếp kế thừa rất sâu và rất khó hiểu.

Mặt khác Thành phần đối tượng được định nghĩa trong thời gian chạy thông qua các đối tượng có được các tham chiếu đến các đối tượng khác. Trong trường hợp như vậy, các đối tượng này sẽ không bao giờ có thể tiếp cận dữ liệu được bảo vệ của nhau (không bị đóng gói) và sẽ buộc phải tôn trọng giao diện của nhau. Và trong trường hợp này, các phụ thuộc triển khai sẽ ít hơn rất nhiều so với trường hợp thừa kế.


91
2018-05-21 08:34



Đây là một trong những câu trả lời tốt hơn, theo ý kiến ​​của tôi - tôi sẽ thêm vào điều này để cố gắng nghĩ lại các vấn đề của bạn về thành phần, theo kinh nghiệm của tôi, có xu hướng dẫn đến các lớp học nhỏ hơn, đơn giản hơn, tự chứa hơn, tái sử dụng nhiều hơn , với phạm vi trách nhiệm rõ ràng hơn, nhỏ hơn, tập trung hơn. Thường thì điều này có nghĩa là ít cần cho những thứ như tiêm phụ thuộc hoặc chế nhạo (trong thử nghiệm) vì các thành phần nhỏ hơn thường có thể tự đứng vững. Chỉ là kinh nghiệm của tôi. YMMV :-) - mindplay.dk
Đoạn cuối cùng trong bài đăng này thực sự đã nhấp cho tôi. Cảm ơn bạn. - Salx
Câu trả lời tốt nhất, tay xuống - bertie


Một lý do rất thực dụng khác, để thích thành phần hơn thừa kế phải làm với mô hình miền của bạn và ánh xạ nó thành một cơ sở dữ liệu quan hệ. Thật khó để ánh xạ kế thừa cho mô hình SQL (bạn kết thúc với tất cả các cách giải quyết khác nhau, như tạo các cột không phải lúc nào cũng được sử dụng, sử dụng các khung nhìn, v.v.). Một số ORML cố gắng giải quyết vấn đề này, nhưng nó luôn phức tạp một cách nhanh chóng. Thành phần có thể dễ dàng được mô hình hóa thông qua một mối quan hệ ngoại khóa giữa hai bảng, nhưng thừa kế thì khó hơn nhiều.


77
2017-09-08 02:48





Trong khi nói ngắn gọn tôi sẽ đồng ý với "Prefer composition over inheritance", rất thường đối với tôi, nó giống như "thích khoai tây hơn coca-cola". Có những nơi thừa kế và địa điểm cho sáng tác. Bạn cần phải hiểu sự khác biệt, thế thì câu hỏi này sẽ biến mất. Điều đó thực sự có ý nghĩa đối với tôi là "nếu bạn định sử dụng thừa kế - hãy suy nghĩ lại, rất có thể bạn cần sáng tác".

Bạn nên thích khoai tây hơn coca cola khi bạn muốn ăn, và coca cola trên khoai tây khi bạn muốn uống.

Tạo một lớp con nên có nghĩa là nhiều hơn chỉ là một cách thuận tiện để gọi các phương thức siêu lớp. Bạn nên sử dụng thừa kế khi lớp con "is-a" siêu cả về mặt cấu trúc và chức năng, khi nó có thể được sử dụng làm siêu lớp và bạn sẽ sử dụng nó. Nếu nó không phải là trường hợp - nó không phải là thừa kế, nhưng cái gì khác. Thành phần là khi các đối tượng của bạn bao gồm một đối tượng khác hoặc có một số mối quan hệ với chúng.

Vì vậy, đối với tôi có vẻ như ai đó không biết liệu anh ấy có cần thừa kế hay sáng tác hay không, vấn đề thực sự là anh ấy không biết anh ấy muốn uống hay ăn. Hãy suy nghĩ về miền vấn đề của bạn nhiều hơn, hiểu nó tốt hơn.


64
2018-05-08 22:29



Công cụ thích hợp cho đúng công việc. Một cái búa có thể tốt hơn ở những thứ đập hơn là một cái cờ lê, nhưng điều đó không có nghĩa là người ta nên xem một cái cờ lê là "một cái búa kém". Thừa kế có thể hữu ích khi những thứ được thêm vào lớp con là cần thiết để đối tượng cư xử như một đối tượng siêu lớp. Ví dụ, hãy xem xét một lớp cơ sở InternalCombustionEngine với một lớp dẫn xuất GasolineEngine. Sau này thêm những thứ như bugi, mà lớp cơ sở thiếu, nhưng sử dụng vật như một InternalCombustionEngine sẽ gây ra các bugi để sử dụng. - supercat


Thừa kế là khá hấp dẫn đặc biệt là đến từ thủ tục đất đai và nó thường trông deceptively thanh lịch. Ý tôi là tất cả những gì tôi cần làm là thêm một chút chức năng này vào một số lớp khác, đúng không? Vâng, một trong những vấn đề là

kế thừa có lẽ là dạng ghép nối tồi tệ nhất mà bạn có thể có

Lớp cơ sở của bạn ngắt đóng gói bằng cách hiển thị chi tiết triển khai cho các lớp con dưới dạng các thành viên được bảo vệ. Điều này làm cho hệ thống của bạn cứng nhắc và mong manh. Tuy nhiên, lỗ hổng bi thảm hơn là lớp con mới mang theo tất cả hành lý và ý kiến ​​của chuỗi thừa kế.

Bài viết, Thừa kế là Ác ma: Thất bại sử thi của DataAnnotationsModelBinder, đi qua một ví dụ về điều này trong C #. Nó cho thấy việc sử dụng thừa kế khi thành phần nên được sử dụng và cách nó có thể được tái cấu trúc.


54
2018-01-23 19:55



Thừa kế không tốt hay xấu, nó chỉ là một trường hợp đặc biệt của Thành phần. Ở đâu, thực sự, lớp con đang thực hiện một chức năng tương tự với siêu lớp. Nếu lớp con được đề xuất của bạn không thực hiện lại nhưng chỉ đơn thuần là sử dụng các chức năng của siêu lớp, sau đó bạn đã sử dụng Thừa kế không chính xác. Đó là sai lầm của lập trình viên, không phải là sự phản chiếu về Thừa kế. - iPherian