Чому спадщина та поліморфізм настільки широко використовуються?


18

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

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

Але цього набагато простіше досягти за допомогою набору качок, будь то в динамічній мові, як Python, або в статичній мові на зразок C ++.

Як приклад, розглянемо наступну функцію Python з наступним її статичним еквівалентом C ++:

def foo(obj):
   obj.doSomething()

template <class Obj>
void foo(Obj& obj)
{
   obj.doSomething();
}

Еквівалент OOP буде чимось на зразок наступного коду Java:

public void foo(DoSomethingable obj)
{
  obj.doSomething();
}

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

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


Я не експерт з Python, тому я мушу запитати: що буде, якщо objнемає doSomethingметоду? Чи піднято виняток? Нічого не відбувається?
FrustratedWithFormsDesigner

6
@ Frustrated: Піднімається дуже чіткий конкретний виняток.
dimimcha

11
Хіба не качка типізує просто поліморфізм до одинадцяти? Я здогадуюсь, що ви не засмучені OOP, а його статично вписаними втіленнями. Я можу це зрозуміти, насправді, але це не робить OOP в цілому поганою ідеєю.

3
Вперше читайте "широко" як "дико" ...

Відповіді:


22

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

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

Підсумок: Я погоджуюся, що успадкування та поліморфізм у стилі Java може бути ПДФА, а альтернативи слід використовувати частіше, але це все ж має свої переваги.


29

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

Справа не в тому, що їх широко навчають у школах, це назад: вони широко навчаються в школах, тому що люди (відомі як ринок) виявили, що вони працюють краще, ніж старі інструменти, і тому школи почали їх навчати. [Анекдот: коли я вперше навчався ООП, було надзвичайно важко знайти коледж, який викладав будь-яку мову ООП. Через десять років було важко знайти коледж, який не викладав мови ООП.]

Ти сказав:

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

Я кажу:

Ні, це не так

Те, що ви описуєте, - це не поліморфізм, а спадщина. Недарма у вас проблеми з ООП! ;-)

Створіть резервний крок: поліморфізм - це користь передачі повідомлень; це просто означає, що кожен об’єкт вільний відповісти на повідомлення по-своєму.

Отже ... Введення качки - це, а точніше, уможливлення) поліморфізму

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

Це статична та динамічна річ, яка є дискусією, такою ж старою, як Лісп, і не обмежується OOP.


2
+1 для "типи качок - це поліморфізм". Поліморфізм і успадкування занадто довго підсвідомо зв'язані разом, а сувора статична типізація є єдиною реальною причиною, якою вони коли-небудь мали бути.
cHao

+1. Я думаю, що статичне та динамічне розрізнення повинно бути більше ніж побічна нота - це в основі різниці між типом качок та поліморфізмом у стилі java.
Сава Б.

8

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

Чому?

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

Це все. Немає "зайвого обмеження". Це спрощення.

Поліморфізм, як і те, що означає "типи качок". Ті ж методи. Багато класів з ідентичним інтерфейсом, але різними реалізаціями.

Це не обмеження. Це спрощення.


7

Спадщиною зловживають, але так само, як і качка набирає текст. І те, і інше може призвести до проблем.

Завдяки сильному набору тексту ви отримуєте багато "одиничних тестів", зроблених під час компіляції. Увівши качку, вам часто доводиться їх писати.


3
Ваш коментар щодо тестування стосується лише динамічного набору качок. Статичний набір качок у стилі C ++ (із ​​шаблонами) дає помилки під час компіляції. (Хоча, надані помилки в шаблоні C ++ досить важко розшифрувати, але це зовсім інше питання.)
Channel72,

2

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

Тоді, якщо ви це навчилися догматично, пізніше ви можете "повстати" так само догматично в іншому напрямку. Це теж не добре.

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


2

Так, статичне введення та інтерфейси - це обмеження. Але все, відколи було винайдено структуроване програмування (тобто "перебуває як шкідливе"), нас обмежує. Дядько Боб чудово сприйняв це у своєму відеоблозі .

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

Розслаблення обмежень шляхом (повторного) введення динамічного набору тексту та навіть прямого доступу до пам’яті є дуже потужною концепцією, однак це може також ускладнити справу багатьох програмістів. Особливо програмісти звикли покладатися на компілятор і вводити безпеку протягом більшої частини своєї роботи.


1

Спадщина - це дуже міцний зв’язок між двома класами. Я не думаю, що у Java є сильніший. Тому використовувати його слід лише тоді, коли ви це маєте на увазі. Публічна спадщина - це відносини "є-а", а не "зазвичай є -а". Це справді, дуже просто зловживати спадщиною і закінчуватися безладом. У багатьох випадках успадкування використовується для представлення "has-a" або "take-function-from-a", і це, як правило, краще виконано за складом.

Поліморфізм є прямим наслідком відносин "є-а". Якщо похідне успадковується від Base, то кожна похідна "є-а" база, і тому ви можете використовувати похідне де б ви не використовували базу. Якщо це не має сенсу в ієрархії спадкування, то ієрархія помилкова, і, мабуть, надто багато спадкування відбувається.

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

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

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


0

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

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

В основному це правильний інструмент для роботи: традиційний OOP може працювати дуже добре, коли у вас є модель, яка йому підходить, і закриття можуть працювати дуже добре, коли ви цього не робите.


-1

Істина лежить десь посередині. Мені подобається, що мова, що набирає статичну форму C # 4.0, підтримує "введення качки" за ключовим словом "динамічний".


-1

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

public class Animal {
    public virtual string Sound () {
        return "Some Sound";
    }
}

public class Dog : Animal {
    public override string Sound () {
        return "Woof";
    }
}

public class Cat : Animal {
    public override string Sound () {
        return "Mew";
    }
}

public class GoldenRetriever : Dog {

}

Тут клас GoldenRetrieverтакий самий, Soundяк і Dogза безкоштовну подяку за спадщину.

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

data Animal = Animal | Dog | Cat | GoldenRetriever

sound :: Animal -> String
sound Animal = "Some Sound"
sound Dog = "Woof"
sound Cat = "Mew"
sound GoldenRetriever = "Woof"

Ось тут ви не уникнути того , щоб вказати soundна GoldenRetriever. Найпростіше було б взагалі

sound GoldenRetriever = sound Dog

але просто зображення, якщо у вас є 20 функцій! Якщо є експерт Haskell, будь ласка, покажіть нам простіший спосіб.

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


5
Я клянусь, Animalє антиуроком OOP.
Теластин

@Telastyn Я дізнався приклад від Дерека Банаса в Youtube. Чудовий вчитель. На мою думку, це візуалізувати та обґрунтувати дуже просто. Ви можете редагувати публікацію з кращим прикладом.
Крістіан Гарсія

це, здається, не додає нічого істотного за попередні 10 відповідей
gnat

@gnat Поки «субстанція» вже там, людина, новачка в цій темі, може знайти приклад, простіше зрозуміти.
Крістіан Гарсія

2
Тварини та форми справді погано застосовують поліморфізм з кількох причин, про які йшлося в на-сайті. В основному, сказати кішку "- це" тварина безглуздо, оскільки конкретної "тварини" немає. Те, що ви насправді маєте на увазі моделювати, - це поведінка, а не ідентичність. Визначення інтерфейсу "CanSpeak", який може бути реалізований як мяу чи кору, має сенс. Визначення інтерфейсу "HasArea", який можуть реалізувати квадрат і коло має сенс, але не є загальною формою.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.