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

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

  • автор:

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Одиночка

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

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

Проблема

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

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

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

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

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

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

Но есть и другой нюанс. Неплохо бы хранить в одном месте и код, который решает проблему №1, а также иметь к нему простой и доступный интерфейс.

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

Решение

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

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

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

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

Структура

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

Одиночка определяет статический метод getInstance , который возвращает единственный экземпляр своего класса.

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

Псевдокод

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

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

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

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

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

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

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

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

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

Добавьте в класс приватное статическое поле, которое будет содержать одиночный объект.

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

Добавьте «ленивую инициализацию» (создание объекта при первом вызове метода) в создающий метод одиночки.

Сделайте конструктор класса приватным.

В клиентском коде замените вызовы конструктора одиночка вызовами его создающего метода.

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

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

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

Фасад можно сделать Одиночкой, так как обычно нужен только один объект-фасад.

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

  1. В отличие от Одиночки, вы можете иметь множество объектов-легковесов.
  2. Объекты-легковесы должны быть неизменяемыми, тогда как объект-одиночка допускает изменение своего состояния.

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

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

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

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

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

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

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

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

Проблема

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

1. Гарантирует наличие единственного экземпляра класса. Чаще всего это полезно для доступа к какому-то общему ресурсу, например, базе данных.

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

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

Глобальный доступ к одному объекту Клиенты могут не подозревать, что работают с одним и тем же объектом

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

Но есть и другой нюанс. Неплохо бы хранить в одном месте и код, который решает проблему №1, а также иметь к нему простой и доступный интерфейс.

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

Решение

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

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

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

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

Структура

  1. Одиночка определяет статический метод getInstance , который возвращает единственный экземпляр своего класса.

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

Псевдокод

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

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

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

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

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

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

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

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

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

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

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

Преимущества

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

Недостатки

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

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

Фасад можно сделать Одиночкой, так как обычно нужен только один объект-фасад.

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

.NET Design Patterns (GoF). Singleton (Одиночка)

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

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

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

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

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

Чтобы решить эту проблему перепишем класс синглотона следующим образом. Данное решение называется double check locking и является потокобезопастным:

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

Реализация singleton через readonly поле:

Этот вариант тоже thread-safe и основан на любопытном свойстве полей readonly — они инициализируются не сразу, а при первом вызове. Стоить заметить создание static конструктора, если его не написать, то readonly поля инициализируются вместе со static полями.

И последняя реализация с помощью Lazy<T> класса в С#.

Lazy<T> обеспечивает отложенную реализацию объекта lazy до первого обращения к Instance.

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

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