Як поводитися з методами, які були додані для підтипів у контексті поліморфізму?


14

Використовуючи поняття поліморфізму, ви створюєте ієрархію класів і використовуючи посилання батьків, ви викликаєте функції інтерфейсу, не знаючи, який саме тип має об'єкт. Це чудово. Приклад:

У вас є колекція тварин, і ви закликаєте всіх функцій тварин, eatі вам не байдуже, їсть це собака чи кішка. Але в тій же ієрархії класів у вас є додаткові тварини, крім успадкованих та реалізованих від класу Animal, наприклад makeEggs, getBackFromTheFreezedStateтощо. Тож у деяких випадках у вашому функціонуванні ви можете дізнатися конкретний тип, щоб викликати додаткову поведінку.

Наприклад, якщо це ранковий час, і якщо це просто тварина, то ви телефонуєте eat, інакше, якщо це людина, то дзвоніть спочатку washHands, getDressedа вже потім дзвоніть eat. Як впоратися з цими справами? Поліморфізм гине. Потрібно з’ясувати тип об’єкта, який звучить як запах коду. Чи існує спільний підхід до вирішення цих випадків?


7
Тип описаного вами поліморфізму називається підтиповим поліморфізмом , але це не єдиний вид (див. Поліморфізм ). Вам не потрібно створювати ієрархію класів, щоб виконувати поліморфізм (і я насправді стверджую, що успадкування - це не найпоширеніший метод досягнення поліморфізму підтипів, реалізація інтерфейсу є набагато більш поширеним).
Вінсент Савард

24
Якщо ви визначаєте Eaterінтерфейс із eat()методом, то, як клієнта, вам не байдуже, що його Humanвпроваджувати потрібно для першого виклику, washHands()і getDressed()це деталі реалізації цього класу. Якщо ви, як клієнт, дбаєте про цей факт, то, швидше за все, ви не використовуєте правильний інструмент для роботи.
Вінсент Савард

3
Ви також повинні врахувати, що, перебуваючи вранці, людині може знадобитися getDressedперед ними eat, це не стосується обіду. Залежно від ваших обставин, washHands();if !dressed then getDressed();[code to actually eat]найкращий спосіб реалізувати це для людини. Інша можливість полягає в тому, що якщо інші речі вимагають цього washHandsта / або getDressedназиваються? Припустимо, у вас є leaveForWork? Можливо, вам доведеться структурувати потік вашої програми, щоб вона була такою, що вона називається задовго до цього.
Дункан X Сімпсон

1
Майте на увазі, що перевірка на точний тип може мати запах коду в OOP, але це дуже поширена практика на FP (тобто використовуйте відповідність шаблону для визначення типу дискримінаційного об'єднання, а потім діяти на нього).
Теодорос Чатзіґянакікіс

3
Остерігайтеся прикладів шкільних приміщень ієрархій ОО як тварини. Реальні програми майже ніколи не мають такої чистої систематики. Наприклад, ericlippert.com/2015/04/27/wizards-and-warriors-part-one . Або якщо ви хочете пройти цілу свиню і поставити під сумнів всю парадигму: Об'єктно-орієнтоване програмування погано .
jpmc26

Відповіді:


18

Залежить. На жаль, немає загального рішення. Подумайте про свої вимоги і спробуйте розібратися, що ці речі повинні робити.

Наприклад, ви сказали, що вранці різні тварини роблять різні речі. Як щодо вас ввести метод getUp()або prepareForDay()або що - щось подібне. Тоді ви можете продовжити поліморфізм і дозволити кожній тварині виконувати свій ранковий розпорядок.

Якщо ви хочете розмежовуватися серед тварин, то не слід зберігати їх без розбору у списку.

Якщо нічого іншого не працює, ви можете спробувати шаблон відвідувачів , який є начебто хаком, щоб забезпечити своєрідну динамічну диспетчеризацію, де ви можете надіслати відвідувача, який отримає точні зворотні виклики від тварин. Однак я наголошу, що це має бути крайнім засобом, якщо все інше не вдасться.


33

Це хороше питання, і це неприємності багатьох людей, коли намагаються зрозуміти, як користуватися ОО. Я думаю, що з цим бореться більшість розробників. Я б хотів сказати, що більшість це проходить, але я не впевнений, що це так. Більшість розробників, на мій досвід, в кінцевому підсумку використовують сумки з властивостями псевдо OO .

По-перше, дозвольте мені зрозуміти. Це не ваша вина. Те, як зазвичай викладають ОО, дуже помилково. AnimalПриклад є головним винним, ІМ. В основному, ми говоримо, давайте поговоримо про об’єкти, що вони можуть зробити. AnimalМоже eat()і вона може speak(). Супер. Тепер створіть деяких тварин і кодуйте, як вони їдять і говорять. Тепер ви знаєте ОО, правда?

Проблема полягає в тому, що це йде в ОО з неправильного напрямку. Чому в цій програмі є тварини і чому їм потрібно говорити і їсти?

Мені важко думати про реальне використання Animalтипу. Я впевнений, що вона існує, але давайте обговоримо щось, на що, на мою думку, простіше міркувати: моделювання руху. Припустимо, ми хочемо моделювати трафік у різних сценаріях. Ось кілька основних речей, які нам потрібно мати, щоб мати змогу це зробити.

Vehicle
Road
Signal

Ми можемо заглиблюватися в усілякі речі пішоходів і поїздів, але все це буде просто.

Розглянемо Vehicle. Які можливості потрібні транспортному засобу? Потрібно їхати дорогою. Потрібно вміти зупинятися на сигналах. Потрібно вміти орієнтуватися на перехрестях.

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

Це, мабуть, занадто просто, але це початок. Тепер. А як щодо всіх інших речей, які може зробити транспортний засіб? Вони можуть повернути з дороги і в кювет. Це частина моделювання? Ні. Це не потрібно. У деяких автомобілях та автобусах є гідравліка, яка дозволяє їм відскакувати або ставати на коліна відповідно. Це частина моделювання? Ні. Це не потрібно. Більшість автомобілів спалюють бензин. Деякі ні. Чи є електростанція частиною моделювання? Ні. Це не потрібно. Розмір колеса? Це не потрібно. GPS навігація? Інформаційно-розважальна система? Їм не потрібно.

Вам потрібно лише визначити поведінку, яку ви збираєтеся використовувати. З цією метою я думаю, що часто краще будувати інтерфейси ОО з коду, який взаємодіє з ними. Ви починаєте з порожнього інтерфейсу, а потім починаєте писати код, який викликає неіснуючі методи. Ось як ви знаєте, які методи вам потрібні для вашого інтерфейсу. Потім, як тільки ви це зробите, ви переходите до початку визначення класів, які реалізують ці поведінки. Поведінки, які не використовуються, не мають значення і їх не потрібно визначати.

Вся справа в тому, що ви можете додавати нові реалізації цих інтерфейсів пізніше, не змінюючи код виклику. Єдиний спосіб, який працює, це, якщо потреби викликового коду визначають, що йде в інтерфейсі. Немає можливості визначити всю поведінку всіх можливих речей, про які можна було б придумати пізніше.


13
Це хороша відповідь. "З цією метою я думаю, що часто краще будувати інтерфейси OO з коду, який взаємодіє з ними." Абсолютно, і я можу стверджувати, що це єдиний спосіб. Ви не можете знати державний контракт інтерфейсу лише через його реалізацію, він завжди визначається з точки зору його клієнтів. (І як зауваження, ось що насправді йде про TDD.)
Вінсент Савард,

@VincentSavard "Я б стверджував, що це єдиний спосіб". Ти правий. Я здогадуюсь, що причина, по якій я не зробила це абсолютним, полягає в тому, що як тільки ви маєте ідею, ви можете розім’яти інтерфейс, а потім уточнити його таким чином. Зрештою, коли ви переходите до латунних снастей, це єдине, що має значення.
JimmyJames

@ jpmc26 Можливо, трохи сильно сформульовано. Я не впевнений, що згоден, що реалізувати це рідко. Я не впевнений, наскільки інтерфейси можуть бути корисними, якщо ви не використовуєте їх таким чином, крім маркерних інтерфейсів, які, на мою думку, є жахливою ідеєю.
JimmyJames

9

TL; DR:

Подумайте про абстракцію та методи, що застосовуються до всіх підкласів і охоплюйте все необхідне.

Давайте спочатку зупинимось на вашому eat()прикладі.

Це властивість бути людиною, що, як передумова прийому їжі, люди хочуть мити руки та одягатися перед їжею. Якщо ви хочете, щоб хтось прийшов до вас, щоб поснідати з вами, ви не говорите їм мити руки і одягатися, вони роблять це самостійно, коли ви запрошуєте їх, або вони відповідають "Ні, я не можу прийти я не вимив руки і ще не одягнений ".

Повернутися до програмного забезпечення:

Як , Humanнаприклад , не їстиме без попередніх умов, я б в Human«S eat()метод робити washHands()і , getDressed()якщо це не було зроблено. Це не повинно бути вашою роботою як eat()абонента, щоб знати про цю особливість. Альтернативою впертої людини було б кинути виняток ("я не готовий їсти!"), Якщо передумови не виконуються, залишаючи вас розчарованими, але принаймні поінформованими, що їжа не працює.

Про що makeEggs()?

Я рекомендую змінити свій спосіб мислення. Ви, мабуть, хочете виконувати заплановані ранкові обов'язки всіх істот. Знову ж таки, як особа, яка телефонує, не повинна бути вашою справою знати, які їх обов'язки. Тому я рекомендую doMorningDuties()метод, який застосовують усі класи.


Я згоден з цією відповіддю. Нарек прав щодо кодового запаху. Саме дизайн інтерфейсу смердючий, тому виправте це і ваше благо.
Джонатан ван де Вен

Те, що описується у цій відповіді, зазвичай називають принципом заміни Ліскова .
Філіпп

2

Відповідь досить проста.

Як поводитися з об’єктами, які можуть зробити більше, ніж ви очікували?

Не потрібно з цим поводитися, бо це не послужило б ніякій меті. Інтерфейс зазвичай розробляється залежно від того, яким чином він буде використовуватися. Якщо ваш інтерфейс не визначає миття рук, то ви не переймаєтесь цим, як викликаючи інтерфейс; якби ти це робив, то створив би це по-іншому.

Наприклад, якщо це час вранці, і якщо це просто тварина, то ви закликаєте їсти, інакше якщо це людина, то зателефонуйте спочатку вмитися Руками, getDresses і тільки потім зателефонуйте поїсти. Як впоратися з цими справами?

Наприклад, у псевдокоді:

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(); }
}

Тепер ви реалізуєте IMorningPerformerдля того, Animalщоб просто виконувати їжу, а Humanтакож для того, щоб мити руки та одягатися. Абонент вашого методу MorningTime може менше дбати, чи це людина, чи тварина. Все, що він хоче, - це виконаний ранковий розпорядок, який кожен об’єкт чудово виконує завдяки ОО.

Поліморфізм гине.

Або це?

Потрібно з’ясувати тип об’єкта

Чому припускають це? Я думаю, це може бути помилковим припущенням.

Чи існує спільний підхід до вирішення цих випадків?

Так, зазвичай це вирішується за допомогою ретельно розробленої ієрархії класів або інтерфейсів. Зауважте, що у наведеному вище прикладі немає нічого, що суперечить вашому прикладу, як ви його навели, але, ймовірно, ви будете почувати себе незадоволеними, тому що ви зробили ще кілька припущень, які ви не писали в питанні станом на момент написання , і ці припущення, ймовірно, порушені.

Можна зайти в кролячу нору, підсиливши свої припущення, і я змінивши відповідь, щоб все-таки їх задовольнити, але я не думаю, що це було б корисно.

Розробка хороших ієрархій класів є складною, і вона потребує великого розуміння вашої ділової сфери. Для складних доменів потрібно пройти дві, три або навіть більше ітерації, оскільки вони уточнюють своє розуміння того, як взаємодіють різні суб'єкти у їх бізнес-домені, поки вони не знайдуть адекватну модель.

Ось де бракує спрощених прикладів для тварин. Ми хочемо навчити простому, але проблема, яку ми намагаємося вирішити, не є очевидною, поки не заглибишся, тобто є більш складні міркування та сфери.


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.