Rambler's Top100
"Knowledge itself is power"
F.Bacon
Поиск | Карта сайта | Помощь | О проекте | ТТХ  
 Подземелье Магов
  
 

Фильтр по датам

 
 К н и г и
 
Книжная полка
 
 
Библиотека
 
  
  
 


Поиск
 
Поиск по КС
Поиск в статьях
Яndex© + Google©
Поиск книг

 
  
Тематический каталог
Все манускрипты

 
  
Карта VCL
ОШИБКИ
Сообщения системы

 
Форумы
 
Круглый стол
Новые вопросы

 
  
Базарная площадь
Городская площадь

 
   
С Л С

 
Летопись
 
Королевские Хроники
Рыцарский Зал
Глас народа!

 
  
ТТХ
Конкурсы
Королевская клюква

 
Разделы
 
Hello, World!
Лицей

Квинтана

 
  
Сокровищница
Подземелье Магов
Подводные камни
Свитки

 
  
Школа ОБЕРОНА

 
  
Арсенальная башня
Фолианты
Полигон

 
  
Книга Песка
Дальние земли

 
  
АРХИВЫ

 
 

Сейчас на сайте присутствуют:
 
  
 
Во Флориде и в Королевстве сейчас  18:05[Войти] | [Зарегистрироваться]

Хранение глобальных переменных в модуле данных

Матвей Карпушин
дата публикации 14-08-2008 09:30

Хранение глобальных переменных в модуле данных

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

Теоретические изыскания

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

  1. Чтение значение переменной
  2. Запись значения переменной
  3. Уведомление заинтересованных объектов об изменении переменных

Обычно программисты не обращают внимания на необходимость уведомлений об изменении значений переменных, однако в объектно-ориентированной системе это необходимо. Рассмотрим пример простейшего текстового редактора. Текстовый редактор должен обладать возможностью изменения шрифта и сохранять выбранный пользователем шрифт в файле конфигурации. С точки зрения пользователя к редактору предъявляются очень простые требования. Рассмотрим это с точки зрения объектной модели. Здесь можно выделить как минимум три объекта: собственно редактор текста, объект редактирования настроек и объект чтения/записи. Объект чтения/записи, равно как и объект редактирования настроек могут изменять глобальную переменную, описывающую шрифт. Редактор текста должен получать оповещения об изменении шрифта и в соответствии с этими изменениями изменять отображение текста. Возможно, конечно, что при изменении шрифта соответствующие объекты будут напрямую вызывать внутренние методы редактора, оповещающие редактор об изменении глобальной переменной или модифицировать доступные свойства, однако сути процесса запись — уведомление — чтение это не меняет.

Рассмотрим абстрактные возможности реализации хранения глобальных переменных. Первый вариант, который обычно реализуется программистом, являются глобальные переменные (объекты) или вынесение переменных в интерфейсную часть объекта, что в общем-то аналогично. Рассмотрим взаимодействие между объектами программы в этом случае.


Рисунок 1

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

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


Рисунок 2

Недостатком этого является то, что диспетчер должен знать о структуре объектов программы и иметь ссылки на эти объекты. Таким образом, объекты программы практически не связаны друг с другом и могу свободно изменяться. При изменении кода объекта (и/или удалении/добавлении объектов) достаточно поменять код диспетчера, а код остальных объектов приложения останется без изменения.

Дальнейшим шагом является связывание диспетчера и всех объектов приложения неким способом, при котором диспетчеру нет необходимости знать об особенностях реализации объектов, заинтересованных в получении уведомлении. Несомненно, самым простым способом является отправка сообщения, которое будет передано заинтересованным объектам, средствами операционной системы. Однако для этого требуется, как минимум, чтобы заинтересованные объекты были визуальными, т.е. являлись наследниками TWinControl, что не всегда выполнимо. Другим способом является регистрация заинтересованным объектом некоего объекта уведомления (TLink) и получение уведомлений посредством этого промежуточного объекта. Рассмотрим диаграмму взаимодействия объектов для этого случая


Рисунок 3

Как видно при такой реализации объекты слабо связаны друг с другом, что открывает возможность динамического создания объектов. Глобальный объект с переменными может ничего не знать о других объектах приложения. Уведомления об изменении переменной в этом случае передается в связующий объект, в котором генерируется соответствующее событие. Список всех связующих объектов хранится внутри глобального объекта. Сам связующий объект создается объектами приложения, и ссылка на него хранится внутри соответствующего объекта. После создания связующего объекта его события настраиваются на вызов внутренних процедур объекта. Несомненным достоинством этого метода является слабая взаимосвязь объектов приложения, однако платой за это является большой объем кода. Несколько упростить реализацию позволяет применение в случае уведомления идентификаторов изменяемой переменной.

Решение в лоб

Рассмотрим пример программы, в которой в качестве диспетчера используется модуль данных (GVarLink.zip).

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

В модуле данных необходимо объявить объект, который будет уведомлять формы об изменении данных:

  TVariableLink = class(TObject)
  private
    FOnChangeBackColor: TNotifyEvent;
    FOnChangeTextColor: TNotifyEvent;
    procedure SetOnChangeBackColor(const Value: TNotifyEvent);
    procedure SetOnChangeTextColor(const Value: TNotifyEvent);
  protected
    FDataModule: TDM;
    procedure AssignClient(AClient: TObject); virtual;
    procedure SetDataModule(const Value: TDM); virtual;
  public
    constructor Create(AClient: TObject); virtual;
    destructor Destroy; override;
    property DataModule: TDM read FDataModule write SetDataModule;

    property OnChangeTextColor: TNotifyEvent read FOnChangeTextColor write SetOnChangeTextColor;
    property OnChangeBackColor: TNotifyEvent read FOnChangeBackColor write SetOnChangeBackColor;
  end;

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

procedure TVariableLink.SetDataModule(const Value: TDM);
begin
  // Если ранее линк был привязан к модулю данных
  if FDataModule <> nil then FDataModule.UnRegisterChanges(Self);
  // Если новый модуль данных не назначен, то выходим
  if Value = nil then exit;
  // Регистрируемся в новом модуле данных
  Value.RegisterChanges(Self);
end;

Также объект содержит события OnChangeTextColor и OnChangeBackColor которые устанавливаются в объекте, желающем получать уведомления об изменении глобальных переменных. Сам объект модуля данных

  TDM = class(TDataModule)
  private
    FBackColor: TColor;
    FTextColor: TColor;
    procedure SetBackColor(const Value: TColor);
    procedure SetTextColor(const Value: TColor);
    { Private declarations }
  protected
    FClients: TList;
    procedure RegisterChanges(Value: TVariableLink);
    procedure UnRegisterChanges(Value: TVariableLink);
  public
    constructor Create(AOwner: TComponent); override;
    destructor Destroy; override;
    { Public declarations }
    property TextColor: TColor read FTextColor write SetTextColor;
    property BackColor: TColor read FBackColor write SetBackColor;
  end;

Здесь собственно объявляются свойства TextColor и BackColor, которые и исполняют роль глобальных переменных. Помимо этого объявляется список объектов для посылки уведомлений FClients, а также методы для регистрации и разрегистрации объектов отправки уведомлений. Рассмотрим подробнее эти процедуры

procedure TDM.RegisterChanges(Value: TVariableLink);
begin
  Value.FDataModule := Self;
  FClients.Add(Value);
end;
procedure TDM.UnRegisterChanges(Value: TVariableLink);
var
  I: Integer;
begin
  for I := 0 to FClients.Count - 1 do
    if FClients[I] = Value then
    begin
      Value.FDataModule := nil;
      FClients.Delete(I);
      Break;
    end;
end;

При изменении свойства в модуле данных вызывается соответствующая процедура, которая собственно и вызывает события связующего объекта

procedure TDM.SetBackColor(const Value: TColor);
var
  I: Integer;
begin
  if FBackColor = Value then Exit;
  FBackColor := Value;
  for I := 0 to FClients.Count - 1 do
    with TVariableLink(FClients[I]) do
      // Перехватываем исключения в обработчике
      try
        if Assigned(FOnChangeBackColor) then FOnChangeBackColor(Self);
      except
      end;
end;

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

constructor TfmMain.Create(AOwner: TComponent);
begin
  inherited;
  // Создаем линк на глобальные данные программы
  FVariableLink := TVariableLink.Create(Self);
  FVariableLink.DataModule := DM;
  FVariableLink.OnChangeTextColor := DoChangeTextColor;
  FVariableLink.OnChangeBackColor := DoChangeBackColor;
end;

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

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

Интерфейсы наше всё

Всё выше сказанное замечательно работает, но имеется маленькая ложка дегтя. Каждый объект в конструкторе должен регистрировать себя в диспетчере. В случае отсутствия регистрации объект не будет получать оповещение об изменении глобальных переменных. Поскольку код регистрации совершенно идентичен для всех объектов, то возникает соблазн перегрузить конструктор базового объекта и создавать наследников уже от своего объекта, те фактически появляется потребность создать некий каркас приложения на основе VCL. Однако, если мы создаем свой каркас, изменяя базовые объекты, то можно для каждого объекта создать дополнительный интерфейс, через который можно передавать уведомления. Соответственно, само хранилище переменных предоставляет глобальный интерфейс, который могут использовать все объекты приложения. Недостатком этого метода является то, что набор глобальных переменных будет известен только на момент компиляции приложения. Соответственно если мы хотим обозначать переменную перечислимым типом, то этот тип будет известен только на момент компиляции приложения. Однако, алгоритмы работы с глобальными переменными известны намного ранее момента компиляции и имеет смысл вынести работу с глобальными переменными в отдельную библиотеку. В языке C++ в этом случае используются шаблоны. В случае Delphi придется отказаться от перечислимого типа и обозначать переменные при помощи целочисленных идентификаторов. Рассмотрим простой пример реализации приведенной концепции (GVarIface.zip).

Описываем используемые идентификаторы в секции констант:

const
  GVAR_TEXT_COLOR = 0;
  GVAR_BACK_COLOR = 1;

Создаем описание используемых интерфейсов:

  INotifyVariable = interface
    ['{D2748414-E9B2-42F8-9161-3323D901CA11}']
    procedure VariableChanged(const VariableID: Integer; const NewValue: Variant);
  end;

  IGlobalVariable = interface
    ['{3EF8706A-FBC7-43D0-AB77-51252A553A7C}']
    procedure RegisterChanges(NotifyInterface: INotifyVariable);
    procedure UnRegisterChanges(NotifyInterface: INotifyVariable);
    function GetVariable(VariableID: Integer): Variant;
    procedure SetVariable(VariableID: Integer; const Value: Variant);
    property Variable[VariableID: Integer]: Variant read GetVariable write SetVariable;
  end;

IGlobalVariable является интерфейсом к хранилищу глобальных переменных и позволяет сохранять и читать значения глобальных переменных а также регистрировать объекты для получения уведомлений об изменении значений переменных. Уведомления к объектам будут передаваться через специальный интерфейс INotifyVariable.

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

var
  GVariables: IGlobalVariable;

Сама работа хранилища переменных реализована в защищенной секции модуля при помощи классов TDataHolder = class(TInterfacedObject, IGlobalVariable) и TVariableHolder = class(TObject). При создании главной формы приложения необходимо инициализировать глобальные переменные и зарегистрировать главную форму для получения уведомлений об их изменении.

constructor TfmMain.Create(AOwner: TComponent);
begin
  inherited;
  if GVariables <> nil then
  begin
    // Устанавливаем значения глобальных переменных по умолчанию
    GVariables.Variable[GVAR_TEXT_COLOR] := Memo1.Font.Color;
    GVariables.Variable[GVAR_BACK_COLOR] := Memo1.Color;
    // Подписываемся на получение изменений
    GVariables.RegisterChanges(Self as INotifyVariable);
  end;
end;

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

procedure TfmMain.VariableChanged(const VariableID: Integer;
  const NewValue: Variant);
begin
  // Для пустого значения нет корректной конвертации
  if NewValue = null then Exit;
  case VariableID of
    GVAR_TEXT_COLOR: Memo1.Font.Color := NewValue;
    GVAR_BACK_COLOR: Memo1.Color := NewValue;
  end;
end;

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

destructor TfmMain.Destroy;
begin
  if GVariables <> nil then
    GVariables.UnRegisterChanges(Self as INotifyVariable);
  inherited;
end;

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

Событийное программирование

Одним из сторонних эффектов является возможность событийного программирования на уровне приложения. Сама концепция событийного программирования активно используется как в Windows, так и в Delphi, однако очень часто обработка глобальных событий производится процедурными методами. Рассмотрим пример реализации такого приложения (GVarActn.zip).

Одним из часто реализуемых алгоритмов является авторизация пользователя в базе данных. Рассмотрим данный процесс подробнее. После ввода пользователем своего имени (пароля) (GVAR_USER_LOGIN) программа должна проверить введенное имя пользователя (пароль) в базе данных. Однако, в этом месте стоит сделать оговорку. Дело в том, что соединение с базой данных не может быть устойчивым (возможны разрывы связи, перезагрузка сервера базы данных и тд). В случае восстановления соединения с базой данных программа должна автоматически повторить процедуру регистрации пользователя. Сама процедура регистрации пользователя должна вернуть уникальный идентификатор пользователя в базе данных (GVAR_USER_ID) или null в случае отсутствия пользователя с указанными аутентификационными данными в СУБД. После изменения идентификатора пользователя программа должна получить полное имя пользователя (GVAR_USER_NAME), которое показывается в строке состояния.

Регистрация пользователя обеспечивается функцией UserLogin, которая возвращает false при неуспешной попытке регистрации пользователя. Получение полного имени пользователя обеспечивается процедурой GetFullName. Эти процедуры являются процедурными обертками над запросами к базе данных. Вся же логика работы приложения с алгоритмом авторизации пользователя сконцентрирована в процедуре обработчике TDM.VariableChanged и максимальна прозрачна:

procedure TDM.VariableChanged(const VariableID: Integer;
  const NewValue: Variant);
begin
  case VariableID of
    GVAR_USER_LOGIN:
      if NewValue = null then
        GVariables.Variable[GVAR_USER_ID] := null
      else
        if not UserLogin then
          GVariables.Variable[GVAR_USER_LOGIN] := null;
    GVAR_USER_ID:
      if NewValue = null then
        GVariables.Variable[GVAR_USER_NAME] := null
      else
        GetFullName;
  end;
end;

Кроме этого, в основном окне приложения похожий обработчик меняет текст на визуальном компоненте, расположенном на главной форме приложения:

procedure TfmMain.VariableChanged(const VariableID: Integer;
  const NewValue: Variant);
begin
  case VariableID of
    GVAR_USER_NAME:
      if NewValue = null then
        StatusBar1.SimpleText := 'Пользователь не зарегистрирован'
      else
        StatusBar1.SimpleText := NewValue;
  end;
end;

Таким образом, действия алгоритма авторизации пользователя выполняются в соответствующих модулях — главная форма приложения управляет реакцией визуальных компонентов, модуль данных — обеспечивает связь с базой данных. Кроме того, хотелось бы обратить на начальную инициализацию глобальных переменных, которая осуществляется в модуле данных:

constructor TDM.Create(AOwner: TComponent);
begin
  inherited;
  if GVariables <> nil then
  begin
    // Подписываемся на получение изменений
    GVariables.RegisterChanges(Self as INotifyVariable);
    // Начальная инициализация
    GVariables.Variable[GVAR_USER_LOGIN] := null;
  end;
end;

В самом деле, оператор GVariables.Variable[GVAR_USER_LOGIN] := null; создает глобальную переменную с идентификатором GVAR_USER_LOGIN, вследствие чего вызывается событие VariableChanged, в котором в свою очередь будут соответственно установлены все остальные переменные как для случая разлогиненого пользователя, а поскольку главная форма приложения создана до модуля данных, то соответственно и обновятся визуальные компоненты.

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

Заключение

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

На этом логично поставить точку, но я думаю, что стоит поставить многоточие и продолжить в будущем рассказом о каркасе приложения на основе VCL.

К статье прилагаются примеры:




Смотрите также материалы по темам:


 Обсуждение материала [ 16-11-2012 06:44 ] 1 сообщение
  
Время на сайте: GMT минус 5 часов

Если вы заметили орфографическую ошибку на этой странице, просто выделите ошибку мышью и нажмите Ctrl+Enter.
Функция может не работать в некоторых версиях броузеров.

Web hosting for this web site provided by DotNetPark (ASP.NET, SharePoint, MS SQL hosting)  
Software for IIS, Hyper-V, MS SQL. Tools for Windows server administrators. Server migration utilities  

 
© При использовании любых материалов «Королевства Delphi» необходимо указывать источник информации. Перепечатка авторских статей возможна только при согласии всех авторов и администрации сайта.
Все используемые на сайте торговые марки являются собственностью их производителей.

Яндекс цитирования