Автоматическое создание объектных полей с помощью RTTI в Delphi

Вольный перевод поста Automagically Creating Object Fields with RTTI

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

Проблема

Мне нужны классы следующего вида

Но я слишком ленив чтобы писать в каждом классе раз примерно одинаковый конструктор и деструктор. Что мне делать?

Результат

Используя надлежащую инфраструктуру, код выше может быть переписан следующим образом

Вся реализация скрыта в классе TGpManaged, который описан ниже. Он реализован в модуле GpAutoCreate, который является частью моего open-source проекта GpDelphiUnits, вместе с тестовой программой TestGpAutoCreate.

Решение

Класс TGpManaged реализует только конструктор и деструктор. Конструктор автоматически создаёт поля в классах-потомках, а деструктор автоматически уничтожает их.

Не всегда хорошая идея автоматически создавать и уничтожать все поля. Поля, управление которыми будет проходить в автоматическом режиме, должны быть помечены атрибутом [GpManaged], который реализован в этом же модуле.

Атрибут может быть указан в двух формах:

Первая форма [GpManaged] которая будет вызывать конструктор без параметров для создания объекта помеченного атрибутом.

Вторая форма [GpManaged(false)] или [GpManaged(true)] которая будет вызывать конструктор с одним параметром с типом Boolean для создания объекта помеченного атрибутом.

Поддержка конструкторов с другими типами параметров может быть добавлена в дальнейшем.

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

Более детально реализацию GpManagedAttribute вы можете посмотреть в исходном коде.

Создание объектов

Поля помеченные любой версией атрибута [GpManaged] создаются в конструкторе TGpManaged.Create.

Код сначала обращается к расширенному контексту RTTI и ищет информацию об объекте который создаётся (ctx.GetType(Self.ClassType)). Потом происходит перебор всех полей объявленный в этом объекте.

Для каждого поля проверяется помечено ли оно атрибутом [GpManaged]. Если поле не отмечено то происходит переход к следующему полю.

Если же поле помечено [GpManaged], то происходит цикл по всем методам с именем Create (я намеренно отказался от поддержки конструкторов с другими именами). Для каждого найденного метода происходит проверка, содержит ли он соответствующее число параметров и их типы.

Если соответствие найдено то используется ctor.Invoke(f.FieldType.AsInstance.MetaclassType) для вызова найденного конструктора. Подходящие параметры конструктора передаются вторым аргументом Invoke. Результат вызова конструктора помещается поле методом f.SetValue.

Затем вся процедура повторяется для следующего поля.

Уничтожение полей

Поля очищаются похожим образом, только взамен конструктора вызывается деструктор Destroy. Код проще потому что не требуется проверять каком именно деструктор вызывать.

Потенциальные проблемы

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

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

Если вы нашли ошибку, пожалуйста, выделите фрагмент текста и нажмите Ctrl+Enter.

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *