В объектно-ориентированном программировании (ООП) полиморфизм — одна из базовых концепций. На практике это один вызов метода, за которым стоят разные реализации. Начинающему разработчику важно понять три вещи: как определить полиморфизм простыми словами, чем отличаются его виды и как узнать его в готовом коде.
Без этого сложно читать программы с наследованием и интерфейсами и отличать перегрузку метода от переопределения. Дальше разобраны определение, виды, примеры на Java и JavaScript, а также преимущества и цена полиморфизма. Тем, кто хочет изучить ООП глубже, пригодятся книги для программистов.
- Что такое полиморфизм в ООП
- Как работает полиморфизм в классах и интерфейсах
- Виды полиморфизма
- Примеры полиморфизма на Java и JavaScript
- Преимущества и недостатки полиморфизма
- FAQ по полиморфизму
- Коротко о главном
Что такое полиморфизм в ООП
Полиморфизм в ООП — это возможность обращаться через одно имя метода или функции к данным разных типов. Например, объекты разных классов по-своему отвечают на один и тот же вызов метода. Учебник Oracle по Java объясняет, что название пришло из биологии, где слово означает «много форм». В ООП подклассы могут определять собственное поведение и при этом сохранять часть функциональности родителя.
Для примера возьмем интернет-магазин с двумя товарами, мячом и шахматами. Каждому товару соответствует свой класс, а у обоих есть метод AddToCart. Код корзины вызывает AddToCart одинаково, а что именно происходит при вызове, определяет класс товара. Если появится третий товар, для него достаточно добавить новый класс с тем же методом: код корзины менять не придется, пока он работает с общим типом товара.
Простыми словами, полиморфизм — это то, что позволяет использовать один и тот же метод для разных объектов.
Как работает полиморфизм в классах и интерфейсах
Механизм полиморфизма строится на общем типе. Сначала задают базовый класс или интерфейс с методом, затем дочерние классы реализуют этот метод под тем же именем, каждый по-своему, а вызывающий код обращается к объектам различных типов через общий тип. В Java при таком вызове виртуальная машина выполняет метод того объекта, на который указывает переменная, а не метод, заданный типом переменной. Oracle называет это виртуальным вызовом метода.
Абстрактный класс — это класс, объявленный с ключевым словом abstract: его экземпляр создать нельзя, но от него можно наследовать. Он задает общую основу для дочерних классов. Класс в Java может наследовать только от одного класса, абстрактного или обычного.
Интерфейс в Java — это, как правило, группа связанных методов без тел, то есть контракт между классом и остальной программой. Если не абстрактный класс заявляет, что реализует интерфейс, он обязан реализовать все абстрактные методы интерфейса, иначе код не скомпилируется. Абстрактный класс может реализовать их частично, а методы по умолчанию (default) реализовывать не обязательно. Число интерфейсов, которые реализует класс, не ограничено. Подробнее о понятии рассказывает статья об интерфейсе.
Полиморфизм входит в число принципов ООП. Документация C# называет четыре основных принципа: абстракцию, инкапсуляцию, наследование и полиморфизм. Это отдельные принципы, но в примере с магазином они работают вместе: интерфейс или абстрактный класс задает общий тип товара, наследование создает классы конкретных товаров, а полиморфизм описывает их разное поведение при вызове AddToCart.
Виды полиморфизма
Чаще всего выделяют три формы полиморфизма. Различаются они тем, что связывает общее имя: общий родительский класс, единый код для разных типов или произвольный набор типов.
- Полиморфизм подтипов (наследование). Одно имя обозначает объекты разных классов с общим родителем. Вызов, адресованный родительскому типу, срабатывает у каждого дочернего класса по-своему.
- Параметрический полиморфизм. Нет привязки к конкретному типу: функция, как правило, работает единообразно с разными типами.
- Полиморфизм ad hoc. Одно имя и разное поведение для разных типов. Одним из механизмов служит перегрузка методов, но с ней ad hoc не совпадает.
Пример одного имени для разных типов данных — встроенная функция len() в Python. По документации Python, ее аргументом может быть последовательность (строка, bytes, кортеж, список, range) или коллекция (словарь, множество, frozenset), и во всех случаях функция возвращает число элементов объекта. Так одно имя обслуживает разные типы данных.
Статический и динамический полиморфизм
Полиморфизм различают и по моменту, когда выбирается реализация. При статическом полиморфизме ее выбирают при компиляции, при динамическом полиморфизме — во время выполнения программы. Статический вариант типичен для ad hoc и параметрического полиморфизма, а динамический обычно связан с полиморфизмом подтипов.
Чтобы понять, какой метод сработает, спросите себя, что определяет выбор: список параметров (решается при компиляции) или тип объекта (решается при выполнении).
В Java этому делению соответствуют два механизма, которые описывает спецификация языка. Перегрузка — это методы с одним именем и разными списками параметров: компилятор выбирает подходящий метод по имени и аргументам вызова, а тип возвращаемого значения в выборе не участвует. Переопределение — это замена реализации метода родителя в дочернем классе: во время выполнения вызывается переопределяющий метод того объекта, у которого произошел вызов.
| Признак | Статический полиморфизм | Динамический полиморфизм |
|---|---|---|
| Когда выбирается реализация | При компиляции | При выполнении программы |
| Типичные формы | Ad hoc и параметрический | Полиморфизм подтипов |
| Пример в Java | Перегрузка: одно имя, разные параметры | Переопределение: дочерний класс заменяет метод |
| Скорость и гибкость | Обычно быстрее, нужна поддержка компилятора | Гибче, но медленнее |
Примеры полиморфизма на Java и JavaScript
Первый пример показывает перегрузку метода в Java. В классе четыре метода shapearea с одним именем и разными списками параметров: без параметров, с радиусом, с основанием и высотой, с длиной и шириной. Какой из них выполнится, зависит от аргументов вызова, то есть от фигуры, площадь которой нужно найти.

Второй пример показывает переопределение. Дочерний класс Cars наследует родительский класс Vehicles и объявляет метод drive() с тем же именем и параметрами, что и у родителя. Метод дочернего класса заменяет реализацию родителя, поэтому программа выводит строку «Управлять автомобилем». Переменная model в этом коде имеет тип Cars, поэтому выбор метода по типу объекта здесь не виден.

Чтобы увидеть полиморфный вызов, объявите переменную родительского типа и присвойте ей объект дочернего класса, например Vehicles model = new Cars();. Тогда метод выбирается по объекту, а не по типу переменной.
Третий пример написан на JavaScript. Ключевое слово class объявляет класс, а extends делает его потомком другого класса, как поясняет документация MDN Web Docs. На картинке три класса, firstClass, secondClass и thirdClass: каждый следующий наследует предыдущий, а метод add() определен в каждом из них по-своему. Результат зависит от того, у объекта какого класса вызван этот метод.

Преимущества и недостатки полиморфизма
Преимущества полиморфизма следуют из сказанного выше: подклассы сохраняют часть функциональности родителя, а код работает с общим типом, а не с каждым классом по отдельности.
- Повторное использование кода. Подклассы в Java сохраняют часть функциональности родителя и добавляют свое поведение, поэтому общую часть не нужно писать заново для каждого класса. Это свойство наследования, которое в ООП используется вместе с полиморфизмом.
- Расширяемость и гибкость. Новый класс с тем же методом можно подставить на место существующих, и код, написанный под общий интерфейс, продолжит работать.
- Единый контракт. В Java интерфейс задает методы, которые обязан реализовать каждый не абстрактный класс с этим интерфейсом, а компилятор проверяет это при сборке.
У гибкости динамического полиморфизма есть цена во время выполнения. В официальной книге по языку Rust объясняют, что при динамической диспетчеризации, то есть выборе метода во время выполнения, нужный метод ищут в момент вызова, а компилятор не может встроить его код, из-за чего часть оптимизаций недоступна. Насколько это заметно в конкретной программе, зависит от языка и реализации.
FAQ по полиморфизму
Как в Python выбрать реализацию функции по типу аргумента?
Для этого в модуле functools есть декоратор @singledispatch. Он превращает функцию в обобщенную, и реализация выбирается по типу первого аргумента. По документации Python, реализации для других типов добавляют через метод register() такой функции.
Какие методы Java нельзя переопределить?
Нельзя переопределить final-методы: модификатор final запрещает это прямо. Статический метод подкласса не переопределяет метод родителя, а скрывает его. Спецификация Java допускает переопределение только для методов с доступом public, protected или пакетным (без модификатора доступа, действует в том же пакете), поэтому закрытый private-метод родителя в подклассе не переопределяется.
Есть ли полиморфизм в Python без явных интерфейсов?
Есть, одним из механизмов служит утиная типизация. При ней тип объекта не проверяют, а просто вызывают нужный метод, и, как сказано в документации Python, акцент на интерфейсах, а не на конкретных типах повышает гибкость хорошо спроектированного кода за счет полиморфной подстановки. Абстрактные базовые классы дополняют утиную типизацию, когда другие приемы, например проверка через hasattr(), были бы неуклюжими или неточными.
Коротко о главном
- Полиморфизм — одна из базовых концепций ООП: одно имя метода или функции работает с данными разных типов, а у объектов разных классов один и тот же вызов выполняется по-разному.
- Чаще всего выделяют три формы: полиморфизм подтипов, параметрический и ad hoc.
- Статический полиморфизм выбирает реализацию при компиляции, динамический при выполнении. В Java перегрузка относится к первому, переопределение ко второму.
- Общую часть кода не нужно дублировать, а новые классы с тем же методом подставляются на место существующих.
- Динамический полиморфизм гибче, но, как правило, медленнее статического.


Комментарии (5)
Оставить комментарий