GLib
  • Initial page
  • I. Концепция
    • Background
    • Типы данных и программирование
  • Динамическая система типов Glib
    • Введение
    • Функции копирования
    • Соглашения
    • Неинстанциируемые не классифицированные базовые типы
    • Инстанциируемые классифицируемые типы: объекты
    • Неинстанциированные классифицированные типы: Интерфейсы.
  • Основной класс GObject
    • Введение
    • Инстанциация объекта
    • Объектное управление памятью
    • Свойства объекта
  • Система сообщений GObject
    • Замыкания
    • Сигналы
  • II. Описание API
    • GType
    • GTypePlugin
    • GTypeModule
    • GObject часть 1
    • GObject часть 2
    • Enums and Flags
    • GBoxed
    • Generic Values
    • Parameters and Values часть 1
    • Parameters and Values часть 2
    • Parameters and Values часть 3, ага
    • Value arrays
  • III. Описание инструментов
    • glib-mkenums
    • glib-genmarshal
    • gobject-query
  • IV. Руководство
    • Как определить и реализовать новый GObject?
    • Объектные методы
    • Как определять и реализовывать Интерфейсы?
    • Как создавать и использовать сигналы
    • Как пользователи могут злоупотреблять сигналами (и почему некоторые думают что это хорошо)
  • V. Об инструментах
    • Об инструментах
  • GTK 4
    • GtkApplication и GtkApplicationWindow
    • Widgets
Powered by GitBook
On this page

Was this helpful?

  1. IV. Руководство

Объектные методы

PreviousКак определить и реализовать новый GObject?NextКак определять и реализовывать Интерфейсы?

Last updated 6 years ago

Was this helpful?

Так же как в C++, есть много разных способов определить методы объекта и дополнить их: следующее описание и разделы используют словарь C++. (Ожидается что читатель знает основные базовые термины C++. Те кто давно не писал код на C++ могут обратиться например к чтобы освежить свою память.)

  • невиртуальные общие методы (non-virtual public methods),

  • виртуальные общие методы (virtual public methods) и

  • виртуальные закрытые методы (virtual private methods)

Не-виртуальные общие методы

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

/* декларация в заголовочном файле. */
void maman_bar_do_action (MamanBar *self, /* parameters */);
/* реализация в исходном фале */
void maman_bar_do_action (MamanBar *self, /* parameters */)
{
  /* наполнение. */
}

В этом нет ничего страшного.

Виртуальные общие методы

Это предпочтительный способ создания полиморфных объектов GObjects. Всё что вам нужно сделать это определить основной метод и его классовую функцию в общем заголовке, реализовать основной метод в исходном файле и перереализовать классовую функцию в каждом объекте который вас интересует.

/* декларация в maman-bar.h. */
struct _MamanBarClass {
  GObjectClass parent;

  /* наполнение */
  void (*do_action) (MamanBar *self, /* параметры */);
};
void maman_bar_do_action (MamanBar *self, /* параметры */);
/* реализация в maman-bar.c */
void maman_bar_do_action (MamanBar *self, /* параметры */)
{
  MAMAN_BAR_GET_CLASS (self)->do_action (self, /* параметры */);
}

Код выше просто переадресует вызов do_action в подходящую классовую функцию. Некоторые пользователи, заинтересованные в производительности, не обеспечивают maman_bar_do_action функцию оболочку и требуют чтобы пользователи самостоятельно разыменовывали классовый указатель. Это не очень хорошая идея в период инкапсуляции и мешает изменять реализацию объекта в последствие.

Другие пользователи, также заинтересованные в производительности, декларируют maman_bar_do_action функцию inline в заголовочном файле. Это также мешает изменять реализацию объекта в последствие (хотя проще чем требование от пользователей непосредственного разыменования классовой функции) и часто сложно написать переносимым способом (ключевое слово inline не является частью стандарта C).

Сомневайтесь, пока пользователь не показал вам твёрдые цифры о производительности функционального вызова, по сравнению с простой maman_bar_do_action в исходном файле.

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

static void 
maman_bar_real_do_action_two (MamanBar *self, /* параметры */)
{
  /* реализация по умолчанию для виртуального метода. */
}

static void
maman_bar_class_init (BarClass *klass)
{
  /* чисто виртуальный метод: мандаты реализуются в наследниках (children). */
  klass->do_action_one = NULL;
  /* просто виртуальный метод. */
  klass->do_action_two = maman_bar_real_do_action_two;
}

void maman_bar_do_action_one (MamanBar *self, /* параметры */)
{
  MAMAN_BAR_GET_CLASS (self)->do_action_one (self, /* параметры */);
}
void maman_bar_do_action_two (MamanBar *self, /* параметры */)
{
  MAMAN_BAR_GET_CLASS (self)->do_action_two (self, /* параметры */);
}

Виртуальный закрытый метод

Это очень похожие на Virtual Public методы. Просто они не имеют общих функций для непосредственного вызова функции. Заголовочный файл содержит только декларацию классовой функции:

/* декларация в maman-bar.h. */
struct _MamanBarClass {
  GObjectClass parent;

  /* наполнение */
  void (*helper_do_specific_action) (MamanBar *self, /* параметры */);
};
void maman_bar_do_any_action (MamanBar *self, /* параметры */);

Эти классовые функции часто используются для делегирования части работы в дочерние классы:

/* статичная функция аксессор: она не экспортируется за пределы этого файла. */
static void 
maman_bar_do_specific_action (MamanBar *self, /* параметры */)
{
  MAMAN_BAR_GET_CLASS (self)->do_specific_action (self, /* параметры */);
}

void maman_bar_do_any_action (MamanBar *self, /* параметры */)
{
  /* здесь произвольный код */

  /* 
   * Пробуем выполнить требуемое действие. Возможно требуемое действие не может быть реализовано
   * здесь. Поэтому мы делегируем его реализацию в дочерний класс:
   */
  maman_bar_do_specific_action (self, /* параметры */);

  /* здесь другой произвольный код */
}

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

static void
maman_bar_class_init (MamanBarClass *klass)
{
  /* чисто виртуальный метод: мандаты реализуются в наследниках (children). */
  klass->do_specific_action_one = NULL;
  /* просто виртуальный метод. */
  klass->do_specific_action_two = maman_bar_real_do_specific_action_two;
}

Наследники могут реализовать подкласс с помощью похожего кода:

static void
maman_bar_subtype_class_init (MamanBarSubTypeClass *klass)
{
  MamanBarClass *bar_class = MAMAN_BAR_CLASS (klass);
  /* реализуем чисто виртуальную функцию класса. */
  bar_class->do_specific_action_one = maman_bar_subtype_do_specific_action_one;
}

Формирование цепочки

Формирование цепочки часто неточно определяется следующим набором условий:

  • Родительский класс A определяет общий виртуальный метод с именем foo и обеспечивает реализацию по умолчанию.

  • Дочерний класс B перереализует метод foo.

  • В методе B::foo, дочерний класс B вызывает его родительский классовый метод A::foo.

Эту идиому можно использовать по разному:

  • Вам нужно изменить поведение класса без изменения его кода. Вы создаёте подкласс чтобы унаследовать его реализацию, перереализуете общий виртуальный метод для незначительного изменения поведения и формируете цепочку убедившись что предыдущее поведение действительно не изменилось, а только расширилось.

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

  • Вам нужно реализовать шаблон Chain Of Responsability: каждый объект наследует дерево сформированных цепочек родителя (обычно, в начале или в конце метода) гарантируя что каждый их обработчик выполнится в свою очередь.

Я лично не уверен что два последних способа использования являются хорошей идеей, н так как эта идиома программирования часто используется, этот раздел попытается объяснить как это реализуется.

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+ хранят указатель на родительскую классовую структуру в глобальной статической переменной, чтобы избегать расточительных вызовов для каждого вызова функции. Обычно, class_init callback-функция инициализирует глобальную статичную переменную. Это делает gtk/gtkhscale.c.

[] Оригинальное прилагательное используемое в этом предложении весьма вредное. Для полного понимания что это значит, вы должны повторить как сструктуры класса инициализируются: для каждого объектного типа, классовая структура привязанная к объекту создаётся с помощью начального копирования классовой сструктуры родительского типа (просто memcpy) а зтем вызывается class_init callback-функция в результирующей структуре. Так как class_init callback-функция отвечает за перезапись классовой сструктуры с пользовательской перереализацией методов класса, мы не можем просто использовать изменённую копию родительской сструктуры класса сохранённую в нашем производном экземпляре. Мы хотим получить копию классовой сструктуры экземпляра родительского класса.

http://www.cplusplus.com/doc/tutorial/
13
g_type_class_peek_parent
g_type_class_peek_parent
13