Справочное описание GObject |
---|
Формирование цепочки часто неточно определяется следующим набором условий:
Родительский класс A определяет общий виртуальный метод с именем foo
и
обеспечивает реализацию по умолчанию.
Дочерний класс B перереализует метод foo
.
В методе B::foo, дочерний класс B вызывает его родительский классовый метод A::foo.
Эту идиому можно использовать по разному:
Вам нужно изменить поведение класса без изменения его кода. Вы создаёте подкласс чтобы унаследовать его реализацию, перереализуете общий виртуальный метод для незначительного изменения поведения и формируете цепочку убедившись что предыдущее поведение действительно не изменилось, а только расширилось.
Вы ленивы, у вас есть доступ к исходному коду родительского класса но вы не хотите изменять его добавляя вызовы метода в новые специализированные вызовы метода: быстрее хакнуть дочерний класс сформировав цепочку чем изменять родительский для вызова вниз.
Вам нужно реализовать шаблон Chain Of Responsability: каждый объект наследует дерево сформированных цепочек родителя (обычно, в начале или в конце метода) гарантируя что каждый их обработчик выполнится в свою очередь.
Я лично не уверен что два последних способа использования являются хорошей идеей, н так как эта идиома программирования часто используется, этот раздел попытается объяснить как это реализуется.
Для явного формирования цепочки в реализации виртуального метода в родительском классе, вам сначала нужно обработать оригинальную структуру родительского класса. Этот указатель можно зтем использовать для доступа к оригинальному указателю функции класса и вызывать её непосредственно. [13]
Функция g_type_class_peek_parent
используется для доступа к оригинальной родительской классовой структуре. Её ввод - это указатель на класс производного объекта
и она возвращает указатель на оригинальную родительскую структуру класса. Код ниже показывает как вы должны её использовать:
static void
b_method_to_call (B *obj, int a)
{
BClass *klass;
AClass *parent_class;
klass = B_GET_CLASS (obj);
parent_class = g_type_class_peek_parent (klass);
/* do stuff before chain up */
parent_class->method_to_call (obj, a);
/* do stuff after chain up */
}
Многие люди использующие эту идиому в GTK+ хранят указатель на родительскую классовую структуру в глобальной статической переменной,
чтобы избегать расточительных вызовов
g_type_class_peek_parent
для каждого вызова функции.
Обычно, class_init callback-функция инициализирует глобальную статичную переменную.
Это делает gtk/gtkhscale.c
.
[13]
Оригинальное прилагательное используемое в этом предложении весьма вредное.
Для полного понимания что это значит, вы должны повторить как сструктуры класса инициализируются: для каждого объектного типа,
классовая структура привязанная к объекту создаётся с помощью начального копирования классовой сструктуры родительского типа
(просто memcpy
) а зтем вызывается class_init callback-функция в результирующей структуре.
Так как class_init callback-функция отвечает за перезапись классовой сструктуры с пользовательской перереализацией методов класса,
мы не можем просто использовать изменённую копию родительской сструктуры класса сохранённую в нашем производном экземпляре.
Мы хотим получить копию классовой сструктуры экземпляра родительского класса.