Перейти к основному содержимому

Полиморфизм - единый интерфейс для разных реализаций

Разработчику Аналитику Тестировщику
Архитектору Инженеру


Полиморфизм

Что такое полиморфизм?

Полиморфизм — способность одного и того же интерфейса (имени операции, типа переменной) работать с разными реализациями. В ООП чаще всего имеют в виду полиморфизм подтипов — объекты разных классов с общим предком обрабатываются через ссылку на базовый тип, а вызывается метод фактического класса (динамическое связывание, virtual / override).

Слово происходит от греческого "много форм". В теории типов различают несколько видов:

ВидВ ООППример
Подтипы (включение)переопределение, интерфейсыФигура f = new Круг(); f.Нарисовать()
Ad hocперегрузка методовсложить(int,int) и сложить(String,String)
Параметрическийобобщения (generics)List<T> в Java и C#

В сообществе ООП слово "полиморфизм" обычно означает подтипы и наследование; параметрический полиморфизм называют обобщённым программированием — теория и примеры в Обобщения и обобщённое программирование; синтаксис — generics в C#, типы в Java.

Виртуальный полиморфизм — при динамическом связывании указатель или ссылка базового типа может указывать на объект производного класса; вызов метода разрешается по реальному типу объекта, а не по типу переменной.

Позднее связывание

В учебниках по C++ это называют поздним связыванием (late binding): сигнатуры методов наследуются от предка, а реализация выбирается во время выполнения после переопределения (override, virtual). Код, написанный против базового типа или интерфейса, работает с любым подтипом, подставленным в рантайме.

Чисто виртуальные (абстрактные) методы в базовом классе задают контракт без реализации; конкретику дают только потомки. Множественное наследование (несколько базовых классов у одного потомка) усиливает выразительность, но порождает ромбовидную иерархию, когда два "ветвя" наследуют одного предка — какую переопределённую версию метода взять, становится неоднозначным. В C++ это смягчают виртуальным наследованием; в Java — запретом множественного наследования классов (остаются интерфейсы). См. ООП в C++, композицию вместо глубоких иерархий.

image-10.png

Play ITЗагрузка интерактивного демо…

ИНТЕРФЕЙС Фигура
метод Нарисовать()
КОНЕЦ

КЛАСС Круг РЕАЛИЗУЕТ Фигура
метод Нарисовать() вывести("Круг")
КОНЕЦ

КЛАСС Прямоугольник РЕАЛИЗУЕТ Фигура
метод Нарисовать() вывести("Прямоугольник")
КОНЕЦ

список := [новый Круг(), новый Прямоугольник()]
для каждой ф в список
ф.Нарисовать() // одна операция — разные результаты
конец

Пример:

Код ITЗагрузка примера кода…

Здесь метод нарисовать() имеет разные реализации в зависимости от типа объекта - Круг или Прямоугольник, однако вызывается через единый интерфейс.


Переопределение

Переопределение методов — это механизм, при котором подкласс предоставляет свою собственную реализацию метода, унаследованного от родительского класса. Это ключевой аспект полиморфизма. Пример:

Код ITЗагрузка примера кода…

Здесь метод издатьЗвук() переопределяется в методах Кот и Собака. Оба класса наследуются от базового класса Животное, но при вызове метода через Животное будет реализация, соответствующая фактическому типу объекта.

К примеру, "Собака.издатьЗвук()" будет "Гав!", а "Кот.издатьЗвук()" будет "Мяу!".

Метод в базовом классе один и тот же, но его внутренности для каждого подкласса - разные.


Перегрузка

Перегрузка методов — это создание нескольких методов с одинаковым именем, но разными параметрами (типами или количеством). Это не связано напрямую с наследованием, в отличие от переопределения, но также является частью полиморфизма.

Пример:

Код ITЗагрузка примера кода…

Здесь в одном классе Математика есть две реализации одного и того же метода - "сложить" - одна для чисел, вторая для строк. Выбор реализации будет зависеть от переданных аргументов.

Когда мы вызовем "сложить(1, 2)" - тип данных определится как целое число (int) и соответственно будет вызвана реализация первая - для чисел. Если же мы напишем вместо целых чисел строки, то будет вывод путём конкатенации, а не суммирования.

Давайте подведём итоги по ООП.


Мы будем повторно изучать особенности ООП для каждого языка, но суть должны были понять корректно. Сведём всё в итог.

ПринципОпределениеЦельМеханизмы
АбстракцияВыделение важных характеристик объекта и игнорирование несущественных деталей.Упрощение моделирования сложных систем, фокус на "что" делает объект, а не "как".Абстрактные классы; Интерфейсы; Скрытие деталей реализации.
ИнкапсуляцияОбъединение данных (атрибутов) и методов работы с ними в единый объект.Защита данных, управление доступом, обеспечение целостности объекта.Модификаторы доступа; Геттеры и сеттеры; Автоматические свойства.
НаследованиеСоздание нового класса (подкласса) на основе существующего (родительского), перенимая его свойства и методы.Уменьшение дублирования кода, повторное использование, расширение функциональности.Базовые и производные классы; Абстрактные классы; Интерфейсы.
ПолиморфизмСпособность объекта принимать разные формы в зависимости от контекста.Гибкость кода, единый интерфейс для работы с разными типами объектов.Переопределение методов; Перегрузка методов; Интерфейсы; Абстрактные классы.

Как можно заметить, особенно от всех выделяется именно инкапсуляция, тогда как абстракция, наследование и полиморфизм настолько тесно связаны, что их легко перепутать - они используют одни и те же механизмы интерфейсов, абстрактных классов. Поэтому ООП — это единая парадигма, в которой важны все эти четыре принципа.