6.10 Новый метакласс — PointClass

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

Object и Class являются основой нашей иерархии классов. Каждый класс является подклассом Object и наследует его методы, каждый метакласс является подклассом Class и взаимодействует с его конструктором. Any в разделе 6.4 показал, как может быть создан простой подкласс путём замены динамически скомпонованных методов своего суперкласса и, возможно, определением новых статически скомпонованных методов.

Теперь мы переходим к созданию классов с большей функциональностью. В качестве примера мы подключаем в нашу иерархию классов Point и Circle. Эти классы имеют новый динамически скомпонованный метод draw(); поэтому для размещения этой связи нам нужен новый метакласс. Вот файл интерфейса Point.h:

 

#include "Object.h"

 

extern const void * Point;      /* new(Point, x, y); */

 

void draw (const void * self);

void move (void * point, int dx, int dy);

 

extern const void * PointClass; /* добавление draw */

 

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

Как и раньше, файл представления Point.r содержит структуру объекта struct Point со своими макросами доступа и селекторы суперкласса вместе со структурой для метакласса:

 

#include "Object.r"

 

struct Point { const struct Object _; /* Point : Object */

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

};

 

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

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

 

void super_draw (const void * class, const void * self);

 

struct PointClass {

    const struct Class _;            /* PointClass : Class */

    void (* draw) (const void * self);

};

 

Файл реализации Point.c содержит move(), Point_draw(), draw() и super_draw(). Эти методы написаны как и прежде; в предыдущем разделе мы увидели технику для селектора суперкласса. Конструктор должен вызывать конструктор суперкласса:

 

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

    struct Point * self = super_ctor(Point, _self, app);

 

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

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

    return self;

}

 

Одна из новых идей в этом файле это конструктор метакласса. Для выполнения наследования он вызывает конструктор суперкласса, а затем чтобы переписать новый динамически компонуемый метод draw() использует тот же цикл, что и Class_ctor():

 

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

    struct PointClass * self

                    = super_ctor(PointClass, _self, app);

    typedef void (* voidf) ();

    voidf selector;

    va_list ap = * app;

 

    while ((selector = va_arg(ap, voidf))) {

        voidf method = va_arg(ap, voidf);

 

        if (selector == (voidf) draw)

            * (voidf *) & self -> draw = method;

    }

    return self;

}

 

Обратите внимание, что мы распределяем пары селектор / метод в списке аргументов с помощью конструктора суперкласса: ap предпринимает все, что Class_ctor() возвращает в * app, и после этого запускает цикл.

С помощью этого конструктора можно совместно динамически проинициализировать описания новых классов: PointClass создаётся с описанием Class, а затем Point создаётся с описанием класса PointClass:

 

void initPoint (void)

{

    if (! PointClass)

        PointClass = new(Class, "PointClass",

                Class, sizeof(struct PointClass),

                ctor, PointClass_ctor,

                0);

 

    if (! Point)

        Point = new(PointClass, "Point",

                Object, sizeof(struct Point),

                ctor, Point_ctor,

                draw, Point_draw,

                0);

}

 

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

В главе 9 мы будем выполнять эту инициализацию автоматически. На данный момент к интерфейсу в Point.h добавляется initPoint() и эта функция обязательно должна быть вызвана прежде чем мы сможем создавать точки или подклассы. Функция выполняет внутреннюю проверку, поэтому её можно вызывать многократно — она создаст лишь одно описание класса PointClass и Point.

До тех пор, пока initPoint() вызывается из main(), можно использовать тестовую программу points из раздела 4.1, и мы получаем тот же результат:

 

$ points p

"." at 1,2

"." at 11,22

 

Circle является подклассом Point, представленным в главе 4. Добавив его в иерархию классов, можно удалить уродливый код в конструкторе, показанный в разделе 4.7:

 

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

    struct Circle * self = super_ctor(Circle, _self, app);

 

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

    return self;

}

 

Конечно, надо добавить функцию инициализации initCircle(), которая будет вызываться из main() до того, как можно будет создавать окружности:

 

void initCircle (void)

{

    if (! Circle) {

        initPoint();

        Circle = new(PointClass, "Circle",

                Point, sizeof(struct Circle),

                ctor, Circle_ctor,

                draw, Circle_draw,

                0);

    }

}

 

Поскольку Circle зависит от Point, до инициализации Circle вызывается initPoint(). Все эти функции делают свою реальную работу только один раз и их можно вызывать в любом порядке до тех пор, как мы учитываем взаимозависимости внутри самой такой функции.

 

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