Одиночка (шаблон проектирования)
Из Википедии, бесплатной энциклопедии
Одиночка | |
---|---|
Singleton | |
Тип | порождающий |
Плюсы | организует API; неявно загружает нужные модули в нужном порядке; оставляет место для второго похожего объекта |
Минусы | усложняет тестирование, многопоточность и отслеживание задержек; одиночки не должны неявно зависеть друг от друга |
Описан в Design Patterns | Да |
Одиночка (англ. Singleton) — порождающий шаблон проектирования, гарантирующий, что в однопоточном приложении будет единственный экземпляр некоторого класса, и предоставляющий глобальную точку доступа к этому экземпляру.
Цель[править | править код]
У класса есть только один экземпляр, и он предоставляет к нему глобальную точку доступа. При попытке создания данного объекта он создаётся только в том случае, если ещё не существует, в противном случае возвращается ссылка на уже существующий экземпляр и нового выделения памяти не происходит. Существенно то, что можно пользоваться именно экземпляром класса, так как при этом во многих случаях становится доступной более широкая функциональность. Например, к описанным компонентам класса можно обращаться через интерфейс, если такая возможность поддерживается языком.
Глобальный «одинокий» объект — именно объект (log().put("Test");
), а не набор процедур, не привязанных ни к какому объекту (logPut("Test");
) — бывает нужен, если:
- Используется существующая объектно-ориентированная библиотека и ей нужен объект, унаследованный от определённого класса/интерфейса.
- Есть шансы, что один объект когда-нибудь превратится в несколько.
- Дополнительные факторы, исполнимые в обеих концепциях, но хорошо сочетающиеся с методикой ООП:
- Интерфейс объекта (например, игрового мира) слишком сложен, и объект/префикс
log
служит для организации API. - В зависимости от каких-нибудь условий и настроек, создаётся один из нескольких объектов. Например, в зависимости от того, ведётся лог или нет, создаётся настоящий объект, пишущий в файл, или «заглушка», ничего не делающая.
- Создание объекта занимает время, и для красоты объект можно создавать, когда на экране уже что-то видно.
- Интерфейс объекта (например, игрового мира) слишком сложен, и объект/префикс
Такие объекты можно создавать и при инициализации программы. Это может приводить к следующим трудностям:
- Если объект нужен уже при инициализации, он может быть затребован раньше, чем будет создан.
- Бывает, что объект нужен не всегда. В таком случае его создание можно пропустить. Особенно это важно, если одиночек (например, диалоговых окон) много — тогда пользователь быстро получит интерфейс, а окна будут создаваться по одному, не мешая работе пользователя.
Одиночка может принадлежать и не глобальному пространству имён, а какому-то объекту — например, главной форме Qt.
Плюсы[править | править код]
- Наведение порядка в глобальном пространстве имён.
- Ускорение начального запуска программы, если есть множество одиночек, которые не нужны для запуска. Особенно удачно выходит, если создание всех «одиночек» даёт ощутимую задержку, а создание каждого отдельного — практически незаметно.
- Упрощение кода инициализации — система автоматически неявно отделит нужные компоненты от ненужных и проведёт топологическую сортировку.
- Одиночку можно в дальнейшем превратить в шаблон-стратегию или несколько таких объектов.
- Пример шаблона-стратегии: запись журнала действий в файл или в никуда.
- Пример нескольких объектов: размножив классы
Player
иRenderer
, можно сделать игру вдвоём на одной машине.
Минусы[править | править код]
- Усложняется контроль за межпоточными гонками и задержками.
- Многопоточного «одиночку» сложно писать «из головы»[1]: доступ к давно построенному одиночке в идеале не должен открывать мьютекс. Лучше проверенные решения. Как пример см. преамбулу к статье Модель памяти Java.
- Конфликт двух потоков за недостроенного одиночку приведёт к задержке.
- Если объект создаётся долго, задержка может мешать пользователю или нарушать реальное время. В таком случае его создание лучше перенести в старт программы.
- Если программа стартует долго, сложнее становится сделать строку прогресса.
- Требуются особые функции для модульного тестирования, чтобы физически изолировать тесты[2] — например, сделать менеджер одиночек не единственным[2]. Впрочем, одиночками часто являются модули общения с аппаратурой, объектами ОС, программным окружением и пользователем[1], которые модульному тестированию поддаются плохо.
- Требуется особая тактика тестирования готовой программы, ведь пропадает даже понятие «простейшая запускаемость» — запускаемость зависит от конфигурации.
- Маленький объект без данных — чистый шаблон-стратегию или null object — обычно держат в сегменте данных, а не в динамической памяти, и превращают в одиночку в особых случаях.
- Сами по себе одиночки никак не заведуют порядком выгрузки. Возможна даже ситуация «сборщик мусора уничтожил одиночку, клиент создал нового»[1]. Если этот порядок важен, можно перевести систему на умные указатели или при выходе явно прекратить всю подозрительную деятельность в ключевых компонентах.
- Компоненты не должны иметь неявных связей между собой, иначе небольшое изменение — в программном коде, файле настроек, сценарии пользования — может спутать порядок и вызвать трудноуловимую ошибку. Пример: одиночка А использует COM, но полагается на
CoInitialize
, вызванный одиночкой Б, и без него работать не может. Решение: сделать одиночку CoInit, который явно используется и А, и Б.- Одиночки требуют особого внимания, если один из компонентов заведомо ненадёжен и для адекватной работы требует особых условий («разглючек»): библиотека, которая иногда портит память; сеть, которая разрывает соединение, если слишком долго ждать; типографский движок, способный загрузить шрифты в одном порядке и не способный наоборот… Тогда, в зависимости от порядка инициализации, компонент может сработать адекватно или нет.
Применение[править | править код]
- должен быть ровно один экземпляр некоторого класса, легко доступный всем клиентам;
- единственный экземпляр должен расширяться путём порождения подклассов, и клиентам нужно иметь возможность работать с расширенным экземпляром без модификации своего кода.
Примеры использования[править | править код]
- Ведение отладочного файла для приложения.
- В любом приложении для iOS существует класс AppDelegate, реагирующий на системные события.
Примеры реализации[править | править код]
Java 1.6[править | править код]
public class Singleton { private static Singleton instance; private Singleton () {}; public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
Java[править | править код]
Этот вариант блокирует метод getInstance() только на момент создания экземпляра. Дальнейшие обращения к методу не будут его блокировать за счет двойной проверки. Volatile не позволяет конкурентным потокам видеть промежуточное значение instance, что позволяет избежать ошибок при возвращении результата.
public class Singleton { private volatile Singleton instance; private Singleton() {} public Singleton getInstance() { if (instance == null) { synchronized (this) { if (instance == null) { return new Singleton(); } } } return instance; } }
Java[править | править код]
public class Singleton { private static Singleton instance; static { instance = new Singleton(); // В этом блоке возможна обработка исключений } private Singleton () {} public static Singleton getInstance() { return instance; } }
Java 1.5[править | править код]
public class Singleton { private Singleton() {} private static class SingletonHolder { public static final Singleton instance = new Singleton(); } public static Singleton getInstance() { return SingletonHolder.instance; } }
Java 1.5[править | править код]
public enum SingletonEnum { INSTANCE; public void someMethod() { *** } public void anotherMethod() { *** } }
Python[править | править код]
Из PEP 0318 Архивная копия от 3 июня 2020 на Wayback Machine:
def singleton(cls): instances = {} def getinstance(): if cls not in instances: instances[cls] = cls() return instances[cls] return getinstance @singleton class MyClass: ...
Python[править | править код]
Из PEP 0318 Архивная копия от 3 июня 2020 на Wayback Machine[нет в источнике]:
class MetaSingleton(type): _instances = {} def __call__(cls, *args, **kwargs): if cls not in cls._instances: cls._instances[cls] = super(MetaSingleton, cls).__call__(*args, **kwargs) return cls._instances[cls] class MyClass(metaclass=MetaSingleton): ...
C++[править | править код]
Ниже приведена одна из возможных реализаций паттерна Одиночка на C++ (известная как синглтон Майерса), где одиночка представляет собой статический локальный объект. Важным моментом является то, что конструктор класса объявлен как private
, что позволяет предотвратить создание экземпляров класса за пределами его реализации. Помимо этого, закрытыми также объявлены конструктор копирования и оператор присваивания. Последние следует объявлять, но не определять, так как это позволяет в случае их случайного вызова из кода получить легко обнаруживаемую ошибку компоновки. Отметим также, что приведенный пример не является потокобезопасным в C++03, для работы с классом из нескольких потоков нужно защитить переменную theSingleInstance
от одновременного доступа, например, с помощью мьютекса или критической секции. Впрочем, в C++11 синглтон Майерса является потокобезопасным и без всяких блокировок.
class OnlyOne { public: static OnlyOne& Instance() { static OnlyOne theSingleInstance; return theSingleInstance; } private: OnlyOne(){} OnlyOne(const OnlyOne& root) = delete; OnlyOne& operator=(const OnlyOne&) = delete; };
Ещё один пример реализации одиночки на C++ с возможностью наследования для создания интерфейса, каркасом которого послужит, собственно, одиночка. Временем «жизни» единственного объекта удобно управлять, используя механизм подсчета ссылок.
class Singleton { protected: static Singleton* _self; Singleton() {} virtual ~Singleton() {} public: static Singleton* Instance() { if(!_self) { _self = new Singleton(); } return _self; } static bool DeleteInstance() { if(_self) { delete _self; _self = 0; return true; } return false; } }; Singleton* Singleton ::_self = 0;
C#[править | править код]
Максимально простой способ реализации потокобезопасного и ленивого синглтона, требующего, однако, .NET версии 4 и более.
public sealed class Singleton { private static readonly Lazy<Singleton> instanceHolder = new Lazy<Singleton>(() => new Singleton()); private Singleton() { ... } public static Singleton Instance { get { return instanceHolder.Value; } } }
Для отложенной инициализации Singleton'а в C# рекомендуется использовать конструкторы типов (статический конструктор). CLR автоматически вызывает конструктор типа при первом обращении к типу, при этом обеспечивая безопасность в отношении синхронизации потоков. Конструктор типа автоматически генерируется компилятором и в нем происходит инициализация всех полей типа (статических полей). Явно задавать конструктор типа не следует, так как в этом случае он будет вызываться непосредственно перед обращением к типу и JIT-компилятор не сможет применить оптимизацию (например, если первое обращение к Singleton'у происходит в цикле).
/// generic Singleton<T> (потокобезопасный с использованием generic-класса и с отложенной инициализацией) /// <typeparam name="T">Singleton class</typeparam> public class Singleton<T> where T : class { /// Защищённый конструктор необходим для того, чтобы предотвратить создание экземпляра класса Singleton. /// Он будет вызван из закрытого конструктора наследственного класса. protected Singleton() { } /// Фабрика используется для отложенной инициализации экземпляра класса private sealed class SingletonCreator<S> where S : class { //Используется Reflection для создания экземпляра класса без публичного конструктора private static readonly S instance = (S) typeof(S).GetConstructor( BindingFlags.Instance | BindingFlags.NonPublic, null, new Type[0], new ParameterModifier[0]).Invoke(null); public static S CreatorInstance { get { return instance; } } } public static T Instance { get { return SingletonCreator<T>.CreatorInstance; } } } /// Использование Singleton public class TestClass : Singleton<TestClass> { /// Вызовет защищённый конструктор класса Singleton private TestClass() { } public string TestProc() { return "Hello World"; } }
Также можно использовать стандартный вариант потокобезопасной реализации Singleton с отложенной инициализацией:
public class Singleton { /// защищённый конструктор нужен, чтобы предотвратить создание экземпляра класса Singleton protected Singleton() { } private sealed class SingletonCreator { private static readonly Singleton instance = new Singleton(); public static Singleton Instance { get { return instance; } } } public static Singleton Instance { get { return SingletonCreator.Instance; } } }
Если нет необходимости в каких-либо публичных статических методах или свойствах (кроме свойства Instance), то можно использовать упрощенный вариант:
public class Singleton { private static readonly Singleton instance = new Singleton(); public static Singleton Instance { get { return instance; } } /// защищённый конструктор нужен, чтобы предотвратить создание экземпляра класса Singleton protected Singleton() { } }
Пример с ленивой инициализацией
namespace Singleton{ public class Singleton { private static Singleton instance; public static Singleton Instance { get { instance ??= new Singleton(); return instance; } } protected Singleton() { } } }
PHP 4[править | править код]
<?php class Singleton { function Singleton( $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(); ?>
PHP 5[править | править код]
<?php class Singleton { private static $instance; // экземпляр объекта private function __construct(){ /* ... @return Singleton */ } // Защищаем от создания через new Singleton private function __clone() { /* ... @return Singleton */ } // Защищаем от создания через клонирование private function __wakeup() { /* ... @return Singleton */ } // Защищаем от создания через unserialize public static function getInstance() { // Возвращает единственный экземпляр класса. @return Singleton if ( empty(self::$instance) ) { self::$instance = new self(); } return self::$instance; } public function doAction() { } } /* Применение */ Singleton::getInstance()->doAction(); // ?>
PHP 5.4[править | править код]
<?php trait Singleton { private static $instance = null; private function __construct() { /* ... @return Singleton */ } // Защищаем от создания через new Singleton private function __clone() { /* ... @return Singleton */ } // Защищаем от создания через клонирование private function __wakeup() { /* ... @return Singleton */ } // Защищаем от создания через unserialize public static function getInstance() { return self::$instance===null ? self::$instance = new static() // Если $instance равен 'null', то создаем объект new self() : self::$instance; // Иначе возвращаем существующий объект } } /** * Class Foo * @method static Foo getInstance() */ class Foo { use Singleton; private $bar = 0; public function incBar() { $this->bar++; } public function getBar() { return $this->bar; } } /* Применение */ $foo = Foo::getInstance(); $foo->incBar(); var_dump($foo->getBar()); $foo = Foo::getInstance(); $foo->incBar(); var_dump($foo->getBar()); ?>
Delphi[править | править код]
Для Delphi 2005 и выше подходит следующий пример (не потоко-безопасный):
type TSingleton = class strict private class var Instance: TSingleton; public class function NewInstance: TObject; override; end; class function TSingleton.NewInstance: TObject; begin if not Assigned(Instance) then Instance := TSingleton(inherited NewInstance); Result := Instance; end;
Для более ранних версий следует переместить код класса в отдельный модуль, а объявление Instance
заменить объявлением глобальной переменной в его секции implementation
(до Delphi 7 включительно секции class var
и strict private
отсутствовали).
Dart[править | править код]
На основе фабричного конструктора из документации Dart
class Singleton { static final Singleton _singleton = Singleton._internal(); factory Singleton() { return _singleton; } Singleton._internal(); }
Io[править | править код]
Singleton := Object clone Singleton clone := Singleton
Ruby[править | править код]
class Singleton def self.new @instance ||= super end end
В стандартную библиотеку (Ruby 1.8 и выше) входит модуль Singleton, что позволяет создавать синглтоны ещё проще:
require 'singleton' class Foo include Singleton end a = Foo.instance # Foo.new недоступен, для получения ссылки на (единственный) # экземпляр класса Foo следует использовать метод Foo#instance
Common Lisp[править | править код]
(defclass singleton-class () ;;метакласс, реализующий механизм синглтона ((instance :initform nil))) (defmethod validate-superclass ((class singleton-class) (superclass standard-class)) t) ;;Разрешаем наследование классов-синглтонов от обычных классов (defmethod validate-superclass ((class singleton-class) (superclass singleton-class)) t) ;;Разрешаем наследование классов-синглтонов от других классов-синглтонов (defmethod validate-superclass ((class standard-class) (superclass singleton-class)) nil) ;;Запрещаем наследование обычных классов от синглтонов (defmethod make-instance ((class singleton-class) &key) (with-slots (instance) class (or instance (setf instance (call-next-method))))) (defclass my-singleton-class () () (:metaclass singleton-class))
VB.NET[править | править код]
Module Program Sub Main() Dim T1 As Singleton = Singleton.getInstance T1.Value = 1000 Dim T2 As Singleton = Singleton.getInstance Console.WriteLine(T2.Value) Console.Read() End Sub End Module Public Class Singleton Public Value As Integer 'Не разрешаем конструктор Protected Sub New() End Sub Private NotInheritable Class SingletonCreator Private Shared ReadOnly m_instance As New Singleton() Public Shared ReadOnly Property Instance() As Singleton Get Return m_instance End Get End Property End Class Public Shared ReadOnly Property getInstance() As Singleton Get Return SingletonCreator.Instance End Get End Property End Class
Perl[править | править код]
use v5.10; use strict; package Singleton; sub new { # Объявление статической переменной $instance # и возврат её как результат выполнения метода new state $instance = bless {}; } package main; my $a = Singleton->new; my $b = Singleton->new; say "$a $b"; # Ссылки $a и $b указывают на один объект
Perl[править | править код]
#!/usr/bin/perl -w use feature "say"; use strict; use warnings; package Singleton { my $instance; # экземпляр класса (статическое поле) # -- ** конструктор ** -- sub new { my $class = shift; unless($instance) { # проверяем нет ли уже созданного экземпляра класса $instance = { # если нет, создаем новый и записываем в него имя того, с кем надо поздороваться name => shift, }; bless $instance, $class; } return $instance; # возвращаем единственный и неповторимый экземпляр нашего класса } # -- ** приветствие ** -- sub hello { my($self) = (shift); say "Hello, $self->{name}"; # давайте поприветствуем хозяина этого объекта } } my $a = Singleton->new('Alex'); # создаем экземпляр класса с именем Alex my $b = Singleton->new('Barney'); # ... а теперь пытаемся создать ещё один экземпляр для Barney $a->hello(); # Hello, Alex # да, здравствуй, Алекс $b->hello(); # Hello, Alex # ой, Барни, извини, какое недоразумение...
ActionScript 3[править | править код]
Вариант с приватным классом:
package { public class Singleton { private static var _instance:Singleton; public function Singleton(privateClass:PrivateClass) { } public static function getInstance():Singleton { if (!_instance) _instance = new Singleton(new PrivateClass()); return _instance; } } } // Из-за того, что класс объявлен в том же файле за пределами // пакета, использовать его сможет только класс Singleton. class PrivateClass { public function PrivateClass() { } }
Вариант с вызовом исключения:
package { public class Singleton { public static const instance:Singleton = new Singleton(); public function Singleton() { // Boolean(Singleton) равно false, в случае если экземпляр класса // будет создан до выполнения статического конструктора if (Singleton) throw new Error("Class is singleton."); } } }
Вариант с переменной доступа:
package { public class MySingleton { private static var _instance:MySingleton; //Переменная доступа private static var _isConstructing:Boolean; public function MySingleton() { if (!_isConstructing) throw new Error("Singleton, use MySingleton.instance"); } public static function get instance():MySingleton { if (_instance == null) { _isConstructing = true; _instance = new MySingleton(); _isConstructing = false; } return _instance; } } }
Преимущества варианта с приватным классом:
- При попытке использовать конструктор напрямую, ошибка будет выявлена компилятором сразу же. // Не является преимуществом только этого метода
- Создание объекта происходит по запросу.
Недостаток варианта с приватным классом:
- Можно подменить приватный класс своим собственным с таким же названием.
Преимущества варианта с использованием исключения:
- Меньше кода.
CoffeeScript[править | править код]
Классический подход (Coffeescript ≠ 1.5)
class Singleton instance = undefined constructor : -> if instance? return instance else instance = @ # Код конструктора console.assert( new Singleton is new Singleton );
Подход, основанный на возможности доступа к функции из её тела (Coffeescript ≠ 1.5)
class Singleton init = -> # конструктор как приватный метод класса # Код конструктора # ... # Заменяем конструктор, сохраняя this (@) init = => @ return @ # Реальный конструктор. Служит для вызова init # return использовать обязательно, иначе вернёт this (@) constructor : -> return init.apply(@, arguments) console.assert( new Singleton is new Singleton )
constructor : -> Singleton = => @
Однако, если использовать пространства имён, то возможен такой вариант:
ns = {} class ns.Singleton constructor : -> # Код конструктора ns.Singleton = => @ console.assert( new ns.Singleton is new ns.Singleton )
JavaScript[править | править код]
Метод, основанный на сокрытии переменных с помощью замыканий. В качестве бонуса - возможность объявлять приватные методы и свойства, которые будут доступны и конструктору и методам "класса".
const Singleton = (function() { let instance; // Приватные методы и свойства // Конструктор function Singleton() { if (instance) return instance; instance = this; } // Публичные методы Singleton.prototype.test = function() {}; return Singleton; })(); console.log(new Singleton() === new Singleton());
Без использования сокрытия переменных есть простое решение, основанное на том, что функция Singleton является объектом. Минусом является возможность изменения свойства instance вне класса:
function Singleton() { const instance = Singleton.instance; if (instance) return instance; Singleton.instance = this; } Singleton.prototype.test = function() {}; console.log(new Singleton() === new Singleton());
Наиболее короткий вариант.
const Singleton = new (function() { const instance = this; return function() { return instance; }; })(); console.log(new Singleton() === new Singleton());
С использованием статических приватных полей JS-класса:
class Singleton{ static #onlyInstance = null; constructor(){ if(!Singleton.#onlyInstance){ Singleton.#onlyInstance = this; } else { return Singleton.#onlyInstance; } } } console.log(new Singleton() === new Singleton());
Objective-C[править | править код]
Singleton.h
@interface Singleton : NSObject { } + (Singleton *)sharedInstance; @end
Singleton.m
@implementation Singleton static Singleton *_sharedInstance = nil; + (Singleton *)sharedInstance { @synchronized(self) { if (!_sharedInstance) { _sharedInstance = [[Singleton alloc] init]; } } return _sharedInstance; } @end
Или (только для OS X 10.6+, iOS 4.0+):
@implementation Singleton + (Singleton *)sharedInstance { static dispatch_once_t pred; static Singleton *sharedInstance = nil; dispatch_once(&pred, ^{ sharedInstance = [[self alloc] init]; }); return sharedInstance; } @end
Swift[править | править код]
class Singleton { static let shared = Singleton() private init() { } }
Scala, Kotlin[править | править код]
object Singleton {} // ключевое слово "object" создает класс, который реализует паттерн "одиночка" по умолчанию
См. также[править | править код]
Литература[править | править код]
- Алан Шаллоуей, Джеймс Р. Тротт Шаблоны проектирования. Новый подход к объектно-ориентированному анализу и проектированию = 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.
Ссылки[править | править код]
- Паттерн Singleton (Одиночка) Архивная копия от 17 февраля 2006 на Wayback Machine — пример использования шаблона (C++).
- Одиночка Архивная копия от 4 ноября 2005 на Wayback Machine — простое описание с примером применения.
- Реализация Singleton на Java — описание классической реализации и многопоточные модификации.
- [1] Архивировано 1 марта 2012 года. — The «Double-Checked Locking is Broken» Declaration in java
- Реализация синглтонов на Perl Архивная копия от 7 октября 2010 на Wayback Machine — пример для Perl.
- Singleton Considered Stupid Архивная копия от 17 декабря 2009 на Wayback Machine — критика паттерна Singleton
- Мультисинглтон Архивная копия от 17 февраля 2011 на Wayback Machine — фабрика синглтонов.
- Паттерн Singleton (Одиночка) Архивная копия от 2 февраля 2012 на Wayback Machine — три варианта реализации на C++.
- Классы-одиночки (недоступная ссылка) — реализация для Delphi 6/7.
- Singleton Java, Python, PHP