JavaRush /Java блог /Random UA /Множинне успадкування в Java. Композиція порівняно з Спад...
DSergey_Kh
12 рівень

Множинне успадкування в Java. Композиція порівняно з Спадкуванням

Стаття з групи Random UA

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

Множинне наслідування дає можливість створити клас, успадкований від кількох суперкласів. На відміну від деяких інших популярних об'єктно-орієнтованих мов програмування, таких як С++ Java заборонено множинне успадкування від класів. Java не підтримує множинне спадкування класів тому, що це може призвести до ромбоподібної проблеми. І замість того, щоб шукати способи вирішення цієї проблеми, є найкращі варіанти, як ми можемо досягти того ж самого результату як множинне спадкування.

Ромбоподібна проблема

Для легшого розуміння ромбовидної проблеми, давайте припустимо, що множинне спадкування підтримується в Java. У цьому випадку, у нас могла б бути ієрархія класів, як показано на зображенні нижче. Множинне успадкування в Java.  Композиція порівняно з Спадкуванням - 1Припустимо, що клас 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 є схвалення композиції перед успадкуванням. Ми вивчимо деякі аспекти, які схвалюють цей підхід.
  1. Припустимо, що ми маємо суперклас і клас, що розширює його:

    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, то вона вам запропонує змінити тип значення, що повертається в суперкласі або підкласі.

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

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

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

  3. Ще одна перевага композиції – те, що вона забезпечує гнучкість у викликі методів. Наша реалізація класу 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

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

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

    В ідеалі ми повинні використовувати успадкування лише коли ставлення підкласу до суперкласу визначається як «є». В інших випадках рекомендується використовувати композицію.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ