Làm thế nào để xử lý các phương thức đã được thêm vào cho các kiểu con trong bối cảnh đa hình?


14

Khi bạn sử dụng khái niệm đa hình, bạn tạo một hệ thống phân cấp lớp và sử dụng tham chiếu cha mẹ, bạn gọi các hàm giao diện mà không biết loại cụ thể nào có đối tượng. Điều đó thật tuyệt. Thí dụ:

Bạn có bộ sưu tập động vật và bạn kêu gọi tất cả các động vật hoạt động eatvà bạn không quan tâm nếu đó là một con chó đang ăn hay một con mèo. Nhưng trong hệ thống phân cấp lớp cùng bạn có động vật có bổ sung - khác hơn là kế thừa và thực hiện từ lớp Animal, ví dụ makeEggs, getBackFromTheFreezedStatevà vân vân. Vì vậy, trong một số trường hợp trong chức năng của bạn, bạn có thể muốn biết loại cụ thể để gọi các hành vi bổ sung.

Ví dụ, trong trường hợp đó là thời gian buổi sáng và nếu đó chỉ là một con vật thì bạn gọi eat, nếu không thì đó là con người, sau đó gọi trước washHands, getDressedvà sau đó gọi eat. Làm thế nào để xử lý trường hợp này? Đa hình chết. Bạn cần tìm ra loại đối tượng, nghe có vẻ như mùi mã. Có một cách tiếp cận phổ biến để xử lý trường hợp này?


7
Loại đa hình mà bạn mô tả được gọi là đa hình phụ , nhưng nó không phải là loại duy nhất (xem Đa hình ). Bạn không cần phải tạo một hệ thống phân cấp lớp để thực hiện đa hình (và tôi thực sự cho rằng kế thừa không phải là phương pháp phổ biến nhất để đạt được đa hình phụ, việc thực hiện một giao diện phổ biến hơn nhiều).
Vincent Savard

24
Nếu bạn xác định một Eatergiao diện với eat()phương thức, thì với tư cách là một máy khách, bạn không quan tâm rằng Humanviệc triển khai nó phải gọi đầu tiên washHands()getDressed(), đó là một chi tiết triển khai của lớp này. Nếu, với tư cách là một khách hàng, bạn quan tâm đến thực tế này, thì rất có thể bạn không sử dụng đúng công cụ cho công việc.
Vincent Savard

3
Bạn cũng phải xem xét rằng trong khi vào buổi sáng, một con người có thể cần phải getDressedtrước họ eat, đó không phải là trường hợp cho bữa trưa. Tùy thuộc vào hoàn cảnh của bạn, washHands();if !dressed then getDressed();[code to actually eat]có thể là cách tốt nhất để thực hiện điều này cho một con người. Một khả năng khác là nếu những thứ khác yêu cầu điều đó washHandsvà / hoặc getDressedđược gọi thì sao? Giả sử bạn có leaveForWork? Bạn có thể cần cấu trúc luồng chương trình của mình để nó được gọi là lâu trước đó.
Duncan X Simpson

1
Hãy nhớ rằng việc kiểm tra loại chính xác có thể là mùi mã trong OOP nhưng đó là một thực tế rất phổ biến trong FP (nghĩa là sử dụng khớp mẫu để xác định loại liên kết bị phân biệt đối xử và sau đó hành động theo nó).
Theodoros Chatzigiannakis

3
Coi chừng các ví dụ trong phòng học của các hệ thống phân cấp OO như động vật. Các chương trình thực tế hầu như không bao giờ có phân loại sạch như vậy. Ví dụ: ericlippert.com/2015/04/27/wizards-and-warriors-part-one . Hoặc nếu bạn muốn đi cả con lợn và đặt câu hỏi cho toàn bộ mô hình: Lập trình hướng đối tượng là xấu .
jpmc26

Câu trả lời:


18

Phụ thuộc. Thật không may, không có giải pháp chung chung. Hãy suy nghĩ về các yêu cầu của bạn và cố gắng tìm ra những điều này nên làm.

Ví dụ, bạn nói vào buổi sáng các động vật khác nhau làm những việc khác nhau. Làm thế nào về bạn giới thiệu một phương pháp getUp()hay prepareForDay()hoặc một cái gì đó như thế. Sau đó, bạn có thể tiếp tục với đa hình và để mỗi con vật thực hiện thói quen buổi sáng của nó.

Nếu bạn muốn phân biệt giữa các loài động vật, thì bạn không nên lưu trữ chúng một cách bừa bãi trong một danh sách.

Nếu không có gì khác hoạt động, thì bạn có thể thử Mẫu khách truy cập , đây là một loại hack để cho phép gửi một loại động mà bạn có thể gửi một khách truy cập sẽ nhận được các cuộc gọi lại chính xác từ động vật. Tuy nhiên, tôi sẽ nhấn mạnh rằng đây sẽ là giải pháp cuối cùng nếu mọi thứ khác thất bại.


33

Đây là một câu hỏi hay và đó là loại rắc rối của nhiều người khi cố gắng hiểu cách sử dụng OO. Tôi nghĩ rằng hầu hết các nhà phát triển đấu tranh với điều này. Tôi ước tôi có thể nói rằng hầu hết đã vượt qua nó nhưng tôi không chắc đó là trường hợp. Hầu hết các nhà phát triển, theo kinh nghiệm của tôi, cuối cùng sử dụng túi tài sản giả OO .

Đầu tiên, hãy để tôi được rõ ràng. Đây không phải là lỗi của bạn. Cách OO thường được dạy là rất thiếu sót. Các Animalví dụ là người phạm tội đầu, IMO. Về cơ bản, chúng tôi nói, hãy nói về các đối tượng, những gì họ có thể làm. Một Animalcó thể eat()và nó có thể speak(). Siêu. Bây giờ tạo một số động vật và mã hóa cách chúng ăn và nói. Bây giờ bạn biết OO, phải không?

Vấn đề là điều này đến ở OO từ sai hướng. Tại sao có động vật trong chương trình này và tại sao chúng cần nói và ăn?

Tôi có một thời gian khó khăn để nghĩ về việc sử dụng thực sự cho một Animalloại. Tôi chắc chắn rằng nó tồn tại nhưng hãy thảo luận về một cái gì đó mà tôi nghĩ là dễ dàng hơn để lý do: một mô phỏng giao thông. Giả sử chúng ta muốn mô hình lưu lượng trong các kịch bản khác nhau. Dưới đây là một số điều cơ bản mà chúng ta cần phải có để làm điều đó.

Vehicle
Road
Signal

Chúng ta có thể đi sâu hơn với tất cả các loại người đi bộ và xe lửa nhưng chúng ta sẽ giữ cho nó đơn giản.

Hãy xem xét Vehicle. Xe cần những khả năng gì? Nó cần phải đi trên một con đường. Nó cần phải có khả năng dừng lại ở các tín hiệu. Nó cần để có thể điều hướng các giao lộ.

interface Vehicle {
  move(Road road);
  navigate(Road... intersection);
}

Điều này có lẽ quá đơn giản nhưng đó là một sự khởi đầu. Hiện nay. Thế còn tất cả những thứ khác mà một chiếc xe có thể làm? Họ có thể tắt một con đường và vào một con mương. Đó có phải là một phần của mô phỏng không? Không, đừng cần nó. Một số xe hơi và xe buýt có thủy lực cho phép chúng nảy hoặc quỳ tương ứng. Đó có phải là một phần của mô phỏng không? Không, đừng cần nó. Hầu hết các xe đốt xăng. Một số thì không. Là nhà máy điện một phần của mô phỏng? Không, đừng cần nó. Kích thước bánh xe? Đừng cần nó. Định vị toàn cầu? Hệ thống thông tin giải trí? Đừng cần em.

Bạn chỉ cần xác định các hành vi mà bạn sẽ sử dụng. Cuối cùng, tôi nghĩ việc xây dựng giao diện OO từ mã tương tác với chúng thường tốt hơn. Bạn bắt đầu với một giao diện trống và sau đó bắt đầu viết mã gọi các phương thức không tồn tại. Đó là cách bạn biết những phương pháp bạn cần trên giao diện của mình. Sau đó, khi bạn đã thực hiện điều đó, bạn đi và bắt đầu xác định các lớp thực hiện các hành vi này. Các hành vi không được sử dụng là không liên quan và không cần phải được xác định.

Toàn bộ quan điểm của OO là bạn có thể thêm các triển khai mới của các giao diện này sau mà không thay đổi mã gọi. Cách duy nhất hoạt động là nếu nhu cầu của mã gọi xác định những gì diễn ra trong giao diện. Không có cách nào để xác định tất cả các hành vi của tất cả những điều có thể có thể được nghĩ ra sau này.


13
Đây là một câu trả lời tốt. "Cuối cùng, tôi nghĩ việc xây dựng giao diện OO từ mã tương tác với chúng thường tốt hơn." Hoàn toàn, và tôi cho rằng đó là cách duy nhất. Bạn không thể biết hợp đồng công khai của một giao diện chỉ thông qua triển khai, nó luôn được xác định theo quan điểm của khách hàng. (Và như một lưu ý phụ, đây là những gì TDD thực sự hướng tới.)
Vincent Savard

@VincentSavard "Tôi cho rằng đó là cách duy nhất." Bạn đúng rồi. Tôi đoán lý do tôi đã không làm cho nó tuyệt đối là vì một khi bạn có ý tưởng, bạn có thể loại bỏ giao diện và sau đó tinh chỉnh nó theo cách này. Cuối cùng, khi bạn đi xuống những cái thau, đó là điều duy nhất quan trọng.
JimmyJames

@ jpmc26 Có lẽ một chút mạnh mẽ từ. Tôi không chắc chắn tôi đồng ý rằng thật hiếm khi thực hiện điều này. Tôi không chắc làm thế nào các giao diện có thể hữu ích nếu bạn không sử dụng chúng theo cách này ngoài các giao diện đánh dấu mà tôi nghĩ là một ý tưởng tồi tệ.
JimmyJames

9

TL; DR:

Hãy suy nghĩ về một sự trừu tượng và các phương thức áp dụng cho tất cả các lớp con và bao gồm mọi thứ bạn cần.

Trước tiên hãy ở lại với eat()ví dụ của bạn .

Đó là một tài sản của một con người, như một điều kiện tiên quyết để ăn, con người muốn rửa tay và mặc quần áo trước khi ăn. Nếu bạn muốn ai đó đến ăn sáng với bạn, bạn đừng bảo họ rửa tay và mặc quần áo, họ tự làm điều đó khi bạn mời họ, hoặc họ trả lời "Không, tôi không thể đến Tôi đã rửa tay và tôi chưa mặc quần áo ".

Quay lại phần mềm:

Như một Humanví dụ sẽ không ăn mà không cần điều kiện tiên quyết, tôi muốn có Human's eat()phương pháp làm washHands()getDressed()nếu điều đó đã không được thực hiện. Bạn không nên eat()gọi công việc của mình là người gọi để biết về tính đặc thù đó. Sự thay thế của con người bướng bỉnh sẽ là ném một ngoại lệ ("Tôi không chuẩn bị ăn!") Nếu điều kiện tiên quyết không được đáp ứng, khiến bạn thất vọng, nhưng ít nhất thông báo rằng việc ăn uống không hiệu quả.

Thế còn makeEggs()?

Tôi khuyên bạn nên thay đổi cách suy nghĩ của bạn. Bạn có thể muốn thực hiện các nhiệm vụ buổi sáng theo lịch trình của tất cả chúng sinh. Một lần nữa, với tư cách là người gọi, công việc của bạn là không biết nhiệm vụ của họ là gì. Vì vậy, tôi muốn giới thiệu một doMorningDuties()phương thức mà tất cả các lớp thực hiện.


Tôi đồng ý với câu trả lời này. Narek nói đúng về mùi mã. Đó là thiết kế giao diện có mùi, vì vậy hãy sửa nó và tốt cho bạn.
Jonathan van de Veen

Những gì câu trả lời này mô tả thường được gọi là Nguyên tắc thay thế Liskov .
Philipp

2

Câu trả lời khá đơn giản.

Làm thế nào để xử lý các đối tượng có thể làm nhiều hơn bạn mong đợi?

Bạn không cần phải xử lý nó vì nó sẽ không phục vụ mục đích. Một giao diện thường được thiết kế tùy thuộc vào cách sử dụng. Nếu giao diện của bạn không xác định rửa tay, thì bạn không quan tâm đến nó như người gọi giao diện; nếu bạn đã làm bạn sẽ thiết kế nó khác nhau.

Ví dụ, trong trường hợp là thời gian buổi sáng và nếu đó chỉ là một con vật thì bạn gọi ăn, nếu không thì là con người, sau đó gọi washHands đầu tiên, getDression và chỉ sau đó gọi ăn. Làm thế nào để xử lý trường hợp này?

Ví dụ: trong mã giả:

interface IEater { void Eat(); }
interface IMorningRoutinePerformer { void DoMorningRoutine(); }
interface IAnimal : IEater, IMorningPerformer;
interface IHuman : IEater, IMorningPerformer; 
{
  void WashHands();
  void GetDressed();
}

void MorningTime()
{
   IList<IMorningRoutinePerformer> items = Service.GetMorningPerformers();
   foreach(item in items) { item.DoMorningRoutine(); }
}

Bây giờ bạn thực hiện IMorningPerformercho Animalđến chỉ cần thực hiện ăn, và cho Humanbạn cũng thực hiện nó để rửa tay và mặc quần áo. Người gọi phương thức MorningTime của bạn có thể quan tâm ít hơn nếu đó là người hay động vật. Tất cả những gì nó muốn là thói quen buổi sáng được thực hiện, mà mỗi đối tượng thực hiện một cách đáng ngưỡng mộ nhờ OO.

Đa hình chết.

Hay không?

Bạn cần tìm ra loại đối tượng

Tại sao lại cho rằng? Tôi nghĩ rằng đây có thể là một giả định sai.

Có một cách tiếp cận phổ biến để xử lý trường hợp này?

Có, thường thì nó được giải quyết với phân cấp giao diện hoặc lớp được thiết kế cẩn thận. Lưu ý rằng trong ví dụ trên, không có gì mâu thuẫn với ví dụ của bạn khi bạn đưa ra, tuy nhiên, có lẽ bạn sẽ cảm thấy không hài lòng, vì bạn đã đưa ra một số giả định mà bạn không viết trong câu hỏi vào thời điểm viết và những giả định này có thể bị vi phạm.

Có thể đi đến một hố thỏ bằng cách bạn thắt chặt các giả định của mình và tôi sửa đổi câu trả lời để vẫn thỏa mãn chúng, nhưng tôi không nghĩ rằng nó sẽ hữu ích.

Thiết kế hệ thống phân cấp lớp tốt là khó khăn và nó đòi hỏi nhiều kiến ​​thức sâu sắc về lĩnh vực kinh doanh của bạn. Đối với các miền phức, người ta trải qua hai, ba hoặc thậm chí nhiều lần lặp hơn, vì họ tinh chỉnh hiểu biết của họ về cách các thực thể khác nhau trong miền kinh doanh của họ tương tác, cho đến khi họ đến một mô hình thích hợp.

Đó là nơi mà các ví dụ động vật đơn giản đang thiếu. Chúng tôi muốn dạy đơn giản, nhưng vấn đề chúng tôi đang cố gắng giải quyết là không rõ ràng cho đến khi bạn đi sâu hơn, đó là có những cân nhắc và lĩnh vực phức tạp hơn.


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.