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