Множинне успадкування в Java
Множинне наслідування дає можливість створити клас, успадкований від кількох суперкласів. На відміну від деяких інших популярних об'єктно-орієнтованих мов програмування, таких як С++ Java заборонено множинне успадкування від класів. Java не підтримує множинне спадкування класів тому, що це може призвести до ромбоподібної проблеми. І замість того, щоб шукати способи вирішення цієї проблеми, є найкращі варіанти, як ми можемо досягти того ж самого результату як множинне спадкування.Ромбоподібна проблема
Для легшого розуміння ромбовидної проблеми, давайте припустимо, що множинне спадкування підтримується в Java. У цьому випадку, у нас могла б бути ієрархія класів, як показано на зображенні нижче. Припустимо, що класSuperClass
є абстрактним і у ньому оголошено певний метод. І конкретні класи ClassA
та ClassB
.
package com.journaldev.inheritance;
public abstract class SuperClass {
public abstract void doSomething();
}
package com.journaldev.inheritance;
public class ClassA extends SuperClass{
@Override
public void doSomething(){
System.out.println("doSomething implementation of A");
}
//ClassA own method
public void methodA(){
}
}
package com.journaldev.inheritance;
public class ClassB extends SuperClass{
@Override
public void doSomething(){
System.out.println("doSomething implementation of B");
}
//ClassB specific method
public void methodB(){
}
}
Тепер припустимо ми хочемо реалізувати ClassC
і успадкувати його від ClassA
і ClassB
.
package com.journaldev.inheritance;
public class ClassC extends ClassA, ClassB{
public void test(){
//calling super class method
doSomething();
}
}
Зверніть увагу, що метод test()
викликає метод суперкласу doSomething()
. Це призводить до неоднозначності, оскільки компілятор не знає, який метод суперкласу виконуватиме. Це є діаграма класів ромбоподібної форми, яка називається ромбоподібною проблемою. Це є головна причина, через яку Java не підтримується множинне спадкування. Зауважте, що вищезгадана проблема з багаторазовим успадкуванням класів може статися лише з трьома класами, які мають хоч один загальний метод.
Множинне успадкування Інтерфейсів
У Java множинне спадкування не підтримується в класах, але воно підтримується в інтерфейсах. І один інтерфейс може розширювати багато інших інтерфейсів. Нижче наведено простий приклад.package com.journaldev.inheritance;
public interface InterfaceA {
public void doSomething();
}
package com.journaldev.inheritance;
public interface InterfaceB {
public void doSomething();
}
Зверніть увагу, що обидва інтерфейси оголошують той самий метод. Тепер ми можемо створити інтерфейс, що розширює обидва ці інтерфейси, як наведено в прикладі нижче.
package com.journaldev.inheritance;
public interface InterfaceC extends InterfaceA, InterfaceB {
//same method is declared in InterfaceA and InterfaceB both
public void doSomething();
}
Це добре працює, тому що інтерфейси тільки оголошують способи, а реалізація буде виконана в класах, що успадковують інтерфейс. Таким чином немає ніякої можливості отримати неоднозначність у множинному наслідуванні інтерфейсів.
package com.journaldev.inheritance;
public class InterfacesImpl implements InterfaceA, InterfaceB, InterfaceC {
@Override
public void doSomething() {
System.out.println("doSomething implementation of concrete class");
}
public static void main(String[] args) {
InterfaceA objA = new InterfacesImpl();
InterfaceB objB = new InterfacesImpl();
InterfaceC objC = new InterfacesImpl();
//all the method calls below are going to same concrete implementation
objA.doSomething();
objB.doSomething();
objC.doSomething();
}
}
Зверніть увагу, що кожного разу, перевизначаючи будь-який метод суперкласу або реалізуючи метод інтерфейсу, використовуйте анотацію @Override
. Що робити, якщо ми хочемо використовувати функцію methodA()
, класу ClassA
та функцію methodB()
, класу ClassB
в класі ClassC
? Рішення перебуває у використанні композиції. Нижче представлена версія класу ClassC
, яка використовує композицію, щоб визначити обидва методи класів і метод doSomething()
одного з об'єктів.
package com.journaldev.inheritance;
public class ClassC{
ClassA objA = new ClassA();
ClassB objB = new ClassB();
public void test(){
objA.doSomething();
}
public void methodA(){
objA.methodA();
}
public void methodB(){
objB.methodB();
}
}
Композиція проти успадкування
Однією з найкращих практик програмування на Java є схвалення композиції перед успадкуванням. Ми вивчимо деякі аспекти, які схвалюють цей підхід.-
Припустимо, що ми маємо суперклас і клас, що розширює його:
package com.journaldev.inheritance; public class ClassC{ public void methodC(){ } } package com.journaldev.inheritance; public class ClassD extends ClassC{ public int test(){ return 0; } }
Наведений вище код компілюється та працює добре. Але, якщо ми змінимо реалізацію класу
ClassC
, як показано нижче:package com.journaldev.inheritance; public class ClassC{ public void methodC(){ } public void test(){ } }
Зверніть увагу, що метод
test()
вже існує в підкласі, але тип значення, що повертається, відрізняється. Тепер класClassD
не буде компілюватися і якщо ви будете використовувати якийсь IDE, то вона вам запропонує змінити тип значення, що повертається в суперкласі або підкласі.Тепер уявіть собі ситуацію, коли ми маємо багаторівневу ієрархію спадкування класів і не маємо доступу до суперкласу. У нас не буде жодного вибору, окрім як змінити нашу сигнатуру методу підкласу чи його ім'я, щоб видалити помилку компіляції. Також ми маємо змінити метод підкласу у всіх місцях, де він викликається. Таким чином, успадкування робить наш код крихким.
Вищезгадана проблема ніколи не станеться з композицією і це робить її привабливішою до спадкування.
-
Інша проблема з наслідуванням полягає в тому, що ми надаємо всі методи суперкласу клієнту і якщо наш суперклас належним чином не розроблений і є прогалини в системі безпеки, то навіть при тому, що ми найкраще виконуємо реалізацію нашого класу, на нас впливає погана реалізація суперкласу . Композиція допомагає нам у забезпеченні керованого доступу до методів суперкласу, тоді як успадкування не забезпечує управління методами суперкласу. Це також одна з основних переваг композиції від спадкування.
-
Ще одна перевага композиції – те, що вона забезпечує гнучкість у викликі методів. Наша реалізація класу
ClassC
представлена вище не оптимальна і забезпечує зв'язування часу компіляції з методом, який буде викликатися. З мінімальними змінами ми можемо зробити виклик методу гнучким та динамічним.package com.journaldev.inheritance; public class ClassC{ SuperClass obj = null; public ClassC(SuperClass o){ this.obj = o; } public void test(){ obj.doSomething(); } public static void main(String args[]){ ClassC obj1 = new ClassC(new ClassA()); ClassC obj2 = new ClassC(new ClassB()); obj1.test(); obj2.test(); } }
Результат програми представленої вище:
doSomething implementation of A doSomething implementation of B
Ця гнучкість при виклик методу не доступна при успадкування, що додає ще один плюс на користь вибору композиції.
-
Побічні тестування легше робити при композиції, тому що ми знаємо, що всі методи ми використовуємо від суперкласу і можемо копіювати їх для тесту. Тоді як у спадкуванні ми залежимо переважно від суперкласу і не знаємо всіх методів суперкласу, які будуть використовуватися. Таким чином, ми повинні тестувати всі методи суперкласу, що є додатковою роботою через спадкування.
В ідеалі ми повинні використовувати успадкування лише коли ставлення підкласу до суперкласу визначається як «є». В інших випадках рекомендується використовувати композицію.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ