Основы ООП: наследование

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

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

Когда какой-либо класс выступает родительским для другого, его называют базовым. В свою очередь класс-наследник называют производным.

Разумеется, цепочку наследования (а точнее, генеалогическое дерево) можно продолжать: у каждого класса может быть несколько наследников, у тех в свою очередь — свои наследники, и так далее. Единственное, чего правила наследования не позволяют, так это циклы в родственных отношениях, например, внук не может быть родителем своего деда.

Отношение между каким-либо классом и его потомком любого поколения описывается словом "является" (английское "is a"), то есть потомок способен выступать в роли предка, но не наоборот. Это объясняется тем, что производный объект фактически содержит в себе модель данных предка и дополняет её новыми полями и поведением.

Наследуя множество классов друг от друга, мы получаем возможность унифицированно обрабатывать родственные объекты: ведь часть функций у них — общая.

Например, в гипотетической программе рисования может быть реализовано несколько типов фигур: круг, квадрат, треугольник и т.д. Каждый объект имеет координаты на экране (для простоты будем считать, что задается пара значений X и Y центра фигуры). Кроме того, для отрисовки каждой фигуры используется собственный цвет фона, цвет рамки и её толщина.

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

Более того, в целях упрощения исходного кода желательно каким-то образом унифицировать не только настройки, но и рисование разных фигур. В этой фразе есть некоторое противоречие: поскольку фигуры разные, и каждая должна отображаться по-своему, то о какой унификации идет речь? — Об унификации программного интерфейса. Ведь согласно концепции абстракции следует отделять внешний интерфейс от внутренней реализации. А отображение конкретных фигур — это уже детали реализации.

Единый интерфейс и разные реализации для типов фигур плавно приводят нас к следующей концепции — полиморфизму.