Кілька випадків використання спадщини


15

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

Цікаво, чи Java (зі своєю екосистемою) справді "проста". Python не є складним і має багаторазове успадкування. Отже, не будучи занадто суб'єктивним, моє питання ...

Назвіть типові схеми проблем, які виграють від коду, призначеного для широкого використання багаторазового успадкування


8
Java опускає надзвичайно багато непоганих речей з дуже малих причин. Я не очікував гідного виправдання для ІМ.
DeadMG

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

Я думаю, що причина того, що Java не містить багаторазового успадкування, полягає в тому, що розробники Java хотіли, щоб їх мову було легко вивчити. Багаторазове успадкування, хоча неймовірно потужне в деяких випадках, важко зрозуміти і навіть важче використовувати для хорошого ефекту; це не та річ, з якою ви хотіли б зіткнутися з першокурсником із програмування. Я маю на увазі: як ви пояснюєте віртуальну спадщину тому, хто бореться з самою концепцією спадкування? А оскільки багатократне успадкування також не зовсім тривіальне на стороні впроваджувачів, можливо, Java-розробник, однак, опускаючи це - виграш-виграш.
cmaster - відновити моніку

Java номінально набрана. Python не є. Це робить багатократне успадкування набагато простішим як для реалізації, так і для розуміння в Python.
Жуль

Відповіді:


11

Плюси:

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

Мінуси:

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

У C ++ хороший приклад множинного успадкування, що використовується для складання ортогональних ознак, - це коли ви використовуєте CRTP для, наприклад, налаштування компонентної системи для гри.

Я почав писати приклад, але думаю, що реальний приклад світу варто більше переглянути. Деякий код Ogre3D приємно і дуже інтуїтивно використовує багатократне успадкування. Наприклад, клас Mesh успадковується як від Resources, так і від AnimationContainer. Ресурси відкривають загальний для всіх ресурсів інтерфейс, а AnimationContainer розкриває інтерфейс, специфічний для маніпулювання набором анімацій. Вони не пов'язані між собою, тому легко подумати про Меш як ресурс, який на додачу може поєднати набір анімацій. Відчуває себе природним, чи не так?

Ви можете переглянути інші приклади в цій бібліотеці , як, наприклад, управління розподілом пам’яті оштрафованим зерном шляхом змушення класів успадковувати варіанти варіантів класу CRTP, перевантажуючи нові та видаляючи.

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

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


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

Гаразд, я спробую щось додати.
Клайм

Ogre3D - це не будь-яке місце, на яке я хотів би звернутися до дизайнерського натхнення - ви бачили їхню інфекцію Singleton?
DeadMG

По-перше, спадкоємець одинака насправді не одинак, побудова та знищення явні. Далі, Ogre - це шар над апаратною системою (або графічним драйвером, якщо вам зручніше). Це означає, що має бути лише одне унікальне представлення для системних інтерфейсів (наприклад, Root чи інші). Вони можуть видалити одинаків, але тут справа не в цьому. Я добровільно уникав вказати на це, щоб уникнути обговорення тролів, тому, будь ласка, подивіться на приклади, які я вказав. Їх використання Синглтона може бути не ідеальним, але воно, очевидно, корисне на практиці (але тільки для їхньої системи не все).
Клайм

4

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


+1 це насправді є вагомою причиною отримання багаторазового спадкування. Міксини містять додаткові конотації ("цей клас не слід використовувати як самостійний")
ashes999

2

Я думаю, що вибір в основному ґрунтується на питаннях, зумовлених алмазною проблемою .

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

Я не впевнений у значенні вашого останнього запитання. Але якщо це "в яких випадках корисне багатократне успадкування?", То в усіх випадках, коли ви хочете мати об'єкт A, що має функціональні можливості об'єктів B і C, в основному.


2

Я не буду тут дуже сильно заглиблюватися, але ви точно можете зрозуміти багатократне успадкування в python за наступним посиланням http://docs.python.org/release/1.5.1p1/tut/multiple.html :

Єдине правило, необхідне для пояснення семантики - це правило роздільної здатності, яке використовується для посилань на атрибути класу. Це на першу глибину, зліва направо. Таким чином, якщо атрибут не знайдений в DerivedClassName, його шукають у Base1, потім (рекурсивно) в базових класах Base1, і лише якщо його там не знайти, він шукається в Base2 тощо.

...

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

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


1

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


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

1

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

Це лише один приклад, але мені здається, що для підвищення безпеки та пом’якшення спокус застосовувати каскадні зміни для будь-яких абонентів або підкласів.

Там, де я виявив багатонаступне успадкування неймовірно корисним навіть для самих абстрактних інтерфейсів без стану, це ідіома невіртуального інтерфейсу (NVI) в C ++.

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

Простий приклад (деякі можуть перевірити, що передана ручка файлу відкрита чи щось подібне):

// Non-virtual interface (public methods are nonvirtual/final).
// Since these are modeling the concept of "interface", not ABC,
// multiple will often be inherited ("implemented") by a subclass.
class SomeInterface
{
public:
    // Pre: x should always be greater than or equal to zero.
    void f(int x) /*final*/
    {
        // Make sure x is actually greater than or equal to zero
        // to meet the necessary pre-conditions of this function.
        assert(x >= 0);

        // Call the overridden function in the subtype.
        f_impl(x);
    }

protected:
    // Overridden by a boatload of subtypes which implement
    // this non-virtual interface.
    virtual void f_impl(int x) = 0;
};

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

Таку перевірку безпеки буде важко провести у всіх 1000 місць, куди дзвонять, fабо всіх 100 місць, які перекривають f_impl.

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

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

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


0

По-перше: кілька копій базового класу (випуск C ++) та щільне з'єднання між базовим та похідним класами.

Друге: багаторазове успадкування від абстрактних інтерфейсів


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