JavaRush /Java блог /Random UA /Навіщо потрібний поліморфізм?
Павел
11 рівень

Навіщо потрібний поліморфізм?

Стаття з групи Random UA
Навіщо потрібні інтерфейси? Навіщо потрібне успадкування інтерфейсів? Навіщо потрібний поліморфізм? Для тих, хто шанував визначення поліморфізму та реалізував кілька прикладів з інтерфейсами, але не зрозумів, навіщо він потрібний. Є звички погані, є хороші, є звички індивідуальні, є поширені. Але яка б поширена звичка не була, кожна людина робить її зі своїми нюансами. Наприклад, моя улюблена звичка – спати. Всі люди сплять по-різному, і сім'ї Іванових – це теж має місце. Навіщо потрібний поліморфізм?  - 1Тато спить на спині та похропує, а Мама спить на правому боці і штовхається. Перенесемо сонне царство у світ Java. Знаючи сенс інтерфейсів, звичка спати буде такою:
public interface ПривычкаСпать {
     String якСпит();
}

public class Папа implements ПривычкаСпать {

    @Override
    public String якСпит() {
        return "Папа спит на спине и похрапывает";
    }
}

public class Мама implements ПривычкаСпать {

    @Override
    public String якСпит() {
        return "Мама спит на правом боку и пинается";
    }
}
Щоб точно сказати, хто як спить, потрібно підійти до нього і подивитися хто це тут спить і потім ми вже точно зможемо дізнатися, чи почуємо ми хропіння або отримаємо стусан ногою. Підходитимемо випадковим порядком, то до Папи, то до Мами. У класі з методом main створимо метод, який у випадковому порядку повертатиме нам то Папу , то Маму .
public class Спальня {
    public static void main(String[] args) {

    }

    public static Object посмотретьКтоСпит() {
        int a = 1 + (int) (Math.random() * 2);
        if (a == 1) {
            return new Мама();
        }
        if (a == 2) {
            return new Папа();
        }
        return null;
    }
}
Ми не знаємо заздалегідь, кого саме поверне метод подивитися Хто Спит () , тому тип об'єкта, що повертається, буде загальний для всіх - Object . Щоб перевірити як метод працює в main запишемо конструкцію, яка викличе метод, що перевіряється, 10 разів, і надрукує клас отриманого об'єкта:
for (int i = 0; i < 10; i++) {
    Object случайный = посмотретьКтоСпит ();
    System.out.println(случайный.getClass());
}
При запуску в консоль виведеться випадкова кількість Мам та Пап.
class Папа class Папа class Мама class Папа class Папа class Мама class Мама class Мама class Папа class Мама
Але повернемось до сну. Нам треба розуміти, хто як спить. Метод подивитися Хто спить () ; поверне випадковий об'єкт, і записати ось так:
Object случайный = посмотретьКтоСпит ();
System.out.println(случайный.якСпит());
У нас не вийде. Тому що змінна випадкова має тип Object , а Object немає такого методу, він є тільки у Папи чи Мами , але кого це зупиняє? Зараз зробимо приведення типу Object до Папи або Мами , залежно від отриманого класу та всього ділу. Пишемо (хочете switch, хочете if, я вибрав if):
for (int i = 0; i < 10; i++) {
    Object случайный = посмотретьКтоСпит();

    if (случайный.getClass().equals(Мама.class)) {
        Мама мама = (Мама) случайный;
        System.out.println(мама. якСпит());
    }
    if (случайный.getClass().equals(Папа.class)) {
        Папа папа = (Папа) случайный;
        System.out.println(папа. якСпит());
    }
}
На виході отримаємо відмінний результат зі сплячих Мам та Пап, справа закрита!
Папа спить на спині і похропує Папа спить на спині і похропує Мама спить на правому боці і пинається Папа спить на спині і похропує Папа спить на спині і похропує Папа спить на спині і похропує Мама спить на правому боці і пинається Тато спить на спині і похропує Мама спить на правому боці і штовхається
І так би воно й було, якби не весь час мінливий світ. У сім'ї Іванових є ще дві дитини, вони теж сплять по-своєму, їх треба перенести до Java світу. А якщо в гості до Іванових приїдуть Петроові зі своїми трійнятами, і вони теж сплять кожен по-своєму. У міру розширення програми, новими людьми-класами, які вміють спати, метод main перетвориться на вавилонську вежу з кількох сотень умов.
for (int i = 0; i < 10; i++) {
    Object случайный = посмотретьКтоСпит();

    if (неизвестный.getClass().equals(Мама.class)) {
        Мама мама = (Мама) random;
        System.out.println(мама. якСпит());
    }

    if (неизвестный.getClass().equals(Папа.class)) {
        Папа папа = (Папа) random;
        System.out.println(папа. якСпит());
    }

//тут еще миллион строк кода

}
Тобто для розширення програми така організація класів зовсім не підходить. Вона змушує нас писати багато одноманітного, здебільшого коду, що повторюється. Допоможе нам лише батько всієї ОВП, найсвітліший князь гнучкості — поліморфізм. Навіщо потрібний поліморфізм?  - 2Застосуємо поліморфізм і подивимося, що змінилося:
public class Спальня {

public static void main(String[] args) {
	for (int i = 0; i < 10; i++) {
    	  ПривычкаСпать случайный = посмотретьКтоСпит ();
          System.out.println(случайный.якСпит());
       }
}

public static ПривычкаСпать посмотретьКтоСпит() {
   //тут все без изменений
  }
}
У методі подивитися Хто Спит () змінився тип, що повертається, був загальний для всіх клас Object , став спільний тільки для Папи і Мами інтерфейс Звичка Спати . Значить явно змінювати типи з Object на Мама чи Папа вже не треба, перевіряти тип вхідного класу теж не треба. Можна додати хоч +100500 людей, які мають звичку спати, але метод mainбуде незмінним. Відвернемось. Особисто я маю думку, що об'єктно-орієнтоване програмування дуже схоже на складання текстів. Іменники більше підходять для класів, дієслова для методів, прикметники для класових полів. Наприклад пропозиція: "Червоний автомобіль їде." можна переписати в код:
public class Автомобиль {

String цвет = «Красный»;

public void ехать() {
 System.out.println(цвет + « автомобиль едет.»)
  }
}
Можна і код переписати як пропозицію, наприклад:
ПривычкаСпать случайный = посмотретьКтоСпит ();
System.out.println(случайный.якСпит());
На людському буде: «Виведи в консоль як спить випадковий звичка спати». Не надто по-людськи, правильніше було «Звичка Спати» замінити на «Людину» . Можна змінити найменування інтерфейсу Звичка Спати на Людину , тоді логіка з'явитися: є спільна Людина і в неї є метод як Спит () . Але знаючи про проблеми з різними звичками з минулої статті про успадкування, правильніше створити інтерфейс Людина та успадкуватися від інтерфейсу Звичка Спати . А класам Папа та Мама імплементувати інтерфейс Людина. Тоді все виглядатиме логічно: Звичка спати оголошує метод як спить
public interface ПривычкаСпать {
     String якСпит();
}
Людина теж виглядає як людина зі звичкою спати.
public interface Человек extends ПривычкаСпать {

}
Тато і Мама - люди
public class Папа implements Человек {
    @Override
    public String якСпит() {
        return "Папа спит на спине и похрапывает";
    }
}
public class Мама implements Человек {
    @Override
    public String якСпит() {
        return "Мама спит на правом боку и пинается";
    }
}
І в методі main теж все нормально, в консоль виводиться "Як спить випадкова людина":
public class Спальня {

public static void main(String[] args) {
	for (int i = 0; i < 10; i++) {
    	  Человек случайный = посмотретьКтоСпит ();
          System.out.println(случайный.якСпит());
       }
}

public static Человек посмотретьКтоСпит() {
   //тут все без изменений
  }
}
Резюмує за мене професор Боб Келсо. Навіщо потрібний поліморфізм?  - 3Дядечко Боб каже: що для кращого розуміння перепишіть код в IDEA і запускайте. Поліморфізм автоматично визначає конкретний тип об'єктів із загальним предком. Нам не потрібно писати перевірки на те – яким типом є об'єкт. Резюме для трьох статей: Тут описані найочевидніші (для мене) приклади використання інтерфейсів, наслідування та поліморфізму. Існують інші світи. Взагалі моя основна думка: «Додаток - це реалізація (абстракція) реального світу, а так як світ постійно змінюється, то і додаток постійно схильний до змін, не можна написати раз і назавжди. Процес внесення змін до додатку може бути довгим і не зрозумілим або швидким та зрозумілим. Це багато в чому залежить від організації коду, від організації класів, від дисциплінованого дотримання правил. Поняття розширюваності та внесення змін піднімають уявлення про програмування на новий рівень. Можливо, якщо розглядати ОВП через розширюваність та внесення змін, можна швидше зрозуміти це саме ОВП. Наступними вершинами після ООП стануть: SOLID, чистий код, архітектура додатків та патерни проектування. Їх так само можна розуміти через розширюваність та внесення змін.
Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ