Расскажу сегодня о 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.