Шаблон программирования Singleton в Delphi.

Расскажу сегодня о singleton‘ах — так называемых «одиночках».

Суть singleton’а в том, что он гарантирует, что он существует лишь в единственном экземпляре, и предоставляет доступ в единственной точке кода.

Разберём не просто суть singleton’а, но и сделаем из него шаблон для наследования в производственных масштабах. :]

Грамотный подход к организации ООП программы, используя singleton’ы, зачастую даёт очень оптимальные результаты.

Поясню вкратце одну эволюционную цепочку в программировании, которая логически должна приводить к singleton’ам:

  • Допустим начинающий программист во всю и без опаски юзает глобальные переменные. Не жалуясь на всё это потому что вроде как это всё ловко и лишний раз о каких-то структурах думать не надо.
  • Конечно это работает с университетскими задачками, но не более того.
  • Начав писать норм прогу по размерам и дойдя до 3к-4к строк, кодер уже врубается, что дальше он уже не уедет на глобальных переменных, минимум от того что пространство имён он уже  забил.
  • Ладно думает, буду источать оптимальность! И начинает запихивать самые важные переменные в классы и структуры, а их в свою очередь в объединяющие модули. «Ох ты оказывается как легче стало обращаться к методам и свойствам классов, тут уже точно не ошибёшься!» — радуется он.
  • Ну вообщем такими-то потугами наконец прога в 4к строк закончена.

А т.к. замечена тенденция развития оптимизации в этом плане, то любой пытливый ум поинтересуется — «а что в этом является абсолютом развития?».

И тут ответы приводят к singleton’ам.

Но как я заметил про реализацию singleton’ов не многие знают.

Самый распространённый пример — в основном останавливаются на классах типа TSomeManager, объявляют в этом же модуле глобал экземпляр этого класса и создают его в initialization ну и уничтожают соответственно в finalization.

Рассмотрим достоинства singleton‘ов перед глобальными переменными:

  • Конфликты имён сводятся к минимуму;
  • Не отвлекаемся на инициализацию класса. Singleton — он всегда один и зачастую инициализация проходит автоматически;
  • Нет проблем с многопоточностью. (конечно не до такой степени, чтобы пренебрегать критическими секциями);
  • При изменениях логики программы у singleton’а придётся подправлять только интерфейс, а при глобальных переменных нужно отслеживать весь код по всей программе, который с ними связан.

Теперь рассмотрим и сравним варианты реализации паттернов синглтонов:

1. Нашёл вот такую статью. В которой предлагается применять шаблон внедряя его методы прямо в класс, который необходимо сделать одиночкой. Чесно гря это самый «негибкий» пример.

2. В википедии можно найти такой код:

unit SingletonUnit;

interface

type
  TSingleton = class
  private
    constructor ActualCreate;
  public
    constructor Create;
    class function GetInstance: TSingleton;
  end;

implementation

var
  Singleton: TSingleton;

constructor TSingleton.ActualCreate;
begin
  inherited Create;
end;

constructor TSingleton.Create;
begin
  raise Exception.Create('Attempt to create an instance of TSingleton');
end;

class function TSingleton.GetInstance: TSingleton;
begin
  if Singleton = nil then
    Singleton := TSingleton.ActualCreate;
  Result := Singleton;
end;

end.

Ну наконец-то видна структура по которой становится ясна логика. Видно что такой класс нельзя создать обычным методом через конструктор, можно только получить его ссылку через GetInstance, в котором он проверяет создан ли уже объект Singleton или нет.

В принципе моя реализация, которой я пользовался последнее время, почти такая же, только была чуть более заточена под наследование: метод Create был объявлен как Virtual; что позволяло в наследниках переопределять его, реализуя в нём операции какие были нужны при создании (например загрузку чего-либо).

3. Но совсем недавно я узрел самую удачную, на мой взгляд, реализацию паттерна синглтона — это реализация от Ins’а.

unit uSingletonTemplate;

interface

type
  TSingleton = class(TObject)
  private
    class procedure RegisterInstance(aInstance: TSingleton);
    procedure UnRegisterInstance;
    class function FindInstance: TSingleton;
  protected
    constructor Create; virtual;
  public
    class function NewInstance: TObject; override;
    procedure BeforeDestruction; override;
    constructor GetInstance; // Точка доступа к экземпляру
  end;

implementation

uses
  Contnrs;

var
  SingletonList : TObjectList;

{ TSingleton }

class procedure TSingleton.RegisterInstance(aInstance: TSingleton);
begin
   SingletonList.Add(aInstance);
end;

procedure TSingleton.UnRegisterInstance;
begin
   SingletonList.Extract(Self);
end;

class function TSingleton.FindInstance: TSingleton;
var
  i: integer;
begin
  Result := nil;
  for i := 0 to SingletonList.Count - 1 do
    if SingletonList[i].ClassType = Self then
  begin
    Result := TSingleton(SingletonList[i]);
    Break;
  end;
end;

constructor TSingleton.Create;
begin
  inherited Create;
end;

class function TSingleton.NewInstance: TObject;
begin
  Result := FindInstance;
  if Result = nil then
  begin
    Result := inherited NewInstance;
    TSingleton(Result).Create;
    RegisterInstance(TSingleton(Result));
  end;
end;

procedure TSingleton.BeforeDestruction;
begin
   UnregisterInstance;
   inherited BeforeDestruction;
end;

constructor TSingleton.GetInstance;
begin
   inherited Create;
end;

initialization
   SingletonList := TObjectList.Create(True);

finalization
   SingletonList.Free;

end.

 

Эта реализация принципиально отличается от первых 2-х тем, что это подход наследования класса от шаблонного. Это безусловно посложнее чем первые 2, но и намного прозрачнее(гибче).

Первые же 2 реализации ближе к подходу реализации на классовых методах. Это самый примитивный случай — тут уникальность гарантируется компилятором. Понятно, что у классовых методов есть очевидные ограничения.

Данной реализацией я сейчас и пользуюсь. Как можно заметить тут уже получается не совсем одиночка, т.к. можно создать сколько необходимо экземпляров наследников (если такое потребуется конечно), но если обращаться к нему через GetInstance он остаётся одиночкой по всем правилам, при этом в листе объектов будет находиться всегда 1 объект.

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

Скачать исходники с примером реализации: sharedfiles_singleton_template_demo.