Выбор методов для операций
В объектно-ориентированных языках одна и та же операция нередко реализуется одновременно несколькими методами (полиморфизм). Выбор нужного метода осуществляется в объектно-ориентированных окружениях автоматически, и в этом одно из главных преимуществ объектно-ориенти-рованных языков. Если программа, разработанная по объектно-ориенти-рованной методологии, реализуется на языке, не являющемся объектно-ориентированным, то проблема выбора методов обычно решается во время выполнения программы.
Методы, выбор которых возможен во время компиляции, на языке C могут быть реализованы как непосредственные вызовы соответствующих функций. Это относится прежде всего к тем операциям, которые реализуются только одним методом, что снимает проблему выбора. Например, все методы в классе Window уникальны (единственны). Однако наиболее общий подход состоит в определении для каждого класса дескриптора, содержащего указатели на все методы для каждой операции, видимой из этого класса, включая унаследованные операции. Каждый дескриптор класса является структурой языка C, элементами которой являются все операции, определённые в этом классе или унаследованные им от суперкласса. Следующий фрагмент программы содержит определения дескрипторов класса для классов Item, Shape, Box и Circle:
struct ItemClass
{
char* class_name;
void(* cut)();
void(* move)();
Boolean(* pick)();
void(* ungroup)();
};
struct ShapeClass
{
char* class_name;
void(* cut)();
void(* move)();
Boolean(* pick)();
void(* ungroup)();
void(* write)();
};
struct BoxClass
{
char* class_name;
void(* cut)();
void(* move)();
Boolean(* pick)();
void(* ungroup)();
void(* write)();
};
struct CircleClass
{
char* class_name;
void(* cut)();
void(* move)();
Boolean(* pick)();
void(* ungroup)();
void(* write)();
};
В дескрипторе класса определены операции, видимые в этом классе, но остаётся необходимость определить и проинициализировать дескрипторы класса для каждого класса: в каждом поле дескриптора класса должно стоять имя функции, которая реализует метод, определённый в этом классе или унаследованный им (например, класс Box наследует операцию move у класса Shape, но заменяет операции pick и write своими собственными методами:
struct BoxClass BoxClass =
{
«Box»,
Shape__cut,
Shape__move,
Box__pick,
Shape__ungroup,
Box__write,
};
struct CircleClass CircleClass =
{
«Circle»,
Shape__cut,
Shape__move,
Circle__pick,
Shape__ungroup,
Circle__write,
};
Если у класса есть атрибуты уровня класса, их тоже можно запомнить в дополнительных полях дескриптора класса. Например, можно поместить в дескриптор класса имя этого класса и использовать его во время отладки или каким-либо другим образом.
Отметим, что дескриптор класса не нужен абстрактному классу (например, Shape). Когда порождается новый объект, указатель на его дескриптор класса помещается в поле Class структуры, представляющей этот объект:
struct Circle * create_circle(x0,y0,radius0)
Length x0,y0,radius0;
{
struct Circle * new_circle;
new_circle =
(struct Circle*)malloc(sizeof(struct Circle));
new_circle->class = &CircleClass;
new_circle->x = x0;
new_circle->y = y0;
new_circle-> radius = radius0;
return new_circle;
};
Если выбор метода для операции должен быть сделан во время выполнения программы, то для определения нужной функции используется дескриптор класса. Например, вызов операции pick для формы (Shape), определяемой во время выполнения программы, требует следующего кода:
struct Shape* shape;
Length x,y;
Boolean status;
status = (*shape->class->pick)(shape,x,y);
Динамический выбор метода требует двух дополнительных обращений к памяти по сравнению с непосредственным вызовом функции.
Реализация зависимостей
Для реализации зависимостей есть те же две возможности, что и в случае объектно-ориентированных окружений: отображение зависимостей на указатели (ссылки) или реализация зависимостей с помощью вспомогательных объектов.
Если зависимости отображаются на указатели (наиболее традиционный подход для бинарных зависимостей), для этих указателей предусматриваются дополнительные поля в структурах (записях), представляющих объекты взаимно-зависимых классов; если зависимость множественная (хотя бы в одну из сторон), то представляющий её указатель ссылается не на класс, а на последовательность (в смысле STL) указателей объектов соответствующего класса. Поскольку все зависимости двусторонние, каждый объект из указанной последовательности тоже должен иметь указатель, определяющий рассматриваемую зависимость.
С помощью вспомогательных объектов обычно реализуются зависимости, которые трудно представить через указатели (например, зависимости между несколькими классами).
В качестве примера первого способа реализации зависимостей рассмотрим реализацию зависимости (типа «много к одному») между классами Item и Group:
struct Item
{
struct ItemClass* class;
struct Group* group;
};
struct Group
{
struct GroupClass* class;
int item_count;
struct Item** items;
};