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

Наследование - повторное использование и иерархия типов

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


Наследование

Что такое наследование?

Наследование (в терминах ООП — генерализация в иерархии) — отношение "родитель / потомок", при котором дочерний класс заимствует структуру и контракт родителя и может расширять или уточнять их. Снижает дублирование — общее выносят в базовый (супер-, родительский) класс, отличия оставляют в производных (под-, дочерних) классах.

ТерминСинонимы
Базовый класссуперкласс, родитель, предок
Производный классподкласс, наследник, потомок
Отношениеextends / : / НАСЛЕДУЕТ

При порождении класса производный тип наследует поля и методы базового и может обращаться к общим (public) и защищённым (protected) членам так, будто они объявлены в самом производном классе. К частным (private) данным базового класса снаружи его методов доступа нет — только через публичный интерфейс базы.

В C++ и Java конструктор производного класса обязан инициализировать базовую часть объекта: сначала вызывается конструктор предка (явно Base(args) или неявно по умолчанию), затем выполняется тело конструктора наследника. Если в базе и в потомке есть члены с одинаковым именем, внутри методов потомка по умолчанию видна версия потомка; к версии базы обращаются через квалификатор (Base::member в C++, super в Java). Подробнее — ООП в C++, конструкторы.

К примеру, у нескольких классов совпадают поля модель и год — их выносят в Транспорт, а Автомобиль и Велосипед добавляют своё поведение.

Зачем иерархия

Иерархия классов поддерживает повторное использование кода и единообразную типизацию: переменная типа Транспорт может ссылаться на любой наследник, если дальше нужен полиморфизм подтипов.

image-8.png

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


Базовый класс

★ Базовый класс (суперкласс) — это класс, который предоставляет свои свойства или методы для наследования. Он описывает общие характеристики и поведение, которые могут быть полезны для подклассов.


КЛАСС Транспорт
поля: модель, год
метод Запустить()
вывести("Транспорт запущен")
конец
КОНЕЦ

КЛАСС Автомобиль НАСЛЕДУЕТ Транспорт
поле количество_колёс
метод Запустить()
вывести("Автомобиль поехал")
конец
КОНЕЦ

Пример (тот же смысл на псевдокоде, близком к Java):

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

Здесь класс Транспорт является базовым классом. Он содержит общие атрибуты (модель, год) и метод (запустить()).


Подкласс

Производный класс (или подкласс) — это класс, который наследует свойства и методы базового класса. Подкласс может использовать унаследованные элементы "как есть", добавлять новые элементы или переопределять унаследованные методы.

Пример:

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

Здесь класс Автомобиль наследует модель, год и метод запустить() от класса Транспорт. Также он добавляет поле количество_колёс и метод ехать(), и кроме этого - переопределяет метод запустить().


Интерфейс

Интерфейс — это набор методов, которые должны быть реализованы в классах, использующих этот интерфейс, и интерфейсы не содержат реализации методов. Они используются для определения "контракта", которому должны следовать классы.

Пример:

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

Здесь класс Автомобиль реализует интерфейс Запускаемый и обязан реализовать метод запустить().

Сравнение абстрактного класса и интерфейса с таблицей и примером умного дома — в Абстракции. Ниже — как это выглядит в контексте наследования.

Абстрактный класс

  • содержит и готовые методы, и методы без реализации (их допишут наследники);
  • может хранить поля и вызывать конструктор при создании объекта;
  • экземпляр самого абстрактного класса создать нельзя — только наследника;
  • у класса может быть один такой родитель.

Пример:

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

Здесь класс Автомобиль наследует абстрактный класс Транспорт и обязан реализовать метод ехать().


Множественное наследование

Множественное наследование — это возможность наследовать свойства и методы сразу нескольких классов. Это мощный механизм, но он может вызывать проблемы, такие как "конфликт имён" (если два родительских класса имеют методы с одинаковыми именами).

Пример:

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

Здесь класс Автомобиль наследует методы от классов Двигатель и Колёса.

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

Представим, что класс D наследуется от B и С. Оба класса B и C наследуются от общего предка - класса A. Если в классе A есть какой-то метод или поле, то возникает неоднозначность — когда мы обращаемся к этому методу или полю через объект D, какой именно экземпляр использовать — от B или от C? Компилятор не знает, вызывать ли B или C. У нас могут получиться два разных метода в классе D.

image-9.png

Это называется Diamond Problem (ромбическая проблема) - ситуация в ООП, когда класс наследуется от двух или более классов, которые имеют общий предок, что приводит к неясности - чей метод использовать в дочернем классе. Именно поэтому многие современные языки не поддерживают множественное наследование, а если два интерфейса содержат метод с одинаковой сигнатурой, то класс обязан явно переопределить его.

Python поддерживает множественное наследование и использует MRO (Method Resolution Order) — порядок разрешения методов, где Python сначала будет искать метод в B, потом в C и затем в A.

Иногда класс не должен быть наследуемым, чтобы предотвратить изменение его поведения. Это особенно важно для классов, реализующих важную логику. В некоторых языках есть ключевое слово, например final (Java), которое делает класс ненаследуемым - финальным:

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

Здесь класс Утилиты защищён от наследования.