Одиночка (шаблон проектирования)
Одиночка (англ. Singleton) — порождающий шаблон проектирования, гарантирующий, что в однопоточном приложении будет единственный экземпляр некоторого класса, и предоставляющий глобальную точку доступа к этому экземпляру.
Одиночка | |
---|---|
Singleton | |
Тип | порождающий |
Плюсы | контролируемый доступ к единственному экземпляру |
Минусы |
|
Описан в Design Patterns | Да |
Цель
У класса есть только один экземпляр, и он предоставляет к нему глобальную точку доступа. При попытке создания данного объекта он создаётся только в том случае, если ещё не существует, в противном случае возвращается ссылка на уже существующий экземпляр и нового выделения памяти не происходит. Существенно то, что можно пользоваться именно экземпляром класса, так как при этом во многих случаях становится доступной более широкая функциональность. Например, к описанным компонентам класса можно обращаться через интерфейс, если такая возможность поддерживается языком.
Глобальный «одинокий» объект — именно объект (log().put("Test");
), а не набор процедур, не привязанных ни к какому объекту (logPut("Test");
) — бывает нужен:
- если используется существующая объектно-ориентированная библиотека и ей нужен объект, унаследованный от определённого класса/интерфейса;
- если есть шансы, что один объект когда-нибудь превратится в несколько;
- если интерфейс объекта (например, игрового мира) слишком сложен и не стоит засорять основное пространство имён большим количеством методов;
- если, в зависимости от каких-нибудь условий и настроек, создаётся один из нескольких объектов. Например, в зависимости от того, ведётся лог или нет, создаётся настоящий объект, пишущий в файл, или «заглушка», ничего не делающая.
Такие объекты можно создавать и при инициализации программы. Это может приводить к следующим трудностям:
- Если объект нужен уже при инициализации, он может быть затребован раньше, чем будет создан.
- Бывает, что объект нужен не всегда. В таком случае его создание можно пропустить. Особенно это важно, если одиночек (например, диалоговых окон) много — тогда пользователь быстро получит интерфейс, а окна будут создаваться по одному, не мешая работе пользователя.
Плюсы
- контролируемый доступ к единственному экземпляру.
Минусы
- глобальные объекты могут быть вредны для объектного программирования, в некоторых случаях приводят к созданию немасштабируемого проекта;
- усложняет написание модульных тестов и следование TDD;
- усложняется контроль за межпоточными гонками и задержками.
Применение
- должен быть ровно один экземпляр некоторого класса, легко доступный всем клиентам;
- единственный экземпляр должен расширяться путём порождения подклассов, и клиентам нужно иметь возможность работать с расширенным экземпляром без модификации своего кода.
Примеры использования
- Ведение отладочного файла для приложения.
- В любом приложении для 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() вне зависимости от того, создали ли мы единственный экземпляр или нет. Это замедляет работу программы, если требуется часто получать объект Singleton из разных потоков.
public class Singleton {
private static Singleton instance;
private Singleton () {};
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = 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:
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:
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
, что позволяет предотвратить создание экземпляров класса за пределами его реализации. Помимо этого, закрытыми также объявлены конструктор копирования и оператор присваивания. Последние следует объявлять, но не определять, так как это позволяет в случае их случайного вызова из кода получить легко обнаруживаемую ошибку компоновки. Отметим также, что приведенный пример не является потокобезопасным в С++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 { return instance ?? (instance = new Singleton()); }
}
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
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() { }
}
См. также
Литература
- Алан Шаллоуей, Джеймс Р. Тротт Шаблоны проектирования. Новый подход к объектно-ориентированному анализу и проектированию = 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 (Одиночка) — пример использования шаблона (C++).
- Одиночка — простое описание с примером применения.
- Реализация Singleton на Java — описание классической реализации и многопоточные модификации.
- — The «Double-Checked Locking is Broken» Declaration in java
- Реализация синглтонов на Perl — пример для Perl.
- Singleton Considered Stupid — критика паттерна Singleton
- Мультисинглтон — фабрика синглтонов.
- Паттерн Singleton (Одиночка) — три варианта реализации на C++.
- Классы-одиночки (недоступная ссылка) — реализация для Delphi 6/7.
- Singleton Java, Python, PHP