Chúng ta có thể thay thế hoàn toàn kế thừa bằng cách sử dụng mô hình chiến lược và tiêm phụ thuộc không?


10

Ví dụ:

var duckBehaviors = new Duckbehavior();
duckBehaviors.quackBehavior = new Quack();
duckBehaviors.flyBehavior = new FlyWithWings();
Duck mallardDuck = new Duck(DuckTypes.MallardDuck, duckBehaviors)

Vì lớp Duck chứa tất cả các hành vi (trừu tượng), việc tạo một lớp mới MallardDuck(mở rộng Duck) dường như không bắt buộc.

Tham khảo: Mẫu thiết kế đầu tiên, Chương 1.


Các loại Duckbehavior.quackBehaviorvà các lĩnh vực khác 'trong mã của bạn là gì?
max630

Không có tiêm phụ thuộc trong ví dụ của bạn.
David Conrad

11
Kế thừa là tuyệt vời khi nhà phát triển cấp trung cấp đến trung cấp của bạn bởi vì có một lịch sử lâu dài về tái cấu trúc các thủ thuật và các mẫu thiết kế xung quanh nó. Nhưng hầu hết các nhà phát triển có kinh nghiệm tôi đã nói chuyện thích phân cấp thừa kế thực sự nông dựa trên thành phần bất cứ khi nào có thể. Kế thừa có thể gây ra khớp nối chặt chẽ hơn mức cần thiết và có thể làm cho những thay đổi khó khăn.
Mark Rogers


5
@DavidConrad: OP không thực hiện việc mới trong lớp . DI / IoC là về việc tiêm phụ thuộc, không phải về container hoặc về việc không bao giờ sử dụng "mới"; đó là một mối quan tâm hoàn toàn trực giao. Bên cạnh đó, bạn luôn phải thay đổi mã ở đâu đó - có thể là root thành phần hoặc một số tệp cấu hình. Điều khiển được đảo ngược ở đây trong loại Vịt, vì điều kiểm soát việc tạo ra các phụ thuộc không phải là Vịt vịt, mà là một số bối cảnh bên ngoài; đây là một ví dụ đồ chơi được đưa ra cho rõ ràng, hoàn toàn ổn khi bối cảnh bên ngoài được thể hiện chỉ bằng mã gọi.
Filip Milovanović

Câu trả lời:


21

Chắc chắn, nhưng chúng tôi gọi đó là thành phần và đoàn . Mô hình chiến lược và tiêm phụ thuộc có thể có cấu trúc tương tự nhau nhưng ý định của chúng là khác nhau.

Mẫu chiến lược cho phép sửa đổi thời gian chạy của hành vi trong cùng một giao diện. Tôi có thể bảo một con vịt trời biết bay và xem nó bay bằng đôi cánh. Sau đó đổi nó lấy một con vịt phi công phản lực và xem nó bay với các hãng hàng không Delta. Làm điều đó trong khi chương trình đang chạy là một điều Chiến lược mẫu.

Dependency Injection là một kỹ thuật để tránh phụ thuộc mã hóa cứng để họ có thể thay đổi độc lập mà không yêu cầu khách hàng phải sửa đổi khi họ thay đổi. Khách hàng chỉ đơn giản là bày tỏ nhu cầu của họ mà không biết họ sẽ được đáp ứng như thế nào. Do đó, làm thế nào chúng được đáp ứng được quyết định ở nơi khác (thường là chính). Bạn không cần hai con vịt để sử dụng kỹ thuật này. Chỉ cần một cái gì đó sử dụng một con vịt mà không biết hoặc quan tâm con vịt nào. Một cái gì đó không xây dựng con vịt hoặc đi tìm nó nhưng hoàn toàn hạnh phúc khi sử dụng bất cứ con vịt nào bạn đưa nó.

Nếu tôi có một lớp vịt cụ thể, tôi có thể cho nó thực hiện hành vi bay. Tôi thậm chí có thể khiến nó chuyển đổi các hành vi từ fly-with-wing sang fly-with-Delta dựa trên một biến trạng thái. Biến mà có thể là một boolean, một int, hoặc nó có thể là một FlyBehaviormà có một flyphương pháp mà không bất cứ phong cách bay mà không có tôi phải thử nghiệm nó với một nếu. Bây giờ tôi có thể thay đổi kiểu bay mà không cần thay đổi loại vịt. Bây giờ Mallards có thể trở thành phi công. Đây là thành phần và đoàn . Con vịt bao gồm một FlyBehavior và nó có thể ủy thác các yêu cầu bay cho nó. Bạn có thể thay thế tất cả các hành vi vịt của bạn cùng một lúc theo cách này hoặc giữ một cái gì đó cho mỗi hành vi hoặc bất kỳ sự kết hợp nào ở giữa.

Điều này cung cấp cho bạn tất cả các quyền hạn mà thừa kế có ngoại trừ một. Kế thừa cho phép bạn thể hiện phương thức Vịt nào bạn ghi đè trong các kiểu con Vịt. Thành phần và ủy quyền yêu cầu Vịt phải ủy quyền rõ ràng cho các kiểu con ngay từ đầu. Điều này linh hoạt hơn nhiều nhưng nó liên quan đến việc gõ bàn phím nhiều hơn và Duck phải biết điều đó đang xảy ra.

Tuy nhiên, nhiều người tin rằng sự kế thừa phải được thiết kế rõ ràng ngay từ đầu. Và nếu không, bạn nên đánh dấu các lớp của mình là niêm phong / cuối cùng để không cho phép thừa kế. Nếu bạn có quan điểm đó thì sự kế thừa thực sự không có lợi thế hơn so với thành phần và sự ủy nhiệm. Bởi vì sau đó, dù bằng cách nào bạn cũng phải thiết kế để mở rộng ngay từ đầu hoặc sẵn sàng xé mọi thứ sau này.

Xé mọi thứ thực sự là một lựa chọn phổ biến. Chỉ cần lưu ý rằng có những trường hợp đó là một vấn đề. Nếu bạn đã triển khai độc lập các thư viện hoặc mô-đun mã mà bạn không có ý định cập nhật với bản phát hành tiếp theo, bạn có thể sẽ gặp khó khăn khi xử lý các phiên bản của các lớp không biết gì về những gì bạn sắp làm.

Mặc dù sẵn sàng xé mọi thứ sau này có thể giải phóng bạn khỏi việc thiết kế, có một điều rất mạnh mẽ về việc có thể thiết kế một cái gì đó sử dụng một con vịt mà không cần phải biết con vịt sẽ thực sự làm gì khi sử dụng. Điều đó không biết là công cụ mạnh mẽ. Nó cho phép bạn ngừng suy nghĩ về vịt trong một thời gian và suy nghĩ về phần còn lại của mã của bạn.

"Chúng ta có thể" và "chúng ta nên" là những câu hỏi khác nhau. Thành phần ủng hộ thừa kế không nói không bao giờ sử dụng thừa kế. Vẫn có những trường hợp thừa kế có ý nghĩa nhất. Tôi sẽ chỉ cho bạn ví dụ yêu thích của tôi :

public class LoginFailure : System.ApplicationException {}

Kế thừa cho phép bạn tạo các ngoại lệ với các tên mô tả cụ thể hơn chỉ trong một dòng.

Hãy thử làm điều đó với thành phần và bạn sẽ nhận được một mớ hỗn độn. Ngoài ra, không có rủi ro về vấn đề thừa kế yo-yo vì không có dữ liệu hoặc phương pháp nào ở đây để tái sử dụng và khuyến khích chuỗi kế thừa. Tất cả điều này thêm là một tên tốt. Đừng bao giờ đánh giá thấp giá trị của một cái tên hay.


1
" Đừng bao giờ đánh giá thấp giá trị của một cái tên hay ". Thời gian quần áo mới của Hoàng đế: LoginExceptionkhông phải là một cái tên hay. Đó là cách đặt tên "smurf" cổ điển và, nếu tôi lấy đi Exception, tất cả những gì tôi có là Login, nó không cho tôi biết điều gì đã xảy ra.
David Arno

Đáng buồn thay, chúng ta bị mắc kẹt với quy ước nực cười Exceptionlà kết thúc mọi lớp ngoại lệ, nhưng xin đừng nhầm lẫn giữa "bị mắc kẹt" với "tốt".
David Arno

@DavidArno Nếu bạn có thể đề xuất một cái tên hay hơn thì tôi đều nghe thấy. Ở đây chúng ta có lợi ích của việc không bị mắc kẹt trong các quy ước của một cơ sở mã hiện có. Nếu bạn có sức mạnh để thay đổi thế giới chỉ bằng một cái búng tay, bạn sẽ đặt tên cho nó là gì?
candied_orange

Tôi muốn đặt tên cho chúng, StackOverflow, OutOfMemory, NullReferenceAccess, LoginFailurevv Về cơ bản, có "ngoại lệ" tắt tên. Và, nếu được yêu cầu, hãy sửa chúng để nó mô tả những gì đã sai.
David Arno

@DavidArno bằng lệnh của bạn.
candied_orange

7

Bạn có thể thay thế hầu hết mọi phương pháp bằng bất kỳ phương pháp nào khác và vẫn sản xuất phần mềm hoạt động. Tuy nhiên, một số phù hợp hơn với một vấn đề cụ thể hơn những vấn đề khác.

Nó phụ thuộc vào rất nhiều thứ mà người ta thích hơn. Ưu tiên nghệ thuật trong ứng dụng, kinh nghiệm trong nhóm, dự kiến ​​phát triển trong tương lai, sở thích cá nhân và người mới sẽ khó khăn như thế nào để có được cái đầu của mình xung quanh nó, kể tên một vài người.

Khi bạn có nhiều kinh nghiệm hơn và đấu tranh thường xuyên hơn với những sáng tạo của người khác, bạn có thể sẽ nhấn mạnh hơn vào sự suy ngẫm cuối cùng.

Kế thừa vẫn là một công cụ mô hình hóa hợp lệ và mạnh mẽ không nhất thiết phải luôn linh hoạt nhất nhưng nó cung cấp hướng dẫn mạnh mẽ cho những người mới có thể biết ơn về ánh xạ rõ ràng đến miền vấn đề.


-1

Kế thừa không quan trọng như đã từng nghĩ. Nó vẫn còn quan trọng , và loại bỏ nó sẽ là một sai lầm tồi tệ.

Một ví dụ cực đoan là Objective-C trong đó mọi thứ là một lớp con của NSObject (trình biên dịch thực sự không cho phép bạn khai báo một lớp không có lớp cơ sở, vì vậy mọi thứ không được tích hợp trong trình biên dịch phải là lớp con của một thứ gì đó tích hợp vào trình biên dịch). Có rất nhiều nội dung hữu ích được tích hợp trong NSObject mà bạn sẽ không phải bỏ lỡ.

Một ví dụ thú vị khác là UIView, lớp "xem" cơ bản trong phát triển UIKit cho iOS. Đây là một lớp giống như một chức năng khai báo lớp trừu tượng mà các lớp con được cho là phải điền vào, nhưng bản thân nó cũng hữu ích. Nó có các lớp con được cung cấp bởi UIKit, thường được sử dụng như là. Có thành phần của nhà phát triển cài đặt các cuộc phỏng vấn trong một khung nhìn. Và thường có các lớp con do nhà phát triển định nghĩa, thường sử dụng thành phần. Không có quy tắc duy nhất nghiêm ngặt hoặc thậm chí quy tắc, bạn sử dụng bất cứ điều gì phù hợp với yêu cầu của bạn tốt nhất.


"Ví dụ cực đoan" của bạn đại khái là cách mà hầu hết các ngôn ngữ OO hiện đại hoạt động: các lớp con Python type, mọi thứ không nguyên thủy trong Java đều thừa hưởng từ Object, mọi thứ trong JavaScript đều có nguyên mẫu kết thúc Object, v.v.
Delioth

-1

[Tôi sẽ mạo hiểm một câu trả lời táo tợn cho câu hỏi giật gân.]

Chúng ta có thể thay thế hoàn toàn kế thừa bằng cách sử dụng mô hình chiến lược và tiêm phụ thuộc không?

Có ... ngoại trừ mẫu chiến lược tự sử dụng tính kế thừa. Mẫu chiến lược hoạt động trên kế thừa giao diện. Thay thế sự kế thừa bằng thành phần + chiến lược di chuyển sự kế thừa đến một nơi khác. Tuy nhiên, thay thế như vậy thường là đáng làm, bởi vì nó cho phép trêu chọc các thứ bậc khác nhau .


2
" Mẫu chiến lược hoạt động trên kế thừa giao diện ". Hãy nhớ rằng, mẫu chiến lược là một mẫu thiết kế; không phải là một mô hình thực hiện. Vì vậy, nó có thể được thực hiện bằng các giao diện, nhưng nó có thể được thực hiện bằng cách sử dụng ví dụ: hàm băm / từ điển của các hàm.
David Arno

3
Kế thừa giao diện hoàn toàn không phải là sự kế thừa, nó chỉ là một hợp đồng hoặc phân loại. Bạn cũng có thể sử dụng các đại biểu cho mẫu chiến lược (tôi thích sử dụng chúng).
Deepak Mishra

Thêm một, anh chàng: ... hoạt động trên kế thừa giao diện , danh tiếng về việc sử dụng đúng thuật ngữ "giao diện". Tôi nghĩ rằng thiết kế lớp học kém hoặc không đủ năng lực là lý do cơ bản cho câu hỏi này được nêu ra. Để giải thích tại sao / làm thế nào sẽ lấy một cuốn sách; Ma quỷ là trong các chi tiết. Tôi đã làm việc với các thiết kế thừa kế là một niềm vui được nhìn thấy và đồng thời thừa kế sâu sắc đó là địa ngục. Tôi đã thấy interfacethiết kế craptastic cố định với sự kế thừa. Vấn đề tiềm ẩn ở đây là hành vi phụ thuộc vào việc thực hiện không nhất quán. P, S. kế thừa và giao diện không loại trừ lẫn nhau,
radarbob

@DavidArno bình luận : Nhận xét này sẽ ổn nếu được đính kèm với câu hỏi OP. Câu hỏi OP được trình bày sai về mặt kỹ thuật nhưng chúng tôi vẫn nhận được nó. Vấn đề không phải là câu trả lời cụ thể này.
radarbob

@DeepakMishra bình luận : . Một "giao diện" là tất cả các thành viên công cộng của một lớp. Thật không may, "giao diện" có nghĩa là quá tải. Chúng ta phải cẩn thận để phân biệt ý nghĩa chung với từ khóa của ngôn ngữ lập trìnhinterface
radarbob

-2

Không. Nếu một con vịt trời yêu cầu các thông số khác nhau để khởi tạo so với một số loại vịt khác, thì đó sẽ là một cơn ác mộng khi thay đổi. Ngoài ra, bạn có thể khởi tạo một con vịt? Đó là nhiều hơn một con vịt trời hoặc vịt tiếng Quan thoại hoặc bất kỳ loại vịt nào bạn có.

Ngoài ra tôi có thể muốn một số logic xung quanh các loại để phân cấp loại có thể tốt hơn.

Bây giờ nếu việc tái sử dụng mã trở nên có vấn đề đối với một số chức năng, chúng ta có thể soạn thảo nó bằng cách chuyển các hàm qua hàm tạo.

Một lần nữa, nó thực sự phụ thuộc vào trường hợp sử dụng của bạn. Nếu bạn chỉ có một con vịt và một con vịt trời, một hệ thống phân cấp lớp là một giải pháp đơn giản hơn nhiều.

Nhưng đây là một ví dụ mà bạn muốn sử dụng mẫu chiến lược: Nếu bạn có các lớp khách hàng và bạn muốn chuyển qua một chiến lược thanh toán (giao diện) có thể là chiến lược thanh toán theo giờ hạnh phúc hoặc chiến lược thanh toán thông thường, bạn có thể vượt qua nó trong hàm tạo. Bằng cách này, bạn không phải tạo hai lớp khách hàng khác nhau và bạn không cần phân cấp. Bạn chỉ có một lớp khách hàng.

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.