4.3 Наследование — Circle

Предыдущая  Содержание  Следующая V*D*V

Окружность это просто большая точка: в дополнение к координатам центра нужен радиус. Рисование происходит немного по-другому, но сдвиг требует только изменения координат центра.

Здесь мы как обычно ускорим работу нашего текстового редактора и снова используем уже написанный исходный код. Сделаем копию реализации точек и изменим те части, где окружность отличается от точки. struct Circle получает дополнительный компонент:

 

int rad;

 

Этот компонент инициализируется в конструкторе

 

self -> rad = va_arg(* app, int);

 

и используется в Circle_draw():

 

printf("circle at %d,%d rad %d\n",

    self -> x, self -> y, self -> rad);

 

Получаем небольшую проблему в move(). Необходимые действия идентичны для точки и окружности: надо добавить аргументы смещения к компонентам координат. Тем не менее, в одном случае move() работает с struct Point, а в другом случае она работает с struct Circle. Если бы move() была динамически скомпонованной, можно было бы предоставить две разные функции для выполнения одинаковых действий, но есть гораздо лучший способ. Рассмотрим как выглядят представления точек и кругов:

 

OOC_Inheritance-Circle

 

Рисунок показывает, что каждая окружность начинается с точки. Если мы породим struct Circle добавляя новые компоненты в конец struct Point, то сможем передать окружность в move(), так как начальная часть её представления выглядит так же, как у точки, которую move() ожидает получить, и единственное, что move() может изменить. Вот верный способ сделать так, чтобы начальная часть окружности всегда выглядела как точка:

 

struct Circle { const struct Point _; int rad; };

 

Мы позволяем производной структуре начаться с копии базовой структуры, которую расширяем. Сокрытие информации требует никогда не лезть в базовую структуру напрямую; поэтому в качестве имени используем почти невидимое подчеркивание и декларируем её как const, чтобы предотвратить присвоения из-за невнимательности.

Это всё и есть простое наследование: подкласс является производным от суперкласса (или базового класса) лишь удлиняя структуру, которая представляет объект суперкласса.

Так как представление объекта подкласса (окружности) начинается также как представление объекта суперкласса (точки), окружность всегда может притвориться точкой — в начале адреса представления окружности на самом деле есть представление точки.

Совершенно логично передавать окружность в move(): подкласс наследует методы суперкласса, так как такие методы работают только на той части представления подкласса, которая идентична представлению суперкласса для которой эти методы первоначально были написаны. Передача окружности как точки означает преобразование из struct Circle * в struct Point *. Мы будем называть это повышающим приведением из подкласса в суперкласс — в ANSI-C это может быть достигнуто только с помощью оператора явного преобразования или через промежуточные  значения типа void *.

Однако, как правило, неверно передавать точку в функцию, предназначенную для окружностей, такую как Circle_draw(): преобразование из struct Point * в struct Circle * допустимо лишь в случае, если точка на самом деле была окружностью. Мы будем называть это понижающим приведением от суперкласса к подклассу — это также требует явных преобразований или значений типа void *, и может быть сделано только для указателей на объекты, с которых начинается подкласс.

 

Предыдущая  Содержание  Следующая