О глобальных переменных и RTTI#9
Бытует мнение, что глобальные переменные - большое зло. Как можно поступить, чтобы инкапcулировать множество глобальных переменных простых типов в класс, и реализовать механизм уведомлений об их изменениях?
Именно о таком я сегодня задумался. В дейстивтельности тема глобальных переменных весьма избита, и врядли здесь можно сказать что-то новое. Вывод всегда может быть один: от них нужно избавляться. Собственно главный минус, конечно же, в том, что глобальную переменную любой может изменить, при этом никакие другие модули не будут знать о таком изменении. В проекте, над которым я работаю, глобальных переменных достаточно много. Раньше было еще больше, но много чего удалось почистить. И вот я задумался, как можно легко поместить глобальные переменные внутрь некоторого класса (очевидно в виде свойств) с написанием небольшого кода так, чтобы он реализовывал механизм уведомлений при изменении. В общем то не проблема взять и сгруппировать все переменные в одном классе. Но в таком случае при описании свойства (на которое мы заменяем глобальную переменную) нам придется написать также и внутренее поле, которое хранит ее значение, нужны методы для чтения и записи значения. Метод записи при этом должен как то рассылать уведомления.
Идея, которая меня посетила, аналогична в этой части методу, который был применен в статье про настройки приложения: Настройки программы в INI с использованием RTTI#7. Суть была следующая: для описания новой настройки, нам необходимо добавить только одну единственную строку в класс описания группы настроек. Так и здесь, для расширения числа глобальных переменных необходимо добавить толко одну строку в класс описания. В итоге получаем следующее. Допустим у нас в проекте было три глобальных переменных: CurrentYear, Angle, Title. Теперь мы заменим все упоминания этих переменных в коде на эквиваленты вда globals.CurrentYear и т.д. Переменная globals - это экземпляр класса TGlobals, который описывается следующим образом:
TGlobals = class(TGlobalsContainer) public property CurrentYear : integer index 0 read getIntValue write setIntValue; property Angle : double index 1 read getFloatValue write setFloatValue; property Title : string index 2 read getStringValue write setStringValue; end;
Для включения и использования в программе новой глобальной переменной необходимо лишь дописать новое свойство в класс, это избавляет нас от написания внутренних членов класса и методов их изменения. Как и в случае вышеупомянутой статьи про INI-файл с настройками, используется нумерация свойств с помощью index. Функционал скрыт в базовом класса TGlobalsContainer:
TGlobalsContainer = class(TObject) strict private type TGlobalValue = class(TObject) propName : string; eventList : TList<TNotifyEvent>; FloatValue : double; IntValue : integer; StringValue : string; constructor Create(); destructor Destroy(); override; end; var FValues : TObjectList<TGlobalValue>; FUpdating : boolean; procedure DoNotify(idx : integer); procedure InitGlobalValues(); strict protected function getIntValue(idx: integer):integer; procedure setIntValue(idx : integer; aValue : integer); function getFloatValue(idx: integer):double; procedure setFloatValue(idx: integer; aValue:double); function getStringValue(idx:integer):string; procedure setStringValue(idx:integer; aValue: string); public constructor Create(); destructor Destroy(); override; procedure BeginUpdate(); procedure EndUpdate(); procedure AddEventHandler(propertyName : string; eventHandler : TNotifyEvent); procedure RemoveEventHandler(propertyName : string; eventHandler : TNotifyEvent = nil); end;
Доступ к полям TGlobals основан на их индексах и соответствующих методах getIntValue и т.п. Внутри себя TGlobalsContainer содержит список объектов TGlobalValue, которые отвечают за хранение значений бывших глобальных переменных, а также обработчиков событий их изменний. Итак, обращение к какому-либо свойству TGlobals ведет к вызову метода getIntValue/getFloatValue/getStringValue при чтении, и соответствующих set-методов при записи. Каждый из методов возвращает соответствующее поле IntValue/FloatValue/StringValue элемента списка FValuesList с индексом idx (этим индексом пронумеровано свойство в TGlobals). При вызове set-метода, циклически вызываются зарегистрированнные обработчики изменения значений из eventList элемента.
Поскольку весь функционал скрыт в TGlobalsContainer, то расширять TGlobals новыми свойствами весьма легко и быстро. Так же мы можем внедрить дополнтельный функционал для управления свойствами путем использования атрибутов.
Такая реализация, конечно же, в разы медленней чем использование простых переменных. Но если они редко используются, то такой подход имеет право на жизнь. Так же стоит отметить, что такую перменную не удастся преедать по ссылке. Отмечу, что здесь приведен только подход - пища для размышений, реализация не претендует на хорошую, а является лишь наброском. Исходный код с реализацией прикреплен ниже.
19.05.2012 в 10:47
Использовать LiveBindings =) В сеттерах свойств класса вызывать чего-то типа
У Марко Канту в видео про LB есть такой примерчик. Попробовал использовать это дело для работы с опциями программы: сохранение/загрузка/показ на форме. В итоге для всех трех операций потребовалось что-то около 20 строчек кода :)
19.05.2012 в 12:28
25.05.2012 в 17:46
А совсем без глобалов не пробовали? Намного проще и полезнее.
25.05.2012 в 20:15
Проще и полезней, никто не спорит. И я нигде не писал, что люблю глобальные переменные. И даже наоборот написал в самом первом абзаце что это зло, от которого надо избавляться.
28.05.2012 в 15:20
У Александра Алексеева aka GunSmoker не читали http://www.gunsmoker.ru/2011/04/blog-post.html
28.05.2012 в 17:49
читал (:
15.10.2012 в 16:45