Глобальные переменные в паттерн одиночка c пример. Использование паттерна синглтон

Статья будет полезна в первую очередь разработчикам, которые теряются на собеседованиях когда слышат вопрос «Назовите основные отличия синглтона от статического класса, и когда следует использовать один, а когда другой?». И безусловно будет полезна для тех разработчиков, которые при слове «паттерн» впадают в уныние или просят прекратить выражаться:)

Что такое статический класс?

Для начала вспомним что такое статический класс и для чего он нужен. В любом 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 where T: class { private static T _instance; protected Singleton() { } private static T CreateInstance() { ConstructorInfo cInfo = typeof(T).GetConstructor(BindingFlags.Instance | BindingFlags.NonPublic, null, new Type, new ParameterModifier); return (T)cInfo.Invoke(null); } public static T Instance { get { if (_instance == null) { _instance = CreateInstance(); } return _instance; } } } public class Session: Singleton { public IUser GetUser() { // ... } public bool IsSessionExpired() { // ... } public Guid SessionID { get { // ... } } }
А поскольку множественное наследование в C# и в любом CLI-совместимом языке запрещено - это означает что мы не сможем унаследовать класс Session от любого другого полезного класса. Выходом является делагирование синглтону управления доступом к экземпляру объекта:
public class Session: CoreObject { private Session() { } public static Session Instance { get { return Singleton.Instance; } } }
Наследование интерфейсов
Использование интерфейсов позволяет достичь большей гибкости, увеличить количество повторно используемого кода, повысить тестируемость, и, самое главное - избежать сильной связности объектов. Статические классы не поддерживают наследования в принципе. Синглтон, напротив, наследование интерфейсов поддерживает в полной мере, поскольку это обычный класс. Но вот использовать эту возможность стоит только в том случае, если экземпляр синглтона планируется передавать в качестве входных параметров в смешанных сценариях или транслировать за границу домена. Пример смешанного сценария:
// Этот класс является синглтоном и реализует интерфейс ISession public class Session: CoreObject, ISession { private Session() { } public static Session Instance { get { return Singleton.Instance; } } } // Этот класс не является синглтоном и вообще может быть объявлен и реализован в другой сборке // полностью скрывая детали реализации public class VpnSession: ISession { } public interface ISessionManager { ISession GetSession(Guid sessionID); // Принимает интерфейс ISession, следуя принципам уменьшения связности bool IsSessionExpired(ISession session); }
Возможность передачи в качестве параметров
Для статических классов это не поддерживается - можно передать разве что тип, но в большинстве ситуаций это бесполезно, за исключением случаев применения механизмов отражения (reflection ). Синглтон же по сути является обычным экземпляром объекта:
// ... ISessionManager _sessionManager; // ... bool isExpired = _sessionManager.IsSessionExpired(Session.Instance);
Контроль времени жизни объекта
Время жизни статического класса ограничено временем жизни домена - если мы создали этот домен вручную, то мы косвенно управляем временем жизни всех его статических типов. Временем жизни синглтона мы можем управлять по нашему желанию. Яркий пример - отложенная инициализация:
public class Singleton where T: class { // ... public static T Instance { get { if (_instance == null) { // Создание "по требованию" _instance = CreateInstance(); } return _instance; } } }
Можно также добавить операцию удаления экземпляра синглтона:
public class Singleton where T: class { // ... public static T Instance { // ... } // Очень опасная операция! public void RemoveInstance() { _instance = null; } }
Данная операция является крайне небезопасной, поскольку синглтон может хранить некоторое состояние и поэтому его пересоздание может иметь нежелательные последствия для его клиентов. Если все же необходимость в таком методе возникла (что скорее всего указывает на ошибки проектирования) то нужно постараться свести к минимуму возможное зло от его использования - например сделать его закрытым и вызывать внутри свойства Instance при определенных условиях:
public class Singleton where T: class { // ... public static T Instance { get { if (!IsAlive) { // Удаление по условию RemoveInstance(); } if (_instance == null) { // Создание "по требованию" _instance = CreateInstance(); } return _instance; } } private void RemoveInstance() { _instance = null; } }
Использование абстрактной фабрики для создания экземпляра класса
Статический класс не поддерживает данной возможности ввиду того, что нельзя создать экземпляр статического класса. В случае с синглтоном все выглядит просто:
public interface IAbstractFactory { T Create(); bool IsSupported(); } public class Singleton where T: class { private static T _instance; private static IAbstractFactory _factory; protected Singleton(IAbstractFactory factory) { _factory = factory; } public static T Instance { get { if (_instance == null) { _instance = _factory.Create(); } return _instance; } } } // Вариант с прямым наследованием от синглтона public class Session: Singleton { protected Session() : base(new ConcreteFactory()) { } // ... }
Правда в варианте с аггрегацией синглтона придеться применить не совсем красивое и, немного громоздкое решение:
public class Session: CoreObject, ISession { private class SessionSingleton: Singleton { protected SessionSingleton() : base(new ConcreteFactory2()) { } } private Session() : base(new CoreContext()) { } public static Session Instance { get { return SessionSingleton.Instance; } } // ... }
Сериализация
Сериализация применима только к экземплярам классов. Статический класс не может иметь экзмпляров поэтому сериализовать в данном случае нечего.

Так что же использовать Синглтон или Статический класс?

В любом случае выбор решения зависит от разработчика и от специфики решаемой им задачи. Но, в любом случае, можно сделать следующие выводы:

Использование синглотона оправдано, когда:

  • Необходимо наследование классов или интерфейсов или делегаровать конструирование объектов фабрике
  • Необходимо использование экземпляров класса
  • Необходимо контролировать время жизни объекта (хоть это и очень редкая задача для синглтона)
  • Необходимо сериализовать объект (такая задача гипотетически возможна, но трудно представить себе сценарии использования)
Использование статических классов целесообразно тогда , когда у вас нет необходимости реализовывать ни один из сценариев перечисленных для синглтона. Основное назначение статических классов все-таки в группировке логически схожих методов, констант, полей и свойств. Например: System.Math , System.BitConverter , System.Buffer , System.Convert и т.д.

Введение

Singleton – один из самых простых шаблонов для понимания. Основное назначение – гарантировать существование только одно экземпляра класса. Причиной обычно является следующее: требуется только один объект исходного класса и Вам необходимо, что бы объект был доступен в любом месте приложения, т.е. глобальный доступ.

В качестве примера может служить класс для хранения установочных параметров(Settings). Settings class – хороший пример шаблона Singleton, потому что его данные не измены (единственный путь изменения установочных параметров это редактирование файла установочных параметров) и часто используются в различных частях приложения. Кроме того, создание новых объектов класса Settings, где это необходимо, требует ресурсы, что расточительно, т.к. объекты будут идентичны.

Определение

Шаблон Singleton предполагает наличие статического метода для создания экземпляра класса, при обращении к которому возвращается ссылка на оригинальный объект.

Пример для PHP5

Пример для PHP5(без реализации конкретных методов класса Settings)
class Settings {
private $settings = array();
private static $_instance = null;
private function __construct() {
// приватный конструктор ограничивает реализацию getInstance ()
}
protected function __clone() {
// ограничивает клонирование объекта
}
static public function getInstance() {
if(is_null(self::$_instance))
{
self::$_instance = new self();
}
return self::$_instance;
}
public function import() {
// ...
}
public function get() {
// ...
}
}

Реализация шаблона Singleton

Ключoм реализации шаблона Singleton является статическая переменная, переменная чье значение остается неизменным при исполнении за ее приделами. Это позволяет сохранить объект оригинальным между вызовами статического метода Settings::getInstance(), и возвратить ссылку на него при каждом последующем вызове метода.
Имейте так же в виду, что конструктор, как правило, приватный. Что бы обеспечить использование всегда только одного объекта Settings мы должны ограничить доступ к конструктору, что бы при попытке создания нового объекта возникала ошибка. Так же следует иметь в виду, что данные ограничения не возможны в PHP4.

Последнее обновление: 23.12.2018

Одиночка (Singleton, Синглтон) - порождающий паттерн, который гарантирует, что для определенного класса будет создан только один объект, а также предоставит к этому объекту точку доступа.

Когда надо использовать Синглтон? Когда необходимо, чтобы для класса существовал только один экземпляр

Синглтон позволяет создать объект только при его необходимости. Если объект не нужен, то он не будет создан. В этом отличие синглтона от глобальных переменных.

Классическая реализация данного шаблона проектирования на C# выглядит следующим образом:

Class Singleton { private static Singleton instance; private Singleton() {} public static Singleton getInstance() { if (instance == null) instance = new Singleton(); return instance; } }

В классе определяется статическая переменная - ссылка на конкретный экземпляр данного объекта и приватный конструктор. В статическом методе getInstance() этот конструктор вызывается для создания объекта, если, конечно, объект отсутствует и равен null.

Для применения паттерна Одиночка создадим небольшую программу. Например, на каждом компьютере можно одномоментно запустить только одну операционную систему. В этом плане операционная система будет реализоваться через паттерн синглтон:

Class Program { static void Main(string args) { Computer comp = new Computer(); comp.Launch("Windows 8.1"); Console.WriteLine(comp.OS.Name); // у нас не получится изменить ОС, так как объект уже создан comp.OS = OS.getInstance("Windows 10"); Console.WriteLine(comp.OS.Name); Console.ReadLine(); } } class Computer { public OS OS { get; set; } public void Launch(string osName) { OS = OS.getInstance(osName); } } class OS { private static OS instance; public string Name { get; private set; } protected OS(string name) { this.Name=name; } public static OS getInstance(string name) { if (instance == null) instance = new OS(name); return instance; } }

Синглтон и многопоточность

При применении паттерна синглтон в многопоточным программах мы можем столкнуться с проблемой, которую можно описать следующим образом:

Static void Main(string args) { (new Thread(() => { Computer comp2 = new Computer(); comp2.OS = OS.getInstance("Windows 10"); Console.WriteLine(comp2.OS.Name); })).Start(); Computer comp = new Computer(); comp.Launch("Windows 8.1"); Console.WriteLine(comp.OS.Name); Console.ReadLine(); }

Здесь запускается дополнительный поток, который получает доступ к синглтону. Параллельно выполняется тот код, который идет запуска потока и кторый также обращается к синглтону. Таким образом, и главный, и дополнительный поток пытаются инициализровать синглтон нужным значением - "Windows 10", либо "Windows 8.1". Какое значение сиглтон получит в итоге, пресказать в данном случае невозможно.

Вывод программы может быть такой:

Windows 8.1 Windows 10

Или такой:

Windows 8.1 Windows 8.1

В итоге мы сталкиваемся с проблемой инициализации синглтона, когда оба потока одновременно обращаются к коду:

If (instance == null) instance = new OS(name);

Чтобы решить эту проблему, перепишем класс синглтона следующим образом:

Class OS { private static OS instance; public string Name { get; private set; } private static object syncRoot = new Object(); protected OS(string name) { this.Name = name; } public static OS getInstance(string name) { if (instance == null) { lock (syncRoot) { if (instance == null) instance = new OS(name); } } return instance; } }

Чтобы избежать одновременного доступа к коду из разных потоков критическая секция заключается в блок lock .

Другие реализации синглтона

Выше были рассмотрены общие стандартные реализации: потоконебезопасная и потокобезопасная реализации паттерна. Но есть еще ряд дополнительных реализаций, которые можно рассмотреть.

Потокобезопасная реализация без использования lock

public class Singleton { private static readonly Singleton instance = new Singleton(); public string Date { get; private set; } private Singleton() { Date = System.DateTime.Now.TimeOfDay.ToString(); } public static Singleton GetInstance() { return instance; } }

Данная реализация также потокобезопасная, то есть мы можем использовать ее в потоках так:

(new Thread(() => { Singleton singleton1 = Singleton.GetInstance(); Console.WriteLine(singleton1.Date); })).Start(); Singleton singleton2 = Singleton.GetInstance(); Console.WriteLine(singleton2.Date);

Lazy-реализация

Определение объекта синглтона в виде статического поля класса открывает нам дорогу к созданию Lazy-реализации паттерна Синглтон, то есть такой реализации, где данные будут инициализироваться только перед непосредственным использованием. Поскольку статические поля инициализируются перед первым доступом к статическому членам класса и перед вызовом статического конструктора (при его наличии). Однако здесь мы можем столкнуться с двумя трудностями.

Во-первых, класс синглтона может иметь множество статических переменных. Возможно, мы вообще не будем обращаться к объекту синглтона, а будем использовать какие-то другие статические переменные:

Public class Singleton { private static readonly Singleton instance = new Singleton(); public static string text = "hello"; public string Date { get; private set; } private Singleton() { Console.WriteLine($"Singleton ctor {DateTime.Now.TimeOfDay}"); Date = System.DateTime.Now.TimeOfDay.ToString(); } public static Singleton GetInstance() { Console.WriteLine($"GetInstance {DateTime.Now.TimeOfDay}"); Thread.Sleep(500); return instance; } } class Program { static void Main(string args) { Console.WriteLine($"Main {DateTime.Now.TimeOfDay}"); Console.WriteLine(Singleton.text); Console.Read(); } }

В данном случае идет только обращение к переменной text, однако статическое поле instance также будет инициализировано. Например, консольный вывод в данном случае мог бы выглядеть следующим образом:

Singleton ctor 16:05:54.1469982 Main 16:05:54.2920316 hello

В данном случае мы видим, что статическое поле instance инициализировано.

Для решения этой проблемы выделим отдельный внутренний класс в рамках класса синглтона:

Public class Singleton { public string Date { get; private set; } public static string text = "hello"; private Singleton() { Console.WriteLine($"Singleton ctor {DateTime.Now.TimeOfDay}"); Date = DateTime.Now.TimeOfDay.ToString(); } public static Singleton GetInstance() { Console.WriteLine($"GetInstance {DateTime.Now.TimeOfDay}"); return Nested.instance; } private class Nested { internal static readonly Singleton instance = new Singleton(); } } class Program { static void Main(string args) { Console.WriteLine($"Main {DateTime.Now.TimeOfDay}"); Console.WriteLine(Singleton.text); Console.Read(); } }

Теперь статическая переменная, которая представляет объект синглтона, определена во вложенном классе Nested. Чтобы к этой переменной можно было обращаться из класса синглтона, она имеет модификатор internal, в то же время сам класс Nested имеет модификатор private, что позволяет гарантировать, что данный класс будет доступен только из класса Singleton.

Консольный вывод в данном случае мог бы выглядеть следующим образом:

Main 16:11:40.1320873 hello

Далее мы сталкиваемся со второй проблемой: статические поля инициализируются перед первым доступом к статическому членам класса и перед вызовом статического конструктора (при его наличии). Но когда именно? Если класс содержит статические поля, не содержит статического конструктора, то время инициализации статических полей зависит от реализации платформы. Нередко это непосредственно перед первым использованием, но тем не менее момент точно не определен - это может быть происходить и чуть раньше. Однако если класс содержит статический конструктор, то статические поля будут инициализироваться непосредственно либо при создании первого экземпляра класса, либо при первом обращении к статическим членам класса.

Например, рассмотрим выполнение следующей программы:

Static void Main(string args) { Console.WriteLine($"Main {DateTime.Now.TimeOfDay}"); Console.WriteLine(Singleton.text); Singleton singleton1 = Singleton.GetInstance(); Console.WriteLine(singleton1.Date); Console.Read(); }

Ее возможный консольный вывод:

Main 16:33:33.1404818 hello Singleton ctor 16:33:33.1564802 GetInstance 16:33:33.1574824 16:33:33.1564802

Мы видим, что код метода GetInstance, который идет до вызова конструктора класса Singleton, выполняется после выполнения этого конструктора. Поэтому добавим в выше определенный класс Nested статический конструктор:

Public class Singleton { public string Date { get; private set; } public static string text = "hello"; private Singleton() { Console.WriteLine($"Singleton ctor {DateTime.Now.TimeOfDay}"); Date = DateTime.Now.TimeOfDay.ToString(); } public static Singleton GetInstance() { Console.WriteLine($"GetInstance {DateTime.Now.TimeOfDay}"); Thread.Sleep(500); return Nested.instance; } private class Nested { static Nested() { } internal static readonly Singleton instance = new Singleton(); } }

Теперь при выполнении той же программы мы получим полноценную Lazy-реализацию:

Main 16:37:18.4108064 hello GetInstance 16:37:18.4208062 Singleton ctor 16:37:18.4218065 16:37:18.4228061

Реализация через класс Lazy

Еще один способ создания синглтона представляет использование класса Lazy:

Public class Singleton { private static readonly Lazy lazy = new Lazy(() => new Singleton()); public string Name { get; private set; } private Singleton() { Name = System.Guid.NewGuid().ToString(); } public static Singleton GetInstance() { return lazy.Value; } }

Назначение паттерна Singleton

Часто в системе могут существовать сущности только в единственном экземпляре, например, система ведения системного журнала сообщений или драйвер дисплея. В таких случаях необходимо уметь создавать единственный экземпляр некоторого типа, предоставлять к нему доступ извне и запрещать создание нескольких экземпляров того же типа.

Паттерн Singleton предоставляет такие возможности.

Описание паттерна Singleton

Архитектура паттерна Singleton основана на идее использования глобальной переменной, имеющей следующие важные свойства:

  1. Такая переменная доступна всегда. Время жизни глобальной переменной - от запуска программы до ее завершения.
  2. Предоставляет глобальный доступ, то есть, такая переменная может быть доступна из любой части программы.

Однако, использовать глобальную переменную некоторого типа непосредственно невозможно, так как существует проблема обеспечения единственности экземпляра, а именно, возможно создание нескольких переменных того же самого типа (например, стековых).

Для решения этой проблемы паттерн Singleton возлагает контроль над созданием единственного объекта на сам класс. Доступ к этому объекту осуществляется через статическую функцию-член класса, которая возвращает указатель или ссылку на него. Этот объект будет создан только при первом обращении к методу, а все последующие вызовы просто возвращают его адрес. Для обеспечения уникальности объекта, конструкторы и оператор присваивания объявляются закрытыми.

Паттерн Singleton часто называют усовершенствованной глобальной переменной.

Реализация паттерна Singleton

Классическая реализация Singleton

Рассмотрим наиболее часто встречающуюся реализацию паттерна Singleton.

// Singleton.h class Singleton { private: static Singleton * p_instance; // Конструкторы и оператор присваивания недоступны клиентам Singleton() {} Singleton(const Singleton&); Singleton& operator=(Singleton&); public: static Singleton * getInstance() { if(!p_instance) p_instance = new Singleton(); return p_instance; } }; // Singleton.cpp #include "Singleton.h" Singleton* Singleton::p_instance = 0;

Клиенты запрашивают единственный объект класса через статическую функцию-член getInstance() , которая при первом запросе динамически выделяет память под этот объект и затем возвращает указатель на этот участок памяти. Впоследcтвии клиенты должны сами позаботиться об освобождении памяти при помощи оператора delete .

Последняя особенность является серьезным недостатком классической реализации шаблона Singleton. Так как класс сам контролирует создание единственного объекта, было бы логичным возложить на него ответственность и за разрушение объекта. Этот недостаток отсутствует в реализации Singleton, впервые предложенной Скоттом Мэйерсом.

Singleton Мэйерса

// Singleton.h class Singleton { private: Singleton() {} Singleton(const Singleton&); Singleton& operator=(Singleton&); public: static Singleton& getInstance() { static Singleton instance; return instance; } };

Внутри getInstance() используется статический экземпляр нужного класса. Стандарт языка программирования C++ гарантирует автоматическое уничтожение статических объектов при завершении программы. Досрочного уничтожения и не требуется, так как объекты Singleton обычно являются долгоживущими объектами. Статическая функция-член getInstance() возвращает не указатель, а ссылку на этот объект, тем самым, затрудняя возможность ошибочного освобождения памяти клиентами.

Приведенная реализация паттерна Singleton использует так называемую отложенную инициализацию (lazy initialization) объекта, когда объект класса инициализируется не при старте программы, а при первом вызове getInstance() . В данном случае это обеспечивается тем, что статическая переменная instance объявлена внутри функции - члена класса getInstance() , а не как статический член данных этого класса. Отложенную инициализацию, в первую очередь, имеет смысл использовать в тех случаях, когда инициализация объекта представляет собой дорогостоящую операцию и не всегда используется.

К сожалению, у реализации Мэйерса есть недостатки: сложности создания объектов производных классов и невозможность безопасного доступа нескольких клиентов к единственному объекту в многопоточной среде.

Улучшенная версия классической реализации Singleton

С учетом всего вышесказанного классическая реализация паттерна Singleton может быть улучшена.

// Singleton.h class Singleton; // опережающее объявление class SingletonDestroyer { private: Singleton* p_instance; public: ~SingletonDestroyer(); void initialize(Singleton* p); }; class Singleton { private: static Singleton* p_instance; static SingletonDestroyer destroyer; protected: Singleton() { } Singleton(const Singleton&); Singleton& operator=(Singleton&); ~Singleton() { } friend class SingletonDestroyer; public: static Singleton& getInstance(); }; // Singleton.cpp #include "Singleton.h" Singleton * Singleton::p_instance = 0; SingletonDestroyer Singleton::destroyer; SingletonDestroyer::~SingletonDestroyer() { delete p_instance; } void SingletonDestroyer::initialize(Singleton* p) { p_instance = p; } Singleton& Singleton::getInstance() { if(!p_instance) { p_instance = new Singleton(); destroyer.initialize(p_instance); } return *p_instance; }

Ключевой особенностью этой реализации является наличие класса SingletonDestroyer , предназначенного для автоматического разрушения объекта Singleton. Класс Singleton имеет статический член SingletonDestroyer , который инициализируется при первом вызове Singleton::getInstance() создаваемым объектом Singleton . При завершении программы этот объект будет автоматически разрушен деструктором SingletonDestroyer (для этого SingletonDestroyer объявлен другом класса Singleton).

Для предотвращения случайного удаления пользователями объекта класса Singleton , деструктор теперь уже не является общедоступным как ранее. Он объявлен защищенным.

Использование нескольких взаимозависимых одиночек

До сих пор предполагалось, что в программе используется один одиночка либо несколько несвязанных между собой. При использовании взаимосвязанных одиночек появляются новые вопросы:

  • Как гарантировать, что к моменту использования одного одиночки, экземпляр другого зависимого уже создан?
  • Как обеспечить возможность безопасного использования одного одиночки другим при завершении программы? Другими словами, как гарантировать, что в момент разрушения первого одиночки в его деструкторе еще возможно использование второго зависимого одиночки (то есть второй одиночка к этому моменту еще не разрушен)?

Управлять порядком создания одиночек относительно просто. Следующий код демонстрирует один из возможных методов.

// Singleton.h class Singleton1 { private: Singleton1() { } Singleton1(const Singleton1&); Singleton1& operator=(Singleton1&); public: static Singleton1& getInstance() { static Singleton1 instance; return instance; } }; class Singleton2 { private: Singleton2(Singleton1& instance): s1(instance) { } Singleton2(const Singleton2&); Singleton2& operator=(Singleton2&); Singleton1& s1; public: static Singleton2& getInstance() { static Singleton2 instance(Singleton1::getInstance()); return instance; } }; // main.cpp #include "Singleton.h" int main() { Singleton2& s = Singleton2::getInstance(); return 0; }

Объект Singleton1 гарантированно инициализируется раньше объекта Singleton2 , так как в момент создания объекта Singleton2 происходит вызов Singleton1::getInstance() .

Гораздо сложнее управлять временем жизни одиночек. Существует несколько способов это сделать, каждый из них обладает своими достоинствами и недостатками и заслуживают отдельного рассмотрения. Обсуждение этой непростой темы остается за рамками проекта. Подробную информацию можно найти в .

Несмотря на кажущуюся простоту паттерна Singleton (используется всего один класс), его реализация не является тривиальной.

Результаты применения паттерна Singleton

Достоинства паттерна Singleton

  • Класс сам контролирует процесс создания единственного экземпляра.
  • Паттерн легко адаптировать для создания нужного числа экземпляров.
  • Возможность создания объектов классов, производных от 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 , а дальше работаем, как и с обычным объектом.

Поделиться: