Статические методы python пример реализации патерна синглтон. Подводные камни Singleton: почему самый известный шаблон проектирования нужно использовать с осторожностью. Так что же использовать Синглтон или Статический класс
Одиночка - паттерн, относящийся к группе порождающих паттернов (шиблонов).
Назначение
Гарантирует, что у класса есть только один экземпляр (ещё точнее можно сказать, что у "класса данного типа может быть только один экземпляр" ) , и предоставляет к нему глобальную точку доступа.
Псевдоним
Английское название Singleton (одиночка) часто калькируется в русскоязычные тексты и паттерн Одиночка называют "Синглтоном". Мы же будем использовать русское название так как оно весьма удачно.
Мотивация
Для некоторых классов важно, чтобы существовал только один экземпляр.
Например:
- Хотя в системе может быть много принтеров, но возможен лишь один спулер.
- Должны быть только одна файловая система и единственный оконный менеджер.
- В цифровом фильтре может находиться только один аналого-цифровой преобразователь (АЦП). Бухгалтерская система обслуживает только одну компанию.
Как гарантировать, что у класса есть единственный экземпляр и что этот экземпляр легко доступен?
Глобальная переменная дает доступ к объекту, но не запрещает инстанцировать класс в нескольких экземплярах.
Более удачное решение достигается в том случае, если сам класс:
- контролирует то, что у него есть только один экземпляр
- может запретить создание дополнительных экземпляров, перехватывая запросы на создание новых объектов
- и он же способен предоставить доступ к своему экземпляру.
Известные применения
Применяется во многих системах, моделирующих реальные объекты.
Родственные паттерны
С помощью паттерна одиночка могут быть реализованы многие паттерны.
Смотри описание.
Сегодня я хочу разобрать шаблон проектирования "одиночка" , который очень часто используется в объектно-ориентированном программировании.
Шаблон проектирования "Одиночка" или Pattern Singleton нужен для того, чтобы у нас не было много однотипных объектов, а всегда использовался только один. В качестве примера можно привести класс для работы с базой данных.
Class DB {
protected $db;
Public function __construct() {
$this->
}
Public function get() {}
public function set() {}
public function del() {}
}
$db1 = new DB();
$db2 = new DB();
У нас уже 2 объекта $db1 и $db2 , а потом кто-нибудь, не зная, что уже есть такой объект, создаст третий и т.д. Это очень плохо сказывается на производительности и читаемости кода, а в нашем случае может произойти сбой, т.к. на хостинге ограниченное количество подключений к базе данных.
Чтобы решить эту проблему, и был придуман паттерн singleton .
Class DB {
protected $db;
static private $instance = null;
Private function __construct() {
$this->db = new Mysqli($host, $user, $pass, $database);
}
Private function __clone() {}
Static function getInstance() {
if(self::$instance == null) {
self::$instance = new self();
}
return self::$instance;
}
}
$db = new DB(); // ошибка
$db = DB::getInstance();
$db2 = DB::getInstance();
$db3 = DB::getInstance();
Чтобы создать объект обычным способом было нельзя, мы делаем наш конструктор приватным , но также не забываем и про то, что объекты могут клонироваться и закрываем также метод __clone . Дальше мы создаём статическое свойство $instance , которое по умолчанию равно null . Теперь создаём статический метод getInstance() , который проверяет, равно ли наше статическое свойство null ? Если да, то мы создаём экземпляр нашего объекта и возвращаем его, а если же нет, то просто возвращаем его. Таким образом, у нас всегда будет один и тот же экземпляр, сколько бы мы их не создавали. Использовать его очень просто: присваиваем переменной значение, которое возвращает статический метод getInstance() , класса DB , а дальше работаем, как и с обычным объектом.
Расскажу сегодня про паттерн проектирования Singleton (одиночка). Цель: создать класс, у которого будет только ОДИН объект. Это значит, что сколько бы раз к нему не обращались, возвращаться будет один и тот же объект, который был создан первый раз. Это удобная вещь и необходимая во многих местах, не зря ее внедряют во фреймворки. Применение:- Например необходимо подключить базу данных в проект и класс, который будет отвечать за соединение с ней. Один раз создается соединение и нет нужны создавать его снова и снова
- Application settings - класс отвечающий за настройки отружения, которые нужны для приложения: хост и порт базы данных и т.д. Они создаются один раз и используются всё время работы приложения.
- есть еще множество примеров, о которых я не сказал, поэтому пишите в комментариях свои варианты! =)
Статья будет полезна в первую очередь разработчикам, которые теряются на собеседованиях когда слышат вопрос «Назовите основные отличия синглтона от статического класса, и когда следует использовать один, а когда другой?». И безусловно будет полезна для тех разработчиков, которые при слове «паттерн» впадают в уныние или просят прекратить выражаться:)
Что такое статический класс?
Для начала вспомним что такое статический класс и для чего он нужен. В любом CLI-совместимом языке используется следующая парадигма инкапсуляции глобальных переменных: глобальных перменных нет . Все члены, в том числе и статические, могут быть объявлены только в рамках какого-либо класса, а сами классы могут (но не должны ) быть сгруппированы в каком-либо пространстве имен. И если раньше приходилось иммитировать поведение статического класса с помощью закрытого конструктора, то в.NET Framework 2.0 была добавлена поддержка статических классов на уровне платформы. Основное отличие статического класса от обычного, нестатического, в том, что невозможно создать экземпляр этого класса с помощью оператора new . Статические классы по сути являются некой разновидностью простанства имен - только в отличие от последних предназначены для размещения статических переменных и методов а не типов.Что такое Singleton (Одиночка)?
Один из порождающих паттернов, впервые описанный «бандой четырех» (GoF). Гарантирует, что у класса есть только один экземпляр , и предоставляет к нему глобальную точку доступа . Мы не будем подробно рассматривать здесь этот паттерн, его предназначение и решаемые им задачи - в сети существует масса подробной информации о нем (например и ). Отмечу лишь что синглтоны бывают потокобезопасные и нет, с простой и отложенной инициализацией.А если нет разницы - зачем плодить больше?
Так в чем же все-таки разница между этими двумя сущностями и когда следует их использовать? Думаю что лучше всего это проиллюстрировать в следующей таблице:Singleton |
Static class |
|
---|---|---|
Количество точек доступа
|
Одна (и только одна) точка доступа - статическое поле Instance
|
N (зависит от количества публичных членов класса и методов) |
Наследование классов
|
Возможно, но не всегда (об этом - ниже) |
Невозможно - статические классы не могут быть экземплярными, поскольку нельзя создавать экземпляры объекты статических классов |
Наследование интерфейсов
|
Возможно, безо всяких ограничений |
|
Возможность передачи в качестве параметров
|
Возможно, поскольку Singleton предоставляет реальный
объект |
Отсутствует |
Контроль времени жизни объекта
|
Возможно - например, отложенная инициализация
(или создание по требованию
) |
Невозможно по той же причине, по которой невозможно наследование классов |
Использование абстрактной фабрики для создания экземпляра класса
|
Возможно |
Невозможно по причине осутствия самой возможности создания экземпляра |
Сериализация
|
Возможно |
Неприменима по причине отсутствия экземпляра |
Рассмотрим подробнее перечисленные выше критерии.
Количество точек доступа
Конечно же имеются ввиду внешние точки доступа, другими словами - публичный контракт взаимодействия класса и его клиентов. Это удобнее проиллюстрировать с помощью кода:Singleton в «канонической» реализации:
public class Session
{
private static Session _instance;
// Реализация паттерна...
public static Session Instance
{
get
{
// ...
return _instance;
}
}
public IUser GetUser()
{
// ...
}
public bool IsSessionExpired()
{
// ...
}
public Guid SessionID
{
get
{
// ...
}
}
}
Статический класс:
public static class Session
{
// Точка доступа 1
public static IUser GetUser()
{
// ...
}
// Точка доступа 2
public static bool IsSessionExpired()
{
// ...
}
// ...
// Точка доступа N
public static Guid SessionID
{
get
{
// ...
}
}
}
Наследование классов
С наследованием статических классов все просто - оно просто не поддерживается на уровне языка. С Singleton все несколько сложнее. Для удобства использования многие разработчики чаще всего используют следующую реализацию паттерна:public class Singleton
А поскольку множественное наследование в C# и в любом CLI-совместимом языке запрещено - это означает что мы не сможем унаследовать класс Session от любого другого полезного класса. Выходом является делагирование синглтону управления доступом к экземпляру объекта:
public class Session: CoreObject { private Session() { } public static Session Instance { get { return Singleton
Наследование интерфейсов
Использование интерфейсов позволяет достичь большей гибкости, увеличить количество повторно используемого кода, повысить тестируемость, и, самое главное - избежать сильной связности объектов. Статические классы не поддерживают наследования в принципе. Синглтон, напротив, наследование интерфейсов поддерживает в полной мере, поскольку это обычный класс. Но вот использовать эту возможность стоит только в том случае, если экземпляр синглтона планируется передавать в качестве входных параметров в смешанных сценариях или транслировать за границу домена. Пример смешанного сценария:// Этот класс является синглтоном и реализует интерфейс ISession public class Session: CoreObject, ISession { private Session() { } public static Session Instance { get { return Singleton
Возможность передачи в качестве параметров
Для статических классов это не поддерживается - можно передать разве что тип, но в большинстве ситуаций это бесполезно, за исключением случаев применения механизмов отражения (reflection ). Синглтон же по сути является обычным экземпляром объекта:// ... ISessionManager _sessionManager; // ... bool isExpired = _sessionManager.IsSessionExpired(Session.Instance);
Контроль времени жизни объекта
Время жизни статического класса ограничено временем жизни домена - если мы создали этот домен вручную, то мы косвенно управляем временем жизни всех его статических типов. Временем жизни синглтона мы можем управлять по нашему желанию. Яркий пример - отложенная инициализация:public class Singleton
Можно также добавить операцию удаления экземпляра синглтона:
public class Singleton
Данная операция является крайне небезопасной, поскольку синглтон может хранить некоторое состояние и поэтому его пересоздание может иметь нежелательные последствия для его клиентов. Если все же необходимость в таком методе возникла (что скорее всего указывает на ошибки проектирования) то нужно постараться свести к минимуму возможное зло от его использования - например сделать его закрытым и вызывать внутри свойства Instance при определенных условиях:
public class Singleton
Использование абстрактной фабрики для создания экземпляра класса
Статический класс не поддерживает данной возможности ввиду того, что нельзя создать экземпляр статического класса. В случае с синглтоном все выглядит просто:public interface IAbstractFactory { T Create
Правда в варианте с аггрегацией синглтона придеться применить не совсем красивое и, немного громоздкое решение:
public class Session: CoreObject, ISession { private class SessionSingleton: Singleton
Сериализация
Сериализация применима только к экземплярам классов. Статический класс не может иметь экзмпляров поэтому сериализовать в данном случае нечего.Так что же использовать Синглтон или Статический класс?
В любом случае выбор решения зависит от разработчика и от специфики решаемой им задачи. Но, в любом случае, можно сделать следующие выводы:Использование синглотона оправдано, когда:
- Необходимо наследование классов или интерфейсов или делегаровать конструирование объектов фабрике
- Необходимо использование экземпляров класса
- Необходимо контролировать время жизни объекта (хоть это и очень редкая задача для синглтона)
- Необходимо сериализовать объект (такая задача гипотетически возможна, но трудно представить себе сценарии использования)
Начнём с главного: его код синглтона не потокобезопасен ! Разбор:
If(instance != null) return instance; // (*) Monitor.Enter(s_lock); Singleton temp = new Singleton(); Interlocked.Exchange(ref instance, temp); Monitor.Exit(s_lock); return instance;
Пусть поток 1 начинает выполнять приведённый код. instance в этот момент есть null , так что первая проверка проходит, и выполнение доходит до строки (*).
Пусть в этот момент произойдёт переключение контекста (это ведь возможно, правильно?), и начинает выполняться второй поток. Он точно так же проверяет instance , проходит начальную проверку, пробегает через (*), получает lock , создаёт экземпляр синглтона, записывает его в instance , отпускает lock и выходит. Второй поток получает ссылку на instance только что созданного синглтона.
Теперь управление получает первый поток. Он точно так же получает lock , создаёт второй экземпляр синглтона, записывает его в instance , затирая старое значение, отпускает lock и выходит. Первый поток получает ссылку на другой объект.
Катастрофа.
Теперь по мелочам. Разработчики.NET не просто так сделали lock(obj) синонимом Monitor.Enter(obj, ref lockTaken) , а не просто Monitor.Enter(obj) . Вариант с Monitor.Enter(obj) работает неправильно в случае, если возможны исключения. Поэтому замена lock на явный Monitor.Enter - ухудшение.
Ещё большее ухудшение - отказ от try/catch. Без базара, без try/catch быстрее, только код получается неправильный. (Впрочем, если правильный код нам не нужен, без lock "а было бы ещё быстрее.) Если по каким-то причинам конструктор Singleton "а выбросит исключение (а это может быть не только деление на 0, но и TypeLoadException , например), то s_lock останется залоченным навсегда - deadlock.
Конструктор может быть только статичным. Это особенность компилятора - если конструткор не статичен, то тип будет помечен как beforefieldinit и readonly создадутся одновременно со static-ами.
Это фактически неверно, автор судя по всему просто не понял, что именно написал Jon. У синглтона есть нестатический конструктор, просто явный статический конструктор должен присутствовать . Между «статический конструктор должен присутствовать» и «конструктор может быть только статичным» огромная разница.
Правильная, остроумная, каноническая реализация синглтона с вложенным классом удостоилась лишь замечания «Недостатки у него те же самые, что у любого другого кода, который использует nested-классы». Мне неизвестны недостатки вызванные одним лишь наличием вложенных классов. Если мне кто-либо сообщит, готов изменить своё мнение.
Наконец, самое концептуально верное решение с Lazy
Затем, правильным при реализации синглтона является не многопоточность или прочие технические мелочи, а простой инвариант: как бы синглтон не использовался, всегда гарантировано наличие не более одного экземпляра
синглтона на AppDomain. Именно это гарантируется классом Lazy
Вывод: не все статьи на Хабре одинаково полезны.