6.10 Новый метакласс — PointClass |
Предыдущая Содержание Следующая |
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(). Все эти функции делают свою реальную работу только один раз и их можно вызывать в любом порядке до тех пор, как мы учитываем взаимозависимости внутри самой такой функции.
|
Предыдущая Содержание Следующая |