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

Когда необходимо использовать порождающий паттерн одиночка

  • автор:

Когда необходимо использовать порождающий паттерн одиночка

Одиночка (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 "); Date = System.DateTime.Now.TimeOfDay.ToString(); > public static Singleton GetInstance() < Console.WriteLine($"GetInstance "); Thread.Sleep(500); return instance; > > class Program < static void Main(string[] args) < Console.WriteLine($"Main "); Console.WriteLine(Singleton.text); > >

В данном случае идет только обращение к переменной 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 "); Date = DateTime.Now.TimeOfDay.ToString(); > public static Singleton GetInstance() < Console.WriteLine($"GetInstance "); return Nested.instance; > private class Nested < internal static readonly Singleton instance = new Singleton(); >> class Program < static void Main(string[] args) < Console.WriteLine($"Main "); Console.WriteLine(Singleton.text); > >

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

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

Main 16:11:40.1320873 hello

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

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

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

Программируем просто

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

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

Одиночка определяет операцию Instance, которая позволяет клиентам получать доступ к единственному экземпляру. Клиенты имеют доступ к одиночке только через эту операцию.

Паттерн позволяет избежать засорения пространства имен глобальными переменными, в которых хранятся уникальные экземпляры. От класса Singleton можно порождать подклассы, а приложение легко сконфигурировать экземпляром расширенного класса.

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

Начиная с С++11 инициализация static переменных, является потокобезопасной (If multiple threads attempt to initialize the same static local variable concurrently, the initialization occurs exactly once (similar behavior can be obtained for arbitrary functions with std::call_once).
Note: usual implementations of this feature use variants of the double-checked locking pattern, which reduces runtime overhead for already-initialized local statics to a single non-atomic boolean comparison. If control enters the declaration concurrently while the variable is being initialized, the concurrent execution shall wait for completion of the initialization. )
Поэтому классический пример одиночки Мэйерса(представленный ниже), является потокобезопасным.

#include struct logSingleton < void init()  // вызывается гарантированно один раз // выполняем открытие файла // выполняем нужные действия std::cout static logSingleton& instance() < static logSingleton res; return res; >void log() < std::cout private: logSingleton() < init(); // инициализация выполняется один раз > logSingleton(const logSingleton&) = delete; logSingleton & operator=(const logSingleton&) = delete; logSingleton(const logSingleton&&) = delete; logSingleton &operator=(const logSingleton&&) = delete; >; int main() < < logSingleton& s1 = logSingleton::instance(); s1.log(); > < logSingleton& s2 = logSingleton::instance(); s2.log(); > return 0; > 

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

#include struct SingBase  static SingBase &instance() protected: SingBase() < std::cout ~SingBase() < std::cout SingBase()" >; struct SingType1 : SingBase  static SingType1 &instance() protected: SingType1() < std::cout << "SingType1()" ~SingType1() < std::cout SingType1()" >; int main() < SingBase::instance(); SingType1::instance(); SingBase::instance(); SingType1::instance(); return 0; > 

Сложно проследить зависимость одних одиночек от других.
Всегда думайте и размышляйте 🙂
Можно завести специальное место, в начале функции main() в котором вы вызовете все методы instance(), всех одиночек, этим вы уже оградите себя от ряда проблем.
Но если следовать правилу — код должен быть простым, код пишут для человека, а компилятор переводит этот код для компьютера, то только это одно правило уже избавляет от многих проблем.

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

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

Еще раз про паттерны

Паттерны проектирования — это решения распространенных проблем при разработке приложений. Также они известны как шаблоны проектирования, паттерны объектно-ориентированного программирования и design patterns. В отличие от готовых функций или библиотек, паттерн представляет собой не конкретный код, а общую концепцию решения проблемы, которую еще нужно подстроить под задачи.

Всего существует 23 классических паттерна, которые были описаны в книге «Банды четырех». В зависимости от того, какие задачи они решают, делятся на порождающие, структурные и поведенческие.

Порождающие паттерны

Согласно Википедии, порождающие шаблоны (creational patterns) — шаблоны проектирования, которые позволяют сделать систему независимой от способа создания, композиции и представления объектов.

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

  • Singleton, или Одиночка
  • Builder, или Строитель
  • Factory Method, или Фабричный метод
  • Prototype, или Прототип
  • Abstract Factory, или Абстрактная фабрика

3 самых популярных порождающих паттерна

По мнению разработчиков MediaSoft Singleton, Builder и Factory Method — это самые используемые порождающие паттерны в разработке. Давайте разберемся, с какими задачами они помогают справляться, и посмотрим на примеры их реализации.

Singleton (Одиночка)

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

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

Еще проще: хороший пример этого паттерна из жизни — это классный журнал в школе. У каждого класса он только один, и если учитель спросит журнал, то всегда получит один и тот же экземпляр.

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

Как создать:

  1. Создаем класс, в котором прописываем логику.
  2. Создаем статическое поле и инициализируем его.
  3. Дальше создаем приватный инициализатор. Это гарантирует, что никакой другой клиент или класс не сможет создавать экземпляр этого класса.
  4. Теперь, чтобы использовать методы нашего класса, мы получаем доступ через статическое поле.

Пример реализации:

public class Singleton < private static Singleton instance; private Singleton () <>; public static synchronized Singleton getInstance() < if (instance == null) < instance = new Singleton(); >return instance; > >
// создаем класс Singleton, который проверяет имеется ли у него свойство instance // если нет, тогда создается новый экземпляр, но если свойство есть // тогда мы возвращаем ранее созданный экземпляр класса class Singleton < constructor(data) < if (Singleton.instance) < return Singleton.instance; >Singleton.instance = this; this.data = data; > consoleData() < console.log(this.data); >>; // в работе Singleton мы можем убедиться создав 2 экземпляра класса const firstSingleton = new Singleton('firstSingleton'); const secondSingleton = new Singleton('secondSingleton'); // в обоих случаях в консоли выведется одинаковое сообщение firstSingleton.consoleData(); //firstSingleton secondSingleton.consoleData(); //firstSingleton 

Комментарий: Есть мнение, что одиночка является анти-паттерном — его противники утверждают, что одиночка привносит глобальное состояние в приложение и трудно тестируется. Несмотря на то, что в этом есть своя правда, в действительности же, этот паттерн не вызывает проблем, если помнить о следующем: суть одиночки заключается в том, чтобы в определенный момент выполнения программы обеспечить наличие одного и только одного экземпляра определенного класса. Создание одиночки для «удобства» — верный признак плохого кода.

Builder (Строитель)

Согласно Википедии, Builder — порождающий шаблон проектирования, который предоставляет способ создания составного объекта. Отделяет конструирование сложного объекта от его представления так, что в результате одного и того же процесса конструирования могут получаться разные представления.

Проще говоря, Builder позволяет создавать разные объекты с заданным состоянием, используя один и тот же код.

Еще проще: пример этого паттерна в жизни — покупка компьютера в магазине. При выборе мы указываем, какими характеристиками должна обладать техника (например, память 16 ГБ, процессор Intel core i7 и так далее). Таким образом, мы можем создавать различные виды одного объекта.

Когда нужен: пригодится при составлении SQL-запроса, а также в юнит-тестах.

Как создать:

  1. Создаем абстрактный класс Строитель, у которого объявляем методы инициализации параметров продукта.
  2. Создаем классы, наследующие Строитель, и переопределяющие методы инициализации параметров продукта.
  3. Создаем класс-распорядитель, который выполняет скоординированные действия по созданию Продукта при помощи Строителя

Пример реализации:

class Pizza < private String dough = ""; private String sauce = ""; public void setDough(String dough) < this.dough = dough; >public void setSauce(String sauce) < this.sauce = sauce; >> abstract class PizzaBuilder < // Abstract Builder protected Pizza pizza; public void createNewPizzaProduct() < pizza = new Pizza(); >public abstract void buildDough(); public abstract void buildSauce(); > class SpicyPizzaBuilder extends PizzaBuilder < // Concrete Builder public void buildDough() < pizza.setDough("pan baked"); >public void buildSauce() < pizza.setSauce("hot"); >> class Waiter < // Director private final PizzaBuilder pizzaBuilder; public Waiter(PizzaBuilder pb) < pizzaBuilder = pb; >public void constructPizza() < pizzaBuilder.createNewPizzaProduct(); pizzaBuilder.buildDough(); pizzaBuilder.buildSauce(); >> public class BuilderExample < // A customer ordering a pizza public static void main(String[] args) < Waiter waiter = new Waiter(new SpicyPizzaBuilder()); waiter.constructPizza(); >>
class Apartment < constructor(options) < for (const option in options) < this[option] = options[option]; >> getOptions() < return Количество комнат: $, площадь: $, этаж: $; > > class ApartmentBuilder < setRoomsNumber(roomsNumber) < this.roomsNumber = roomsNumber; return this; >setFloor(floor) < this.floor = floor; return this; >setSquare(square) < this.square = square; return this; >build() < return new Apartment(this); >> const bigApartment = new ApartmentBuilder() .setFloor(10) .setRoomsNumber(5) .setSquare(120) .build(); console.log(bigApartment.getOptions());

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

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

Factory Method (Фабричный метод)

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

Проще говоря, паттерн позволяет использовать один класс для создания объектов разной реализации интерфейса и делегировать логику дочерним классам.

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

Когда нужен: паттерн подходит для ситуаций, когда нам необходимо выполнить действие (например, отправить запрос), но заранее неизвестны все данные, так как они зависят от входных параметров (например, от протокола передачи данных — rest, soap или socket).

Как создать:

  1. Создаем класс TicketFactory.
  2. Добавляем методы, которые принимают нужные параметры, и создают сущности, которые инициализируются переданными параметрами.
  3. Внутри этих методов вызываются конструкторы и сеттеры для инициализации параметров.
  4. На выходе нам возвращается инициализированная сущность.

Пример реализации:

interface Product < >class ConcreteProductA implements Product < >class ConcreteProductB implements Product < >abstract class Creator < public abstract Product factoryMethod(); >class ConcreteCreatorA extends Creator < public Product factoryMethod() < return new ConcreteProductA(); >> class ConcreteCreatorB extends Creator < public Product factoryMethod() < return new ConcreteProductB(); >> public class FactoryMethodExample < public static void main(String[] args) < Listcreators = List.of(new ConcreteCreatorA(), new ConcreteCreatorB()); creators.stream().map(Creator::factoryMethod).map(Object::getClass).forEach(System.out::println); > >
class Apartment < constructor(roomsNumber, square, floor) < this.roomsNumber = roomsNumber; this.floor = floor; this.square = square; >getOptions() < return Количество комнат: $, площадь: $, этаж: $; > >; class ApartmentFactory < createApartament(type, floor) < switch(type) < case('oneRoom'): return new Apartment(1, 35, floor); case('twoRooms'): return new Apartment(2, 55, floor); case('threeRooms'): return new Apartment(3, 75, floor); default: throw new Error('Такой квартиры не найдено'); >> > const oneRoomAparnament = new ApartmentFactory().createApartament('oneRoom', 10); console.log(oneRoomAparnament.getOptions());

ЗАКЛЮЧЕНИЕ

Паттерны проектирования — это решения распространенных проблем при разработке кода. Их знание и использование позволяет экономить время, используя готовые решения, стандартизировать код и повысить общий словарь.

В зависимости от того, какие задачи решают паттерны проектирования, они делятся на три вида: порождающие, структурные и поведенческие.

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

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

Builder, или Строитель, позволяет создавать разные объекты с заданным состоянием, используя один и тот же код. Будет полезен при составлении SQL-запроса или в юнит-тестах.

Factory method, или Фабричный метод, позволяет использовать один класс для создания объектов разной реализации интерфейса и делегировать логику дочерним классам. Подходит для ситуаций, когда нам необходимо выполнить действие, но способ выполнения неизвестен заранее.

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

Одиночка

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

Паттерн Одиночка

Проблема

Одиночка решает сразу две проблемы, нарушая принцип единственной ответственности класса.

Глобальный доступ к одному объекту

  1. Гарантирует наличие единственного экземпляра класса. Чаще всего это полезно для доступа к какому-то общему ресурсу, например, базе данных. Представьте, что вы создали объект, а через некоторое время пробуете создать ещё один. В этом случае хотелось бы получить старый объект, вместо создания нового. Такое поведение невозможно реализовать с помощью обычного конструктора, так как конструктор класса всегда возвращает новый объект.
  1. Предоставляет глобальную точку доступа. Это не просто глобальная переменная, через которую можно достучаться к определённому объекту. Глобальные переменные не защищены от записи, поэтому любой код может подменять их значения без вашего ведома. Но есть и другой нюанс. Неплохо бы хранить в одном месте и код, который решает проблему №1, а также иметь к нему простой и доступный интерфейс.

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

Решение

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

Если у вас есть доступ к классу одиночки, значит, будет доступ и к этому статическому методу. Из какой точки кода вы бы его ни вызвали, он всегда будет отдавать один и тот же объект.

Аналогия из жизни

Правительство государства — хороший пример одиночки. В государстве может быть только одно официальное правительство. Вне зависимости от того, кто конкретно заседает в правительстве, оно имеет глобальную точку доступа «Правительство страны N».

Структура

Структура классов паттерна Одиночка

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

Псевдокод

В этом примере роль Одиночки отыгрывает класс подключения к базе данных.

Этот класс не имеет публичного конструктора, поэтому единственный способ получить его объект — это вызвать метод getInstance . Этот метод сохранит первый созданный объект и будет возвращать его при всех последующих вызовах.

// Класс одиночки определяет статический метод `getInstance`, // который позволяет клиентам повторно использовать одно и то же // подключение к базе данных по всей программе. class Database is // Поле для хранения объекта-одиночки должно быть объявлено // статичным. private static field instance: Database // Конструктор одиночки всегда должен оставаться приватным, // чтобы клиенты не могли самостоятельно создавать // экземпляры этого класса через оператор `new`. private constructor Database() is // Здесь может жить код инициализации подключения к // серверу баз данных. // . // Основной статический метод одиночки служит альтернативой // конструктору и является точкой доступа к экземпляру этого // класса. public static method getInstance() is if (Database.instance == null) then acquireThreadLock() and then // На всякий случай ещё раз проверим, не был ли // объект создан другим потоком, пока текущий // ждал освобождения блокировки. if (Database.instance == null) then Database.instance = new Database() return Database.instance // Наконец, любой класс одиночки должен иметь какую-то // полезную функциональность, которую клиенты будут // запускать через полученный объект одиночки. public method query(sql) is // Все запросы к базе данных будут проходить через этот // метод. Поэтому имеет смысл поместить сюда какую-то // логику кеширования. // . class Application is method main() is Database foo = Database.getInstance() foo.query("SELECT . ") // . Database bar = Database.getInstance() bar.query("SELECT . ") // Переменная "bar" содержит тот же объект, что и // переменная "foo".

Применимость

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

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

Когда вам хочется иметь больше контроля над глобальными переменными.

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

Тем не менее, в любой момент вы можете расширить это ограничение и позволить любое количество объектов-одиночек, поменяв код в одном месте (метод getInstance ).

Шаги реализации

  1. Добавьте в класс приватное статическое поле, которое будет содержать одиночный объект.
  2. Объявите статический создающий метод, который будет использоваться для получения одиночки.
  3. Добавьте «ленивую инициализацию» (создание объекта при первом вызове метода) в создающий метод одиночки.
  4. Сделайте конструктор класса приватным.
  5. В клиентском коде замените вызовы конструктора одиночка вызовами его создающего метода.

Преимущества и недостатки

  • Гарантирует наличие единственного экземпляра класса.
  • Предоставляет к нему глобальную точку доступа.
  • Реализует отложенную инициализацию объекта-одиночки.
  • Нарушает принцип единственной ответственности класса.
  • Маскирует плохой дизайн.
  • Проблемы мультипоточности.
  • Требует постоянного создания Mock-объектов при юнит-тестировании.

Отношения с другими паттернами

  • Фасад можно сделать Одиночкой, так как обычно нужен только один объект-фасад.
  • Паттерн Легковес может напоминать Одиночку, если для конкретной задачи у вас получилось свести количество объектов к одному. Но помните, что между паттернами есть два кардинальных отличия:
    1. В отличие от Одиночки, вы можете иметь множество объектов-легковесов.
    2. Объекты-легковесы должны быть неизменяемыми, тогда как объект-одиночка допускает изменение своего состояния.
  • Абстрактная фабрика, Строитель и Прототип могут быть реализованы при помощи Одиночки.

Примеры реализации паттерна

Не втыкай в транспорте

Лучше почитай нашу книгу о паттернах проектирования.

Теперь это удобно делать даже во время поездок в общественном транспорте.

Эта статья является частью нашей электронной книги Погружение в Паттерны Проектирования.

Refactoring.Guru

  • Премиум контент
    • Книга о паттернах
    • Курс по рефакторингу
    • Введение в рефакторинг
      • Чистый код
      • Технический долг
      • Когда рефакторить
      • Как рефакторить
      • Раздувальщики
        • Длинный метод
        • Большой класс
        • Одержимость элементарными типами
        • Длинный список параметров
        • Группы данных
        • Операторы switch
        • Временное поле
        • Отказ от наследства
        • Альтернативные классы с разными интерфейсами
        • Расходящиеся модификации
        • Стрельба дробью
        • Параллельные иерархии наследования
        • Комментарии
        • Дублирование кода
        • Ленивый класс
        • Класс данных
        • Мёртвый код
        • Теоретическая общность
        • Завистливые функции
        • Неуместная близость
        • Цепочка вызовов
        • Посредник
        • Неполнота библиотечного класса
        • Составление методов
          • Извлечение метода
          • Встраивание метода
          • Извлечение переменной
          • Встраивание переменной
          • Замена переменной вызовом метода
          • Расщепление переменной
          • Удаление присваиваний параметрам
          • Замена метода объектом методов
          • Замена алгоритма
          • Перемещение метода
          • Перемещение поля
          • Извлечение класса
          • Встраивание класса
          • Сокрытие делегирования
          • Удаление посредника
          • Введение внешнего метода
          • Введение локального расширения
          • Самоинкапсуляция поля
          • Замена простого поля объектом
          • Замена значения ссылкой
          • Замена ссылки значением
          • Замена поля-массива объектом
          • Дублирование видимых данных
          • Замена однонаправленной связи двунаправленной
          • Замена двунаправленной связи однонаправленной
          • Замена магического числа символьной константой
          • Инкапсуляция поля
          • Инкапсуляция коллекции
          • Замена кодирования типа классом
          • Замена кодирования типа подклассами
          • Замена кодирования типа состоянием/стратегией
          • Замена подкласса полями
          • Разбиение условного оператора
          • Объединение условных операторов
          • Объединение дублирующихся фрагментов в условных операторах
          • Удаление управляющего флага
          • Замена вложенных условных операторов граничным оператором
          • Замена условного оператора полиморфизмом
          • Введение Null-объекта
          • Введение проверки утверждения
          • Переименование метода
          • Добавление параметра
          • Удаление параметра
          • Разделение запроса и модификатора
          • Параметризация метода
          • Замена параметра набором специализированных методов
          • Передача всего объекта
          • Замена параметра вызовом метода
          • Замена параметров объектом
          • Удаление сеттера
          • Сокрытие метода
          • Замена конструктора фабричным методом
          • Замена кода ошибки исключением
          • Замена исключения проверкой условия
          • Подъём поля
          • Подъём метода
          • Подъём тела конструктора
          • Спуск метода
          • Спуск поля
          • Извлечение подкласса
          • Извлечение суперкласса
          • Извлечение интерфейса
          • Свёртывание иерархии
          • Создание шаблонного метода
          • Замена наследования делегированием
          • Замена делегирования наследованием

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *