Одиночка (англ.Singleton) — порождающий шаблон проектирования, гарантирующий, что в однопоточном приложении будет единственный экземпляр некоторого класса, и предоставляющий глобальную точку доступа к этому экземпляру.
Цель
У класса есть только один экземпляр, и он предоставляет к нему глобальную точку доступа. При попытке создания данного объекта он создаётся только в том случае, если ещё не существует, в противном случае возвращается ссылка на уже существующий экземпляр и нового выделения памяти не происходит. Существенно то, что можно пользоваться именно экземпляром класса, так как при этом во многих случаях становится доступной более широкая функциональность. Например, к описанным компонентам класса можно обращаться через интерфейс, если такая возможность поддерживается языком.
Глобальный «одинокий» объект — именно объект (log().put("Test");), а не набор процедур, не привязанных ни к какому объекту (logPut("Test");) — бывает нужен, если:
Используется существующая объектно-ориентированная библиотека и ей нужен объект, унаследованный от определённого класса/интерфейса.
Есть шансы, что один объект когда-нибудь превратится в несколько.
Дополнительные факторы, исполнимые в обеих концепциях, но хорошо сочетающиеся с методикой ООП:
Интерфейс объекта (например, игрового мира) слишком сложен, и объект/префикс log служит для организации API.
Создание объекта занимает время, и для красоты объект можно создавать, когда на экране уже что-то видно.
Такие объекты можно создавать и при инициализации программы. Это может приводить к следующим трудностям:
Если объект нужен уже при инициализации, он может быть затребован раньше, чем будет создан.
Бывает, что объект нужен не всегда. В таком случае его создание можно пропустить. Особенно это важно, если одиночек (например, диалоговых окон) много — тогда пользователь быстро получит интерфейс, а окна будут создаваться по одному, не мешая работе пользователя.
Плюсы
Наведение порядка в глобальном пространстве имён.
Ускорение начального запуска программы, если есть множество одиночек, которые не нужны для запуска. Особенно удачно выходит, если создание всех «одиночек» даёт ощутимую задержку, а создание каждого отдельного — практически незаметно.
Упрощение кода инициализации — система автоматически неявно отделит нужные компоненты от ненужных и проведёт топологическую сортировку.
Одиночку можно в дальнейшем превратить в шаблон-стратегию или несколько таких объектов.
Пример шаблона-стратегии: запись журнала действий в файл или в никуда.
Пример нескольких объектов: размножив классы Player и Renderer, можно сделать игру вдвоём на одной машине.
Многопоточного «одиночку» сложно писать «из головы»: доступ к давно построенному одиночке в идеале не должен открывать мьютекс. Лучше проверенные решения. Как пример см. преамбулу к статье Модель памяти Java.
Конфликт двух потоков за недостроенного одиночку приведёт к задержке.
Если объект создаётся долго, задержка может мешать пользователю или нарушать реальное время. В таком случае его создание лучше перенести в инициализацию программы.
Если программа стартует долго, сложнее становится сделать строку прогресса.
Требуются особые функции для модульного тестирования — например, чтобы перевести библиотеку в «не-одинокий» режим и полностью изолировать тесты друг от друга. Впрочем, одиночками часто являются модули общения с аппаратурой, объектами ОС, программным окружением и пользователем, которые модульному тестированию поддаются плохо.
Требуется особая тактика тестирования готовой программы, ведь пропадает даже понятие «простейшая запускаемость» — запускаемость зависит от конфигурации.
Сами по себе одиночки никак не заведуют порядком выгрузки — если он важен, можно перевести систему на умные указатели или при выходе явно прекратить всю подозрительную деятельность в ключевых компонентах.
Компоненты не должны иметь неявных связей между собой, иначе небольшое изменение — в программном коде, файле настроек, сценарии пользования — может спутать порядок и вызвать трудноуловимую ошибку. Пример: одиночка А использует COM, но полагается на CoInitialize, вызванный одиночкой Б, и без него работать не может. Решение: сделать одиночку CoInit, который явно используется и А, и Б.
Одиночки требуют особого внимания, если один из компонентов заведомо ненадёжен и для адекватной работы требует особых условий («разглючек»): библиотека, которая иногда портит память; сеть, которая разрывает соединение, если слишком долго ждать; типографский движок, способный загрузить шрифты в одном порядке и не способный наоборот… Тогда, в зависимости от порядка инициализации, компонент может сработать адекватно или нет.
Применение
должен быть ровно один экземпляр некоторого класса, легко доступный всем клиентам;
единственный экземпляр должен расширяться путём порождения подклассов, и клиентам нужно иметь возможность работать с расширенным экземпляром без модификации своего кода.
Примеры использования
Ведение отладочного файла для приложения.
В любом приложении для iOS существует класс AppDelegate, реагирующий на системные события.
Примеры реализации
Java 1.6
Пример на языке Java 1.6: без внутренних классов (ленивая несинхронизированная реализация)
Этот вариант блокирует метод getInstance() вне зависимости от того, создали ли мы единственный экземпляр или нет. Это замедляет работу программы, если требуется часто получать объект Singleton из разных потоков.
Пример на языке Java: без ленивой инициализации, с использованием статического инициализатора
publicclassSingleton{privatestaticSingletoninstance;static{instance=newSingleton();// В этом блоке возможна обработка исключений}privateSingleton(){}publicstaticSingletongetInstance(){returninstance;}}
Java 1.5
Пример на языке Java 1.5: Initialization on Demand Holder
Ниже приведена одна из возможных реализаций паттерна Одиночка на C++ (известная как синглтон Майерса), где одиночка представляет собой статический локальный объект. Важным моментом является то, что конструктор класса объявлен как private, что позволяет предотвратить создание экземпляров класса за пределами его реализации. Помимо этого, закрытыми также объявлены конструктор копирования и оператор присваивания. Последние следует объявлять, но не определять, так как это позволяет в случае их случайного вызова из кода получить легко обнаруживаемую ошибку компоновки. Отметим также, что приведенный пример не является потокобезопасным в C++03, для работы с классом из нескольких потоков нужно защитить переменную theSingleInstance от одновременного доступа, например, с помощью мьютекса или критической секции. Впрочем, в C++11 синглтон Майерса является потокобезопасным и без всяких блокировок.
Ещё один пример реализации одиночки на C++ с возможностью наследования для создания интерфейса, каркасом которого послужит, собственно, одиночка. Временем «жизни» единственного объекта удобно управлять, используя механизм подсчета ссылок.
Для отложенной инициализации Singleton'а в C# рекомендуется использовать конструкторы типов (статический конструктор). CLR автоматически вызывает конструктор типа при первом обращении к типу, при этом обеспечивая безопасность в отношении синхронизации потоков. Конструктор типа автоматически генерируется компилятором и в нем происходит инициализация всех полей типа (статических полей). Явно задавать конструктор типа не следует, так как в этом случае он будет вызываться непосредственно перед обращением к типу и JIT-компилятор не сможет применить оптимизацию (например, если первое обращение к Singleton'у происходит в цикле).
/// generic Singleton<T> (потокобезопасный с использованием generic-класса и с отложенной инициализацией)/// <typeparam name="T">Singleton class</typeparam>publicclassSingleton<T>whereT:class{/// Защищённый конструктор необходим для того, чтобы предотвратить создание экземпляра класса Singleton. /// Он будет вызван из закрытого конструктора наследственного класса.protectedSingleton(){}/// Фабрика используется для отложенной инициализации экземпляра классаprivatesealedclassSingletonCreator<S>whereS:class{//Используется Reflection для создания экземпляра класса без публичного конструктораprivatestaticreadonlySinstance=(S)typeof(S).GetConstructor(BindingFlags.Instance|BindingFlags.NonPublic,null,newType[0],newParameterModifier[0]).Invoke(null);publicstaticSCreatorInstance{get{returninstance;}}}publicstaticTInstance{get{returnSingletonCreator<T>.CreatorInstance;}}}/// Использование SingletonpublicclassTestClass:Singleton<TestClass>{/// Вызовет защищённый конструктор класса SingletonprivateTestClass(){}publicstringTestProc(){return"Hello World";}}
Также можно использовать стандартный вариант потокобезопасной реализации Singleton с отложенной инициализацией:
publicclassSingleton{/// защищённый конструктор нужен, чтобы предотвратить создание экземпляра класса SingletonprotectedSingleton(){}privatesealedclassSingletonCreator{privatestaticreadonlySingletoninstance=newSingleton();publicstaticSingletonInstance{get{returninstance;}}}publicstaticSingletonInstance{get{returnSingletonCreator.Instance;}}}
Если нет необходимости в каких-либо публичных статических методах или свойствах (кроме свойства Instance), то можно использовать упрощенный вариант:
publicclassSingleton{privatestaticreadonlySingletoninstance=newSingleton();publicstaticSingletonInstance{get{returninstance;}}/// защищённый конструктор нужен, чтобы предотвратить создание экземпляра класса SingletonprotectedSingleton(){}}
<?phpclassSingleton{functionSingleton($directCall=true){if($directCall){trigger_error("Нельзя использовать конструктор для создания класса Singleton. Используйте статический метод getInstance()",E_USER_ERROR);}//TODO: Добавьте основной код конструктора здесь}function&getInstance(){static$instance;if(!is_object($instance)){$class=__CLASS__;$instance=new$class(false);}return$instance;}}//usage$test=&Singleton::getInstance();?>
<?phpclassSingleton{privatestatic$instance;// экземпляр объектаprivatefunction__construct(){/* ... @return Singleton */}// Защищаем от создания через new Singletonprivatefunction__clone(){/* ... @return Singleton */}// Защищаем от создания через клонированиеprivatefunction__wakeup(){/* ... @return Singleton */}// Защищаем от создания через unserializepublicstaticfunctiongetInstance(){// Возвращает единственный экземпляр класса. @return Singletonif(empty(self::$instance)){self::$instance=newself();}returnself::$instance;}publicfunctiondoAction(){}}/*Применение*/Singleton::getInstance()->doAction();//?>
PHP 5.4
Пример на языке PHP5.4
<?phptraitSingleton{privatestatic$instance=null;privatefunction__construct(){/* ... @return Singleton */}// Защищаем от создания через new Singletonprivatefunction__clone(){/* ... @return Singleton */}// Защищаем от создания через клонированиеprivatefunction__wakeup(){/* ... @return Singleton */}// Защищаем от создания через unserializepublicstaticfunctiongetInstance(){returnself::$instance===null?self::$instance=newstatic()// Если $instance равен 'null', то создаем объект new self():self::$instance;// Иначе возвращаем существующий объект }}/** * Class Foo * @method static Foo getInstance() */classFoo{useSingleton;private$bar=0;publicfunctionincBar(){$this->bar++;}publicfunctiongetBar(){return$this->bar;}}/*Применение*/$foo=Foo::getInstance();$foo->incBar();var_dump($foo->getBar());$foo=Foo::getInstance();$foo->incBar();var_dump($foo->getBar());?>
Delphi
Для Delphi 2005 и выше подходит следующий пример (не потоко-безопасный):
Для более ранних версий следует переместить код класса в отдельный модуль, а объявление Instance заменить объявлением глобальной переменной в его секции implementation (до Delphi 7 включительно секции class var и strict private отсутствовали).
В стандартную библиотеку (Ruby 1.8 и выше) входит модуль Singleton, что позволяет создавать синглтоны ещё проще:
require'singleton'classFooincludeSingletonenda=Foo.instance# Foo.new недоступен, для получения ссылки на (единственный)# экземпляр класса Foo следует использовать метод Foo#instance
(defclasssingleton-class();;метакласс, реализующий механизм синглтона((instance:initformnil)))(defmethodvalidate-superclass((classsingleton-class)(superclassstandard-class))t);;Разрешаем наследование классов-синглтонов от обычных классов(defmethodvalidate-superclass((classsingleton-class)(superclasssingleton-class))t);;Разрешаем наследование классов-синглтонов от других классов-синглтонов(defmethodvalidate-superclass((classstandard-class)(superclasssingleton-class))nil);;Запрещаем наследование обычных классов от синглтонов(defmethodmake-instance((classsingleton-class)&key)(with-slots(instance)class(orinstance(setfinstance(call-next-method)))))(defclassmy-singleton-class()()(:metaclasssingleton-class))
usev5.10;usestrict;packageSingleton;subnew{# Объявление статической переменной $instance# и возврат её как результат выполнения метода newstate$instance=bless{};}packagemain;my$a=Singleton->new;my$b=Singleton->new;say"$a $b";# Ссылки $a и $b указывают на один объект
#!/usr/bin/perl -wusefeature"say";usestrict;usewarnings;packageSingleton{my$instance;# экземпляр класса (статическое поле)# -- ** конструктор ** --subnew{my$class=shift;unless($instance){# проверяем нет ли уже созданного экземпляра класса$instance={# если нет, создаем новый и записываем в него имя того, с кем надо поздороватьсяname=>shift,};bless$instance,$class;}return$instance;# возвращаем единственный и неповторимый экземпляр нашего класса}# -- ** приветствие ** --subhello{my($self)=(shift);say"Hello, $self->{name}";# давайте поприветствуем хозяина этого объекта}}my$a=Singleton->new('Alex');# создаем экземпляр класса с именем Alexmy$b=Singleton->new('Barney');# ... а теперь пытаемся создать ещё один экземпляр для Barney$a->hello();# Hello, Alex # да, здравствуй, Алекс$b->hello();# Hello, Alex # ой, Барни, извини, какое недоразумение...
package{publicclassSingleton{privatestaticvar_instance:Singleton;publicfunctionSingleton(privateClass:PrivateClass){}publicstaticfunctiongetInstance():Singleton{if(!_instance)_instance=newSingleton(newPrivateClass());return_instance;}}}// Из-за того, что класс объявлен в том же файле за пределами // пакета, использовать его сможет только класс Singleton.classPrivateClass{publicfunctionPrivateClass(){}}
Вариант с вызовом исключения:
package{publicclassSingleton{publicstaticconstinstance:Singleton=newSingleton();publicfunctionSingleton(){// Boolean(Singleton) равно false, в случае если экземпляр класса// будет создан до выполнения статического конструктораif(Singleton)thrownewError("Class is singleton.");}}}
Вариант с переменной доступа:
package{publicclassMySingleton{privatestaticvar_instance:MySingleton;//Переменная доступаprivatestaticvar_isConstructing:Boolean;publicfunctionMySingleton(){if(!_isConstructing)thrownewError("Singleton, use MySingleton.instance");}publicstaticfunctiongetinstance():MySingleton{if(_instance==null){_isConstructing=true;_instance=newMySingleton();_isConstructing=false;}return_instance;}}}
Преимущества варианта с приватным классом:
При попытке использовать конструктор напрямую, ошибка будет выявлена компилятором сразу же. // Не является преимуществом только этого метода
Создание объекта происходит по запросу.
Недостаток варианта с приватным классом:
Можно подменить приватный класс своим собственным с таким же названием.
Преимущества варианта с использованием исключения:
classSingletoninstance=undefinedconstructor:->ifinstance?returninstanceelseinstance=@# Код конструктораconsole.assert(newSingletonisnewSingleton);
Подход, основанный на возможности доступа к функции из её тела (Coffeescript ≠ 1.5)
classSingletoninit=-># конструктор как приватный метод класса# Код конструктора# ...# Заменяем конструктор, сохраняя this (@)init==>@return@# Реальный конструктор. Служит для вызова init# return использовать обязательно, иначе вернёт this (@)constructor:->returninit.apply(@,arguments)console.assert(newSingletonisnewSingleton)
Примечание: изменение настоящего конструктора из него самого, т.е
constructor:->Singleton==>@
ничего не даст, т.к. в результирующем JavaScript-коде constructor указывает на локальный конструктор Singleton, а не на класс Singleton.
Однако, если использовать пространства имён, то возможен такой вариант:
ns={}classns.Singletonconstructor:-># Код конструктораns.Singleton==>@console.assert(newns.Singletonisnewns.Singleton)
Метод, основанный на сокрытии переменных с помощью замыканий. В качестве бонуса - возможность объявлять приватные методы и свойства, которые будут доступны и конструктору и методам "класса".
constSingleton=(function(){letinstance;// Приватные методы и свойства// КонструкторfunctionSingleton(){if(instance)returninstance;instance=this;}// Публичные методыSingleton.prototype.test=function(){};returnSingleton;})();console.log(newSingleton()===newSingleton());
Без использования сокрытия переменных есть простое решение, основанное на том, что функция Singleton является объектом. Минусом является возможность изменения свойства instance вне класса:
Алан Шаллоуей, Джеймс Р. Тротт Шаблоны проектирования. Новый подход к объектно-ориентированному анализу и проектированию = Design Patterns Explained: A New Perspective on Object-Oriented Design. — М.: «Вильямс», 2002. — С. 288. — ISBN 0-201-71594-5.
Эрик Фримен, Элизабет Фримен. Паттерны проектирования = Head First Design Patterns. — СПб.: Питер, 2011. — 656 с. — ISBN 978-5-459-00435-9.