4.7 Реализация подкласса — Circle

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

Мы готовы написать полную реализацию окружностей, где можем выбрать любую технику из предыдущих разделов, которая больше всего нравится. Объектная ориентация предписывает, что нужен конструктор, возможно, деструктор, Circle_draw() и описание типа Circle, чтобы связать всё это вместе. Чтобы заняться нашими методами, подключаем Circle.h и добавляем следующие строки к переключателю в тестовой программе раздела 4.1:

 

case ’c’:

    p = new(Circle, 1, 2, 3);

    break;

 

Теперь наблюдаем такое поведение тестовой программы:

 

$ circles p c

"." at 1,2

"." at 11,22

circle at 1,2 rad 3

circle at 11,22 rad 3

 

Конструктор окружности принимает три аргумента: сначала координаты точки окружности, а затем радиус. Инициализация компонента "точка" является работой конструктора точки. Он использует часть списка аргументов new(). Конструктору окружности остаётся со списком оставшихся аргументов, с помощью которых он инициализирует радиус.

Конструктор подкласса должен сначала дать конструктору суперкласса сделать ту часть инициализации, которая превращает обычную память в суперкласс объекта. После того, как конструктор суперкласса выполнился, конструктор подкласса завершает инициализацию и превращает объект суперкласса в объект подкласса.

Для окружностей это означает, что надо вызвать Point_ctor(). Подобно всем динамически скомпонованным методам, эта функция объявлена static и таким образом скрыта внутри Point.c. Тем не менее, мы всё ещё можем добраться до этой функции с помощью дескриптора типа Point, который доступен в Circle.c:

 

static void * Circle_ctor (void * _self, va_list * app) {

    struct Circle * self =

        ((const struct Class *) Point) -> ctor(_self, app);

 

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

    return self;

}

 

Теперь должно быть понятно, почему мы передаём адрес указателя списка аргументов app каждому конструктору, а не само значение va_list: new() вызывает конструктор подкласса, который вызывает конструктор своего суперкласса, и так далее. Конструктор самого базового класса является первым, кто на самом деле что-то сделает, и он первым забирает из левой части списка аргументов, переданного в new(). Остальные аргументы доступны следующему подклассу и так далее до последнего, пока крайние справа аргументы не будут использованы последний подклассом, то есть конструктором непосредственно вызванным new().

Разрушение лучше всего расположить в точном обратном порядке: delete() вызывает деструктор подкласса. Он должен разрушить свои собственные ресурсы, а затем вызвать деструктор своего непосредственного суперкласса, который сможет уничтожить следующий набор ресурсов и так далее. Конструирование происходит в порядке от суперкласса до подкласса, разрушение происходит в обратном порядке, подкласс перед суперклассом, компонент "окружность" перед компонентом "точка". Здесь, однако, ничего делать не надо.

Перед этим мы работали над Circle_draw(). Мы используем видимые компоненты и код файла представления Point.r следующим образом:

 

struct Point {

    const void * class;

    int x, y;           /* координаты */

};

 

#define x(p)    (((const struct Point *)(p)) -> x)

#define y(p)    (((const struct Point *)(p)) -> y)

 

Теперь в Circle_draw() можно использовать макроопределения доступа:

 

static void Circle_draw (const void * _self) {

    const struct Circle * self = _self;

 

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

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

}

 

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

 

static const struct Class _Circle = {

    sizeof(struct Circle), Circle_ctor, 0, Circle_draw

};

 

const void * Circle = & _Circle;

 

Хотя всё выглядит так, как будто мы имеем жизнеспособную стратегию распределения текста программы, реализуя класс через интерфейс, представление и файл реализации, пример точек и окружностей не выявил одну проблему: если динамически скомпонованный метод, такой как Point_draw(), не будет перезаписан в подклассе, дескриптор типа подкласса должен указывать на функцию, реализованную в суперклассе. Однако, имя функции определяется там как static, так что селектор не может быть обманут. Мы увидим чистое решение этой проблемы в главе 6. В качестве временной меры, мы бы избегали использования static в этом случае, декларировали заголовок функции только в файле реализации подкласса, а также использовали имя этой функции для инициализации описания типа для подкласса.

 

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