6.2 Метаклассы

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

Оказывается, что требование (5) не усугубляют наши проблемы — фактически оно указывает путь к их решению. Так же как окружность добавляет информацию к точке, также добавляется общая информация описания классов для точек и окружностей — полиморфный draw() — к описанию класса для обоих этих классов.

Иными словами: пока два класса имеют одни и те же динамически скомпонованные методы, хотя и с различными реализациями, они могут использовать одну и ту же struct Class для хранения связей — это случай для Point и Circle. После добавления ещё одного динамически скомпонованного метода необходимо удлинить struct Class, чтобы обеспечить место для новой связи — это то, как мы получили из класса только с конструктором и деструктором классы, подобные Point, с добавленным компонентом .draw.

Удлинение структуры и есть то, что мы назвали наследованием, то есть мы обнаруживаем, что описания классов с одинаковым набором методов образуют класс, и что есть наследование среди классов описаний классов!

Мы называем класс описаний классов метаклассом. Метакласс ведёт себя так же, как класс: Point и Circle, описания для всех точек и всех кругов, это два объекта в метаклассе PointClass, потому что они оба могут описывать, как рисовать. Метакласс имеет методы: можно запросить объект, подобный Point или Circle о размере объектов, точек или окружностей, которые он описывает, или можно было бы запросить объект Circle, действительно ли Point описывает суперкласс окружностей.

Динамически скомпонованные методы могут выполнять разные действия для объектов разных классов. Нужны ли метаклассам динамически скомпонованные методы? Деструктор в PointClass вызывался бы как результат delete(Point) или delete(Circle), то есть когда мы попытаемся удалить описание класса для точек или окружностей. Этот деструктор должен возвращать пустой указатель, поскольку удаление описания класса явно не является хорошей идеей. Конструктор метакласса гораздо полезнее:

 

Circle = new(PointClass,     /* запрос метакласса */

    "Circle",                /* создать описание класса */

    Point,                   /* с помощью этого суперкласса, */

    sizeof(struct Circle),   /* данного размера для таких объектов, */

    ctor, Circle_ctor,       /* это конструктор, */

    draw, Circle_draw,       /* а это метод рисования. */

    0);                      /* конец списка */

 

Этот вызов должен создать описание класса для класса, объекты которого могут быть сконструированы, уничтожены и отрисованы. Поскольку отрисовка является новой идеей, общей для описаний всех классов в PointClass, кажется достаточно разумным ожидать, что конструктор PointClass мог бы по крайней мере знать, как установить связь с методом рисования в новом описании.

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

 

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