Чому в Java чи C # заборонено множинне спадкування?


115

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


1
Так само хотілося б зазначити, що там є рамки, які дозволяють поведінку, подібну MI, у C # класах. І звичайно, у вас є можливість мати безкомпонентні міксини (використовуючи методи розширення) - однак без громадянства вони не дуже корисні.
Дмитро Нестерук

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

Відповіді:


143

Коротка відповідь: адже мовні дизайнери вирішили цього не робити.

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

Для більш цікавого та поглибленого читання, в Інтернеті доступні деякі статті з інтерв'ю деяких мовних дизайнерів. Наприклад, для .NET Кріс Брумме (який працював у MS над CLR) пояснив причини, чому вони вирішили не:

  1. Різні мови насправді мають різні очікування щодо того, як працює ІМ. Наприклад, як вирішуються конфлікти та об'єднуються чи зайві дублюючі бази. Перш ніж ми зможемо застосувати MI в CLR, ми повинні провести опитування всіх мов, розібратися у загальних поняттях та вирішити, як їх висловити мовно-нейтрально. Ми також мусимо вирішити, чи належить MI в CLS, і що це означатиме для мов, які не хочуть цієї концепції (наприклад, VB.NET, наприклад). Звичайно, це бізнес, в якому ми працюємо, як загальна мова, але ми ще не зробили цього для MI.

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

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

Повну статтю ви можете прочитати тут.

Для Java ви можете прочитати цю статтю :

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

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


10
Приємне порівняння. Цілком вказує на думки, які лежать в основі обох платформ.
CurtainDog

97

Багаторазове успадкування реалізації - це те, що не дозволено.

Проблема полягає в тому, що компілятор / час виконання не може зрозуміти, що робити, якщо у вас є клас Cowboy і Artist, як з реалізаціями для методу draw (), так і ви спробуєте створити новий тип CowboyArtist. Що відбувається, коли ви викликаєте метод draw ()? Хтось лежить мертвим на вулиці, чи у вас прекрасна акварель?

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


80
Ви отримуєте чудову акварель когось, хто загинув на вулиці :-)
Dan F

Це єдина проблема? Я думаю, я не впевнений, що C ++ вирішує цю проблему віртуальним ключовим словом, це правда? Мені не добре в С ++
Абдулсаттар Мохаммед

2
У C ++ ви можете або вказати, яку з функцій базового класу потрібно викликати у похідному, або повторно реалізувати його самостійно.
Дмитро Різенберг

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

Вам просто знадобиться список пріоритетів. Це використовується в об'єктній системі Common Lisp, CLOS. Усі класи утворюють герахію і метод, який називається, визначається звідти. Крім того, CLOS дозволяє визначити різні правила, щоб CowboyArtist.draw () спершу міг намалювати Ковбоя, а потім Виконавця. Або що ви коли-небудь складаєте.
Себастьян Крог

19

Причина: Java дуже популярна і легко кодується через свою простоту.

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

  1. Вони уникали покажчиків
  2. Вони уникали багаторазового спадкування.

Проблема з багаторазовим успадкуванням: Алмазна проблема.

Приклад :

  1. Припустимо, що клас A має метод fun (). клас В і клас С походять від класу А.
  2. І обидва класи В і С переосмислюють метод fun ().
  3. Тепер припустимо, що клас D успадковує і клас B, і C. (тільки припущення)
  4. Створення об’єкта для класу D.
  5. D d = новий D ();
  6. і спробувати отримати доступ до d.fun (); => Чи буде це називати забавою класу В () або веселості класу С ()?

Це неоднозначність, що існує в алмазній проблемі.

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

Примітка . Але будь-яким способом завжди можна побічно реалізувати декілька спадкових систем, використовуючи інтерфейси.


1
"Але будь-яким способом ви завжди можете реалізувати кілька успадкованих побічно, використовуючи інтерфейси." - що вимагає від програміста кожного разу, знову і знову задавати основу методів.
Карі

13

Оскільки у Java сильно відрізняється філософія дизайну від C ++. (Я не збираюся тут обговорювати C #.)

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

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

Крім того, Java значною мірою була реакцією з боку C ++ та Smalltalk, найвідоміших мов ОО. Існує багато інших мов OO (звичайний Lisp був фактично першим, хто стандартизувався), з різними системами OO, які краще обробляють MI.

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

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


12

Основна (хоча аж ніяк не єдина) причина, чому люди відволікаються від MI, - це так звана "алмазна проблема", що призводить до неоднозначності у вашій реалізації. Ця стаття у Вікіпедії обговорює це та пояснює краще, ніж я міг. ІМ може також призвести до більш складного коду, і багато дизайнерів OO стверджують, що MI вам не потрібен, і якщо ви його використовуєте, ваша модель, ймовірно, помиляється. Я не впевнений, що згоден з цим останнім пунктом, але простота речей - це завжди хороший план.


8

У C ++ багатократне успадкування було головним головним болем при неправильному використанні. Щоб уникнути цих популярних дизайнерських проблем, натомість у сучасних мовах (java, C #) було вимушено кілька інтерфейсів.


8

Множинне спадкування є

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

Тому можна вважати мудрим вибором не включати в мову Java кілька успадкувань.


3
Я не згоден з усім вищесказаним, працюючи із загальною системою об'єктів Lisp.
Девід Торнлі

2
Динамічні мови не враховуються ;-) У будь-якій системі, схожої на Lisp, у вас є REPL, який робить налагодження досить простим. Крім того, CLOS (наскільки я це розумію, я ніколи не використовував його, лише читав про нього) - це мета-об'єкт-система, що має велику гнучкість та своєрідне ставлення. Але розглянемо статично скомпільовану мову на зразок C ++, де компілятор генерує якийсь нелегко складний пошук методу за допомогою декількох (можливо, що перекриваються) vtables: у такій реалізації з'ясування того, до якого методу було застосовано метод, може бути не надто тривіальним завданням.
mfx

5

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


4

Ще в старі часи (70-ті роки), коли комп'ютерні науки були більш науковими та менше масовим виробництвом, програмісти встигли подумати про гарний дизайн та хорошу реалізацію, і в результаті продукти (програми) мали високу якість (наприклад, дизайн TCP / IP та реалізація). Сьогодні, коли всі програмують, а менеджери міняють характеристики перед термінами, тонкі питання, наприклад, описані у посиланні на вікіпедію від поста Стіва Хей, важко відстежити; отже, "множинне успадкування" обмежене дизайном компілятора. Якщо вам це подобається, ви все одно можете використовувати C ++ .... і мати всю потрібну вам свободу :)


4
... включаючи свободу стріляти собі в ногу, кілька разів;)
Mario Ortegón

4

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

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


6
Але ви не успадковуєте інтерфейс, ви реалізуєте його. Інтерфейси - це не класи.
Blorgbeard вийшов

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

Візьміть інтерфейс Closeable: один метод, close (). Припустимо, ви хочете розширити це до Openable: у нього є метод open (), який встановлює булеве поле isOpen для true. Спроба відкрити Openable, яка вже відкрита, - це виняток, як і спроба закрити Openable, який не є відкритим ... Сотні більш цікавих генеалогій класів можуть захотіти прийняти таку функціональність ... не вибираючи розширювати кожен час від класу Openable. Якщо Openable був просто інтерфейсом, він не міг би забезпечити цю функціональність. QED: інтерфейси не забезпечують спадкування!
мійський гризун

4

Динамічне завантаження класів ускладнює реалізацію багаторазового успадкування.

Насправді в Java вони уникали складності багаторазового успадкування, використовуючи єдине успадкування та інтерфейс. Складність багаторазового успадкування дуже висока в ситуації, описаній нижче

алмазна проблема множинної спадщини. У нас є два класи B і C, успадковані від A. Припустимо, що B і C переосмислюють успадкований метод, і вони забезпечують власну реалізацію. Тепер D успадковує і B, і C, здійснюючи множинне спадкування. D повинен успадкувати цей переосмислений метод, jvm не може вирішити, який метод, який буде замінено, буде використовуватися?

У c ++ віртуальні функції використовуються для обробки, і ми повинні робити це явно.

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


3

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


2

У Java є поняття, тобто поліморфізм. У java існує 2 види поліморфізму. Існують методи перевантаження та переоцінка методів. Серед них переопределення методів відбувається із співвідношенням супер- та підкласів. Якщо ми створюємо об’єкт підкласу і застосовуємо метод надкласу, і якщо підклас поширюється на більше одного класу, який метод суперкласу слід називати?

Або, викликаючи конструктор надкласового super()класу, який конструктор суперкласу буде викликаний?

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


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

1

Багаторазове спадкування заборонено на Java безпосередньо, але через інтерфейси це дозволено.

Причина:

Множинне спадкування: вводиться більше складності та неоднозначності.

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

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

Давайте візьмемо простий приклад.

  1. Припустимо, є 2 суперкласи класи A і B з однаковими назвами методів, але різними функціональними можливостями. Через наступний код із (розширюється) ключовим словом багаторазове успадкування неможливо.

       public class A                               
         {
           void display()
             {
               System.out.println("Hello 'A' ");
             }
         }
    
       public class B                               
          {
            void display()
              {
                System.out.println("Hello 'B' ");
              }
          }
    
      public class C extends A, B    // which is not possible in java
        {
          public static void main(String args[])
            {
              C object = new C();
              object.display();  // Here there is confusion,which display() to call, method from A class or B class
            }
        }
  2. Але через інтерфейси за допомогою (реалізує) ключового слова можливе множинне успадкування.

    interface A
        {
           // display()
        }
    
    
     interface B
        {
          //display()
        }
    
     class C implements A,B
        {
           //main()
           C object = new C();
           (A)object.display();     // call A's display
    
           (B)object.display(); //call B's display
        }
    }

0

Хтось може мені точно сказати, чому це не дозволено?

Відповідь можна знайти за цим посиланням на документацію

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

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

  1. Що робити, якщо методи або конструктори з різних суперкласів створюють одне і те ж поле?

  2. Який метод чи конструктор матиме перевагу?

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

Множинне успадкування типу : Можливість класу реалізувати більше одного інтерфейсу.

Багатократне успадкування реалізації (через методи за замовчуванням в інтерфейсах): Можливість успадкування визначень методів з декількох класів

Щоб отримати додаткову інформацію, зверніться до цього пов'язаного з питаннями SE

Кілька неоднозначностей спадкування з інтерфейсом


0

У C ++ клас може успадковувати (прямо чи опосередковано) більш ніж один клас, який називається множинним успадкуванням .

Однак C # і Java обмежують класи на одинакове успадкування, кожен клас успадковує від одного батьківського класу.

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

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

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

class A {
    protected:
    bool flag;
};
class B : public A {};
class C : public A {};
class D : public B, public C {
    public:
    void setFlag( bool nflag ){
        flag = nflag; // ambiguous
    }
};

У цьому прикладі flagчлен даних визначається за допомогою class A. Але class Dсходить class B і class C, які обидва походять з A, так що по суті дві копії з flagдоступні , тому що два примірника Aв Dієрархії класів «S. Який ви хочете встановити? Компілятор поскаржиться, що посилання на flagв D- неоднозначне . Одне виправлення полягає в тому, щоб явно розмежувати посилання:

B::flag = nflag;

Ще одне виправлення полягає в оголошенні B і C як virtual base classes, що означає, що в ієрархії може існувати лише одна копія A, усуваючи будь-яку неоднозначність.

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

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


-1

Уявіть собі цей приклад: у мене є клас Shape1

Він має CalcualteAreaметод:

Class Shape1
{

 public void CalculateArea()

     {
       //
     }
}

Є ще один клас, у Shape2якого також є той самий метод

Class Shape2
{

 public void CalculateArea()

     {

     }
}

Зараз у мене є дочірній клас Circle, він походить від Shape1 і Shape2;

public class Circle: Shape1, Shape2
{
}

Тепер, коли я створюю об'єкт для Circle і викликаю метод, система не знає, який метод обчислення площі слід викликати. Обидва мають однакові підписи. Так компілятор заплутається. Ось чому багаторазові успадкування не допускаються.

Але може бути декілька інтерфейсів, оскільки інтерфейси не мають визначення методу. Навіть обидва інтерфейси мають однаковий метод, обидва вони не мають жодної реалізації, і завжди метод у дочірньому класі буде виконуватися.


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