Вы знаете такое устройство как пульт дистанционного управления? Его используют для управления домашней техникой и одна и та же кнопка может выполнять несколько разных функций. Например, кнопка «питание» включает или выключает телевизор.
В программировании полиморфизм работает так же. Объекты могут принимать разные формы в зависимости от контекста, в котором они используются.
Это делает полиморфизм мощным инструментом для создания гибкого кода. Он широко используется во многих языках программирования и средах, упрощает обслуживание и обновление кода, а также делает программы более надежными и менее подверженными ошибкам.
Полиморфизм — это концепция программирования, которая позволяет обрабатывать объекты разных типов так, как если бы они были одного типа, и по-разному реагировать на одни и те же методы или функции.
Другими словами, полиморфизм позволяет разным объектам совместно использовать один и тот же интерфейс, но вести себя по-разному в зависимости от их целей исполнения.
Например, у вас есть функция, принимающая на вход объект Animal
. Она может работать с любым типом животных: кошкой, собакой, хомячком. У каждого типа животных будет своя собственная реализация функции, но самой функции не нужно знать, с каким конкретным типом животных она работает. Именно эта особенность полиморфизма позволяет с его помощью создавать гибкий и повторно используемый код.
Полиморфизм появился, когда сформировалась концепция объектно-ориентированного программирования (ООП), что сделало его неотъемлемой частью ООП, а также одной из ключевых составляющих языков программирования в целом.
Идею полиморфизма впервые представил в середине 1960-х Кристофер Стрейчи в своей статье «Фундаментальные концепции языков программирования». Но только в конце 1970-х — начале 1980-х годов эта концепция начала внедряться в языки программирования.
Одним из первых языков программирования, использующих полиморфизм, стал Simula (разработан в конце 1960-х — начале 1970-х годов).
Simula разрабатывали для симуляции и моделирования. Он представил концепцию объектов, классов и наследования, благодаря чему стал первым языком, использующим термин «виртуальный» для описания полиморфизма.
Smalltalk, который создали в 1970-е годы, тоже один из ранних примеров языка программирования, где была введена концепция полиморфизма.
Его сделали для образовательных целей, он был одним из первых полностью ОО-языков программирования и представил концепцию передачи сообщений, которая позволяла объектам общаться друг с другом.
Важную роль в развитии полиморфизма сыграл C++. Его разработали в 1980-х годах как расширение языка программирования C и включили в него концепцию перегрузки функций, которая позволяла нескольким функциям иметь одно и то же имя, но разные параметры.
C++ также представил концепцию шаблонов, которая позволила использовать универсальное программирование и еще больше расширила возможности полиморфизма.
Язык Java, который появился в середине 1990-х годов, также активно использует полиморфизм. Основной задачей разработчиков Java было создание универсального языка, который мог бы работать на любых устройствах. Использование концепции полиморфизма стало ключевым элементом для достижения этой цели.
В частности, Java представила концепцию интерфейсов, которая позволяла классам реализовывать несколько интерфейсов и наследовать поведение из нескольких источников.
Сейчас полиморфизм — одна из ключевых концепций ОО-языков программирования и широко применяется при разработке сложных программных систем.
Благодаря использованию полиморфизма разработчики могут создавать более гибкие и универсальные программы, которые проще адаптируются к разным ситуациям и условиям.
Хотя полиморфизм — очень важный инструмент в программировании, как у любой другой концепции, у него есть и преимущества, и недостатки.
Начнем с преимуществ:
Недостатки полиморфизма:
В целом преимущества полиморфизма обычно перевешивают недостатки, но важно учитывать особенности конкретно вашего ПО, прежде чем применять концепцию.
Если проанализировать недостатки полиморфизма, можно сделать вывод, что проблемы возникают, когда его используют слишком часто и слишком много. Поэтому нужно быть вдвойне аккуратнее с концепцией, если в вашем коде сложная иерархия, а ПО, которое вы разрабатываете, тяжеловесное и объемное.
Классифицировать полиморфизм стали относительно недавно. Если сама концепция появились еще в 60-х, то классификацию предложили только в середине 1990-х. До этого полиморфизм описывался обобщенно.
Лука Карделли, итальянский ученый в области компьютерных наук и исследователь языков программирования, выделил такие три вида полиморфизма:
Классификация Карделли одна из самых популярных и повлияла на развитие многих языков программирования, включая Java и C++. Но она не единственная: есть и альтернативные подходы к классификации полиморфизма. Например, есть еще классификация по Бертрану Мейеру. Она основана на концепции полиморфизма подтипов, которую он разработал как часть своей работы над языком программирования Eiffel. Дискуссии о лучшем варианте в компьютерных науках продолжаются.
Другой подход к классификации делит полиморфизм на два основных типа:
Полиморфизм времени компиляции, также известный как статический полиморфизм, возникает, когда компилятор находится в стадии определения, какую функцию или метод вызывать во время компиляции, основываясь на переданных параметрах.
Обычно это достигается путем перегрузки метода. Он определяет методы с одним именем, но разными параметрами, чтобы обеспечить возможность работы с разными типами значений.
Пример:
Допустим, у нас есть такой код на 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
. Они имеют разные параметры и возвращаемые значения:
В зависимости от того, сколько аргументов и каких типов будет подано на вход, компилятор Java определит, какой из реализации метода add
использовать в этом конкретном случае.
Другими словами, если сложить нужно будет два целых числа, метод вернет сумму целым числом, а если два числа двойной точности — сумма вернется тоже числом с двойной точностью.
Другое название полиморфизма времени выполнения — динамический полиморфизм. Он возникает, когда вызываемый метод или функция определяется во время выполнения на основе конкретного объекта.
Из-за этого полиморфизм времени компиляции эффективнее полиморфизма времени выполнения — ведь разрешение метода происходит на этапе компиляции. Но в то же время динамический полиморфизм гибче, потому что позволяет объектам проявлять разное поведение в зависимости от их конкретных типов.
Пример:
В 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
в строку.
Чтобы создавать более гибкие и сложные программные системы, некоторые типы полиморфизма можно комбинировать. Это касается таких типов:
Например, функция, использующая параметрический полиморфизм для работы с любым типом данных, также может использовать полиморфизм 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()
. Это как раз свойство полиморфизма — способность объектов принимать разные формы в зависимости от их контекста.
Полиморфизм — это ключевая концепция объектно-ориентированного программирования, которая позволяет объектам принимать различные формы или вести себя по-разному в зависимости от контекста.
Этот инструмент позволяет программистам писать более гибкий и удобный в сопровождении код. Разрабатывая наш код как полиморфный, мы можем создавать более совершенные программные системы, а также совершать меньше ошибок.
Например, мы можем написать общую функцию, которая работает с разными типами объектов, а не писать отдельную функцию для каждого типа. Это может значительно сэкономить время и уменьшить количество багов, так как у нас не будет дублирования кода.
В то же время, есть риск ухудшить производительность ПО, если полиморфизм использовать слишком часто. Поэтому всегда тщательно взвешивайте риски, прежде чем его применять.
Прокси (proxy), или прокси-сервер — это программа-посредник, которая обеспечивает соединение между пользователем и интернет-ресурсом. Принцип…
Согласитесь, было бы неплохо соединить в одно сайт и приложение для смартфона. Если вы еще…
Повсеместное распространение смартфонов привело к огромному спросу на мобильные игры и приложения. Миллиарды пользователей гаджетов…
В перечне популярных чат-ботов с искусственным интеллектом Google Bard (Gemini) еще не пользуется такой популярностью…
Скрипт (англ. — сценарий), — это небольшая программа, как правило, для веб-интерфейса, выполняющая определенную задачу.…
Дедлайн (от англ. deadline — «крайний срок») — это конечная дата стачи проекта или задачи…