Рубріки: Теорія

Поліморфізм у мовах програмування: види та приклади, як використовувати в коді

Ольга Змерзла

Що таке поліморфізм у програмуванні

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

У програмуванні поліморфізм працює так само. Об’єкти можуть набувати різних форм залежно від контексту, в якому вони використовуються.

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

Поліморфізм — це концепція програмування, яка дозволяє обробляти об’єкти різних типів так, ніби вони належать до одного типу, і по-різному реагувати на одні й ті самі методи чи функції.

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

Наприклад, у вас є функція, яка приймає на вхід об’єкт Animal. Вона може працювати з будь-яким типом тварин: кішкою, собакою, хом’ячком. У кожного типу тварин буде своя реалізація функції, але самій функції не потрібно знати, з яким конкретним типом тварин вона працює. Саме ця особливість поліморфізму дозволяє з його допомогою створювати гнучкий та повторно використовуваний код.

Історія виникнення поліморфізму у мовах програмування

Поліморфізм виник, коли сформувалася концепція об’єктно-орієнтованого програмування (ООП), що зробило його невід’ємною частиною ООП, і однією з ключових складових мов програмування загалом.

Ідею поліморфізму вперше представив у середині 1960-х Крістофер Стрейчі у своїй статті «Фундаментальні концепції мов програмування». Але лише наприкінці 1970-х — початку 1980-х років ця концепція почала впроваджуватися у мови програмування.

Однією з перших мов програмування, які використовують поліморфізм, стала Simula (розроблена наприкінці 1960-х – початку 1970-х років).

Simula розробляли для симуляції та моделювання. Вона представила концепцію об’єктів, класів та успадкування, завдяки чому стала першою мовою, яка використовує термін «віртуальний» для опису поліморфізму.

Smalltalk, яку створили в 1970-ті роки, теж один із ранніх прикладів мови програмування, де було введено концепцію поліморфізму.

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

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

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

Мова Java, яка з’явилася в середині 1990-х років, також активно використовує поліморфізм. Основним завданням розробників Java було створення універсальної мови, яка могла б працювати на будь-яких пристроях. Використання концепції поліморфізму стало ключовим елементом досягнення цієї мети.

Зокрема, Java представила концепцію інтерфейсів, яка дозволяла класам реалізовувати кілька інтерфейсів та успадковувати поведінку з кількох джерел.

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

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

Переваги та недоліки поліморфізму

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

Почнемо з переваг:

  1. Можливість повторного використання коду. Поліморфізм спрощує повторне використання та перепризначення коду. Це може заощаджувати час та зусилля під час розробки програмного забезпечення, тому що програмісти можуть використовувати вже існуючий код для створення нових функцій.
  2. Гнучкість. Поліморфізм дозволяє зробити код більш гнучким і адаптованим, тому що різні об’єкти можна розглядати так, ніби вони належать одному типу. Також, оскільки зміни можна вносити, не впливаючи на поведінку інших частин програми, це робить код більш надійним і менш схильним до помилок.
  3. Зручність обслуговування. Поліморфізм спрощує обслуговування та оновлення коду, тому що зміни можна внести тільки в одній частині програми, а застосувати до всієї. Це також заощаджує час під час виправлення помилок або додавання нових функцій.
  4. Зручність коду. Концепція поліморфізму підвищує читабельність та зрозумілість коду, тому що може використовувати загальні інтерфейси та методи для різних об’єктів. Такий підхід робить код інтуїтивнішим, і розробники можуть швидше визначати функціональність чужої програми. Крім того, це спрощує процес налагодження та тестування програмного забезпечення.

Недоліки поліморфізму:

  1. Продуктивність. Іноді поліморфізм може негативно вплинути на продуктивність. Це відбувається тому, що пристрій потребує більше обчислювальної потужності, щоб визначити, який метод або функцію викликати в цьому конкретному випадку. Якщо варіантів використання занадто багато, а пристрій занадто слабкий, уповільнення виконання може бути суттєвим.
  2. Складність. Хоча зазвичай поліморфізм спрощує код, якщо ви працюєте з великими та складними ієрархіями об’єктів, концепція, навпаки, може призвести до ускладнення коду. Така ситуація також уповільнить процес налагодження та тестування програмного забезпечення.
  3. Залежність. Чим більше варіантів використання має одна функція, тим більше залежностей з’являється між частинами програми. Це ускладнює зміну або оновлення порушених ділянок коду.
  4. Помилки під час виконання. Іноді поліморфізм може стати каталізатором помилок під час виконання. Часто це відбувається при роботі з великою кількістю інтерфейсів. Тому під час проектування програмних систем необхідно враховувати цей фактор.

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

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

Класифікація поліморфізму

Класифікувати поліморфізм стали відносно недавно. Якщо сама концепція з’явилася ще у 60-х, то класифікацію запропонували лише у середині 1990-х. До цього поліморфізм описувався узагальнено.

Лука Карделлі, італійський вчений у галузі комп’ютерних наук та дослідник мов програмування, виділив такі три види поліморфізму:

  1. Універсальний поліморфізм (також використовуються терміни «поліморфізм підтипу» або «поліморфізм включення»). Цей вид поліморфізму дозволяє обробляти об’єкти різних класів так, ніби вони належать до загального суперкласу, забезпечує можливість універсального програмування та використання коду повторно.
  2. Ad-hoc поліморфізм (також використовується термін «перевантаження»). Цей вид включає використання одного імені або оператора для позначення декількох різних функцій або методів, які приймають різні типи аргументів.
  3. Параметричний поліморфізм (також використовується термін «універсальне програмування»). Цей вид полягає у визначенні функції або методу, який обробляє значення різних типів без явної вказівки типів даних заздалегідь. Він зазвичай використовується для створення повторно використовуваного коду, який можна застосовувати до різних типів.

Класифікація Карделлі — одна із найпопулярніших і вплинула на розвиток багатьох мов програмування, включаючи Java та C++. Але вона не єдина: є й альтернативні підходи до класифікації поліморфізму. Наприклад, є ще класифікація за Бертраном Мейєром. Вона ґрунтується на концепції поліморфізму підтипів, яку він розробив як частину своєї роботи над мовою програмування Eiffel.

Види поліморфізму

Інший підхід до класифікації ділить поліморфізм на два основні типи:

  • поліморфізм часу компіляції;
  • поліморфізм часу виконання.

1. Поліморфізм часу компіляції

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

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

Приклад:

Припустимо, у нас є такий код на Java:

public class Calculator {
    
    public int add(int a, int b) {
        return a + b;
    }
    
    public double add(double a, double b) {
        return a + b;
    }
    
    public int add(int a, int b, int c) {
        return a + b + c;
    }
}

Клас Calculator визначає три методи add. Вони мають різні параметри та значення, які повертаються:

  1. перший приймає два цілих аргументи і повертає ціле число;
  2. другий приймає два аргументи подвійної точності та повертає число подвійної точності;
  3. третій приймає три цілих аргументи і повертає ціле число.

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

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

2. Поліморфізм часу виконання

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

Через це поліморфізм часу компіляції ефективніший за поліморфізм часу виконання — адже дозвіл методу відбувається на етапі компіляції. Але водночас динамічний поліморфізм гнучкіше, оскільки дозволяє об’єктам виявляти різне поведінку залежно від своїх конкретних типів.

Приклад:

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

class Animal {
   public void move() {
      System.out.println("Animals can move");
   }
}

class Dog extends Animal {
   public void move() {
      System.out.println("Dogs can walk and run");
   }
}

public class TestDog {
   public static void main(String args[]) {
      Animal animal = new Animal();   // Animal reference and object
      Animal dog = new Dog();   // Animal reference but Dog object

      animal.move();   // runs the method in Animal class
      dog.move();   // runs the method in Dog class
   }
}

У цьому прикладі клас Animal має метод move, який виводить повідомлення у консоль. Клас Dog розширює клас Animal та перевизначає метод move:

  • коли ми створюємо об’єкт Animal та викликаємо його метод move, викликається версія, визначена у класі Animal, і в консоль виводиться повідомлення «Animals can move»;
  • коли ми створюємо об’єкт Dog і викликаємо його метод move, викликається версія методу, визначена в класі Dog, і в консоль виводиться повідомлення «Dogs can walk and run».

Приклади реалізації інших типів поліморфізму

Ad-hoc поліморфізм

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

Візьмемо код на С++:

#include <iostream>

int add(int a, int b) {
    std::cout << "Called the int version" << std::endl;
    return a + b;
}

double add(double a, double b) {
    std::cout << "Called the double version" << std::endl;
    return a + b;
}

int main() {
    std::cout << add(2, 3) << std::endl;     // calls the int version
    std::cout << add(2.0, 3.0) << std::endl; // calls the double version
    return 0;
}

Ми маємо дві функції add з різними типами параметрів: одна приймає два цілих числа, а інша — два числа подвійної точності. Реалізація функції визначається під час компіляції на основі типів аргументів, переданих у функцію.

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

Параметричний поліморфізм

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

Візьмемо такий код на Java:

public class Pair<T, U> {
    private T first;
    private U second;

    public Pair(T first, U second) {
        this.first = first;
        this.second = second;
    }

    public T getFirst() {
        return first;
    }

    public void setFirst(T first) {
        this.first = first;
    }

    public U getSecond() {
        return second;
    }

    public void setSecond(U second) {
        this.second = second;
    }

    public void print() {
        System.out.println("(" + first.toString() + ", " + second.toString() + ")");
    }
}

public class TestPair {
    public static void main(String[] args) {
        Pair<String, Integer> pair1 = new Pair<>("Hello", 123);
        Pair<Double, Boolean> pair2 = new Pair<>(3.14, true);

        pair1.print(); // prints "(Hello, 123)"
        pair2.print(); // prints "(3.14, true)"
    }
}

Тут у нас є клас Pair, який приймає два параметри універсального типу T та U. Об’єкти Pair створюються з різними типами для T та U, і код може бути використаний для будь-якої можливої ​​комбінації типів.

Поліморфізм примусу

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

Якщо ви додасте рядок і число в JavaScript, мова автоматично перетворює число в рядок і з’єднає два значення:

let stringVar = "5";
let numberVar = 10;
let result = stringVar + numberVar; // result will be "510"

Оператор + використовується для об’єднання stringVar та numberVar. Оскільки stringVarце рядок, а numberVarце число, перед об’єднанням двох значень компілятор автоматично перетворює numberVar на рядок.

Поєднання різновидів поліморфізму

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

  1. ad-hoc поліморфізм;
  2. параметричний поліморфізм;
  3. поліморфізм примусу.

Наприклад, функція, яка використовує параметричний поліморфізм для роботи з будь-яким типом даних, може також використовувати поліморфізм ad-hoc.

Приклад:

Код на Python, що демонструє поєднання ad-hoc поліморфізму та параметричного поліморфізму:

def add(x, y): 
    return x + y

def add_strings(x: str, y: str): 
    return x + '' + y

print(add(2, 3)) # Output: 5
print(add('Hello', 'World')) # Output: Hello World
print(add_strings('Hello', 'World')) # Output: Hello World

Ми визначили дві функції з ім’ям add:

  • перша функція add це універсальна функція, яка може приймати дані будь-якого типу, що підтримує оператор +;
  • друга функція add_strings — це спеціалізована функція, яка приймає лише два вхідні рядки та об’єднує їх за допомогою символу пропуску.

Функція add демонструє ad-hoc поліморфізм, тому що вона може обробляти різні типи даних та забезпечувати бажану операцію залежно від типу її аргументів.

Функція add_strings демонструє поєднання ad-hoc поліморфізму та параметричного поліморфізму, тому що це спеціалізована функція, яка працює тільки з рядками, за умови, що тип вхідних даних явно вказаний.

Приклад поліморфізму

Один із прикладів прояву поліморфізму це зміна реалізації методів при наслідуванні.

Припустимо, у нас є такий код на Java:

class Animal {
  void makeSound() {
    System.out.println("The animal makes a sound");
  }
}

class Dog extends Animal {
  void makeSound() {
    System.out.println("The dog barks");
  }
}

class Cat extends Animal {
  void makeSound() {
    System.out.println("The cat meows");
  }
}

У нас є базовий клас Animal та два похідні класи Dog та Cat. Клас Animal має метод makeSound(), який друкує стандартне повідомлення про звук. Класи Dog та Catперевизначають цей метод, щоб вивести, який звук відтворюють ці тварини.

Припустимо, що ми створюємо об’єкти кожного з цих класів і викликаємо метод makeSound():

Animal animal = new Animal();
Dog dog = new Dog();
Cat cat = new Cat();

animal.makeSound(); // The animal makes a sound
dog.makeSound(); // The dog barks
cat.makeSound(); // The cat meows

Ми бачимо, що хоча всі три об’єкти були створені з різних класів, вони реагують на метод makeSound(). Це якраз властивість поліморфізму — здатність об’єктів набувати різних форм залежно від їхнього контексту.

Висновок

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

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

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

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

Останні статті

Айтівець Міноборони США понабирав кредитів і хотів продати рф секретну інформацію

32-річний розробник безпеки інформаційних систем Агентства національної безпеки Джарех Себастьян Далке отримав 22 роки в'язниці…

30.04.2024

Простий та дешевий. Українська Flytech запустила масове виробництво розвідувальних БПЛА ARES

Українська компанія Flytech представила розвідувальний безпілотний літальний апарат ARES. Основні його переваги — недорога ціна…

30.04.2024

Запрошуємо взяти участь у премії TechComms Award. Розкажіть про свій потужний PR-проєкт у сфері IT

MC.today разом з Асоціацією IT Ukraine і сервісом моніторингу та аналітики згадок у ЗМІ та…

30.04.2024

«Йдеться про потенціал мобілізації»: Україна не планує примусово повертати українців із ЄС

Україна не буде примусово повертати чоловіків призовного віку з-за кордону. Про це повідомила у Брюсселі…

30.04.2024

В ЗСУ з’явився жіночий підрозділ БПЛА — і вже можна проходити конкурсний відбір

В Збройних Силах України з'явився жіночий підрозділ з БПЛА. І вже проводиться конкурсний відбір до…

30.04.2024

GitHub на наступному тижні випустить Copilot Workplace — ШІ-помічника для розробників

GitHub анонсував Copilot Workspace, середовище розробки з використанням «агентів на базі Copilot». За задумкою, вони…

30.04.2024