Подводные камни Singleton почему самый известный шаблон проектирования нужно использовать с


Оглавление (нажмите, чтобы открыть):

Паттерн Singleton (одиночка,синглет)

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

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

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

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

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

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

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

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

UML-диаграмма классов паттерна Singleton

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

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

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

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

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

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

Singleton Мэйерса

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Одиночка (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

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

Singleton и абстрактный класс

Решил поюзать паттерн Singleton для общего развития, развития особого не получилось)). Несмотря на обилие описаний, так и не понял, дает ли он какое-нибудь преимущество по сравнению, скажем, с обычным абстрактным классом? Так у меня создалось впечатление, что возникает больше неудобств, а обычный абстрактный класс делает все то же самое. Может, я просто не умею его готовить?

3 ответа 3

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

А это немного разные вещи. Синглтон — это единственный экземпляр (инстанс) класса. А у абстрактного класса может быть больше одного экземпляра (как реализации дочерних классов).

UPD


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

Внесу ясность (скорее для себя, так как ответ на вопрос уже дан)

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

Мастер Йода рекомендует:  Лучшие библиотеки для работы с VK API на C++, Java, Python и других языках

Абстрактный класс — предок структуры классов, у котого есть наследники. НИКАКОГО отношения к приему программирования «Синглтон» напрямую не имеет. Это совершенно другая сущность и у данного класса совершенно другая цель — определить интерфейс для взаимодействия со всеми экземплярами потомков данного класса. НО. Никто не мешает спроектировать абстрактный класс с использованием паттерна «Синглтон». Например, определить абстрактный класс, который будет хранить ОДИН экземпляр подключения к базе данных И этот экземпляр может быть типа одного из потомков данного абстрактного класса — подключение через ОДБС, подключение к SQL, подключение к Ораклу — как варианты.

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

Опубликовано shwan в 19.12.2020 19.12.2020

Идея паттерна проектирования Одиночка (Singleton)

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

Существует три вида паттернов проектирования:

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

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

Архитектура паттерна Одиночка (Singleton)

На рисунке представлена схема структуры класса.

  • Singleton — уникальный статический экземпляр класса
  • getInstance() — метод получения экземпляра класса. Если экземпляр еще не создан, то создает новый.

Логика работы паттерна Singleton (Одиночка)

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

Реализация паттерна проектирования Singleton (Одиночка) на языке C#

Singleton.cs

Вызов класса. Попытаемся создать несколько экземпляров класса одиночки.

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

Шаблон Singleton (Одиночка) один из самых часто используемых шаблонов. Его можно встретить во множестве проектов и он относительно прост для обучения. Его обязательно нужно знать и уметь его использовать.

Статья написана на основе книги: Python: Master the Art of Design Patterns (Dusty Phillips, Chetan Giridhar, Sakis Kasampalis)

В этой статье будет кратко рассмотрены следующие темы:
• Как устроен Singleton
• Реальный пример паттерна Singleton
• Реализация шаблона Singleton в Python
• Шаблон Monostate

Как устроен Singleton

Шаблон Singleton предоставляет механизм создания одного и только один экземпляра объекта, и предоставление к нему глобальную точку доступа. Поэтому, Singletons обычно используются в таких случаях, как ведение журнала или операции с базой данных, диспетчера очереди печати и многих других, где существует необходимость иметь только один экземпляр, который доступен во всем приложении, чтобы избежать конфликтующих запросов на один и тот же ресурс. Например, мы можем захотеть использовать один объект базы данных для выполнения операций с БД для обеспечения согласованности данных или один объект класса ведения журнала для нескольких служб, чтобы последовательно выгружать сообщения журнала в определенный файл журнала.


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

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

Реализация классического синглтона в Python

Вот пример кода шаблона Singleton в Python v3.5. В этом примере мы делаем две основные вещи:

  1. Мы сделаем возможным создание только одного экземпляра класса Singleton.
  2. Если экземпляр существует, мы всегда будем использовать уже существующий объект.

Вывод выполнения кода должен быть примерно таким:

В этом фрагменте кода мы переопределяем метод __new__ (специальный метод Python для создания объектов), что бы управлять созданием объекта. Объект s создается с помощью метода __new__, но перед этим он проверяет, существует ли уже созданный объект. Метод hasattr (специальный метод Python, позволяющий определить, имеет ли объект определенное свойство), используется для проверки наличия у объекта cls свойства instance. При создание объекта s, объект просто создается. В случае создания объекта s1, hasattr() обнаруживает, что у объекта уже существует свойство instance, и, следовательно, s1 использует уже существующий экземпляр объекта (расположенный по адресу 0x10ba9db90).

Отложенный экземпляр в Singleton

Одним из вариантов использования шаблона Singleton является отложенная инициализация. Например, в случае импорта модулей мы можем автоматически создать объект, даже если он не нужен. Отложенное создание экземпляра гарантирует, что объект создается, только тогда, когда он действительно необходим.
В следующем примере кода, когда мы используем s = Singleton(), вызывается метод __init__, но при этом новый объект не будет создан. Фактическое создание объекта произойдет, когда мы используем Singleton.getInstance().

Singleton на уровне модуля

Все модули по умолчанию являются синглетонами из-за особенностей работы импорта в Python. Python работает следующим образом:

  1. Проверяет, был ли уже импортирован модуль.
  2. При импорте возвращает объект модуля. Если объекта не существует, то есть модуль не импортирован, он импортируется и создается его экземпляр.
  3. Когда модуль импортируется, он инициализируется. Но когда тот же модуль импортируется снова, он уже не инициализируется, что похоже на поведение Singleton, имеющим только один объект и возвращающим один и тот же объект.

Моностатический синглтон

В описание шаблона Singleton в книге Gang of Four говорится, что должен быть один и только один объект класса. Однако, согласно Алексу Мартелли, программисту обычно требуется, чтобы экземпляры имели одно и то же состояние. Он предлагает разработчикам больше беспокоиться о состоянии и поведении, а не об идентичности экземпляров. Поскольку концепция основана на том что бы все объекты имели одно и то же состояние, она также известна как шаблон Monostate.

Шаблон Monostate может быть реализован очень простым способом. В коде ниже мы присваиваем переменную __dict__ (специальную переменную Python) переменной класса __shared_state. Python использует __dict__ для хранения состояния каждого объекта класса. В следующем коде мы намеренно назначаем __shared_state всем созданным экземплярам. Поэтому, когда мы создаем два экземпляра, «b» и «b1», мы получаем два разных объекта. Однако состояния переменных b.__dict__ и b1.__dict__ одинаковы. Теперь, даже если переменная объекта x изменится в объекте b, изменение копируется в переменную __dict__, которая является общей для всех объектов, и b1 получит это изменение:

В результате должно получится что то типа такого:

Другой способ реализации класса Borg – это использование метода __new__. Как мы знаем, метод __new__ отвечает за создание экземпляра объекта:

Синглтоны и метаклассы

Начнем с краткого введения в метаклассы. Метакласс – это классы, экземпляры которых являются классами. С помощью метаклассов программисты получают возможность создавать классы своего собственного типа из предопределенных классов Python. Например, если у вас есть объект MyClass, вы можете создать метакласс MyKls, который переопределяет поведение MyClass так, как вам нужно.

Давайте разберемся с этим подробно.

Что было понятнее можно сказать что, метакласс это такая штука, которая создают объекты-классы. В Python все является объектом. Если мы пишем a = 5, тогда type(a) возвращает , что означает, что переменная a имеет тип int. Однако type(int) возвращает , что означает наличие метакласса, поскольку int является классом типа type.
Определение класса определяется его метаклассом, поэтому, когда мы хотим создать класс с помощью строки кода > • name: это название класса
base: это базовый класс
dict: это атрибуты класса

Теперь, если у класса есть предопределенный метакласс (по имени MetaKls), Python создает класс с помощью A = MetaKls(name, base, dict).

Рассмотрим пример реализации метакласса в Python 3.5:

В итоге должно отобразиться что типа такого:

Специальный метод Python __call__ вызывается, когда необходимо создать объект для уже существующего класса. В этом коде, когда мы создаем экземпляр класса int с помощью int(4,5), вызывается метод __call__ метакласса MyInt, что означает, что метакласс теперь управляет созданием объекта.
Что то похожее что мы рассматривали раньше в шаблоне Singleton. Поскольку метакласс имеет больший контроль над созданием классов и созданием объектов, его можно использовать для создания синглетонов. Для управления созданием и инициализацией класса в метаклассах переопределяют методы __new__ и __init__.

Реализация Singleton с метклассами может быть лучше объяснена с помощью следующего примера кода:

Первый пример использования Singleton

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

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

  • Согласованность операций в базе данных – одна операция не должна приводить к конфликтам с другими операциями
  • Использование памяти и ЦП должно быть оптимальным для обработки нескольких операций с базой данных.

В этом коде мы сделали следующее:

  1. Мы создали метакласс по имени MetaSingleton. Как мы объясняли в предыдущем разделе, специальный метод __call__ используется в метаклассе для создания Singleton.
  2. Класс Database создан с помощью метакласса MetaSingleton и является синглтон. Таким образом, когда создается экземпляр класса Database, он создает только один объект.
  3. Когда веб-приложение хочет выполнить определенные операции с БД, оно несколько раз создает экземпляр класса Database (в качестве примера, например в разных частях приложения), но создается только один объект. Поскольку существует только один объект, обращения к базе данных синхронизируются. Кроме того, такой подход позволяет ограничить использование системных ресурсов, и мы можем избежать ситуации нехватки памяти или ресурсов процессора.


Если предположить, что вместо одного веб-приложения у нас есть кластерная установка с несколькими веб-приложениями, но только с одной БД. То это не очень хорошая ситуация для Singletons, потому что с каждым добавлением нового веб-приложения создается новый Singleton и добавляется новый объект, который будет запрашивать базу данных. Это приводит к несинхронизированным операциям с базой данных и потребует больше ресурсов. В таких случаях будет лучше использовать пул соединений с базой данных, чем использование простого Singletons.

Второй пример использования Singleton

Давайте рассмотрим другой сценарий, в котором мы внедряем службы проверки работоспособности (например, Nagios) для нашей инфраструктуры. Мы создаем класс HealthCheck, который реализован как Singleton. Мы также будем поддерживать список серверов, для которых должна выполняться проверка работоспособности. Если сервер удален из этого списка, программное обеспечение для проверки работоспособности должно обнаружить его и удалить с серверов, настроенных для проверки.

В следующем коде объекты hc1 и hc2 являются экземплярами класса в Singleton. Серверы добавляются в инфраструктуру для проверки работоспособности с помощью метода addServer(). В начале выполняется, итерация проверки работоспособности для этих серверов. Затем метод changeServer() удаляет последний сервер и добавляет новый. А затем, когда снова запускается проверка во второй итерации то используется уже измененный список серверов.

Мастер Йода рекомендует:  Позиционирование изображений в Яндекс.Директ

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

В результате работы этого кода, должно отобразиться что то типа такого:

Недостатки шаблона Singleton

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

Резюме

В этой статье вы узнали о шаблоне проектирования Singleton, о том что он используется, когда нужно иметь только один объект. Мы также рассмотрели различные способы реализации Singletons в Python. Классическая реализация допускает несколько попыток создания экземпляров, но всегда возвращает один и тот же объект. Также обсудили паттерн Monostate, который является вариацией паттерна Singleton. Monostate позволяет создавать несколько объектов, которые имеют одно и то же состояние.
Мы рассмотрели приложение, где Singleton может применяться для согласования операций с базами данных в нескольких сервисах.
Наконец, мы также рассмотрели главный недостаток Singletons.

Для чего нужен singleton?

В общем и целом: синглтон как таковой не нужен вообще. Это бред придуманный программистами в пьяном угаре.

У него две задачи:
1) не потеряться в коде (иметь возможность в любом месте получить объект App::getInstance() — если вы его добропорядочно потеряли
2) из этого обьекта получать конфиги там разные, настройки, статусы и что угодно, ну в общем-то дальше вы с ним работаете как с обычным обьектом.

Тем же раскладом вы можете создать файл app.class.php где написать
Class App <>
return new App;

и когда подключаете файл в файле сборки сделать:

$app = require_once app.class.php;

Потом в этом $app лежит ваш «синглтон», который на самом деле просто экземпляр. Задача — не потерять его и следить в какой модуль вы его передали.

Как сказал бы любой яваскриптер пхп-шнику «ребят, мне бы ваши проблемы»

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

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

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

Подход многократно обличался антипаттерном за то, что «глобальное состояние программы — это зло», не смотря на то, что глобальное состояние продолжает оставаться в программе не смотря на удаление всех синглтонов из проекта.
Я видел проекты с более чем 50 синглтонами и очень тяжелыми проблемами их связи между собой. В синглтоны без разбора и по незнанке вытаскивали буквально все. Это яркий пример антиподхода применения абсолютно любого паттерна.
Важно понимать что никакой элемент проектирования не является антипаттерном, он приводит к проблемам только при неумелом использовании.

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

В качестве альтернативы синглтонам пропагандируются два других подхода: Registry / Service locator и Dependency injection.
К слову, все альтернативы столь же активно и все так же незаслуженно обзываются антипаттернами. 🙂

Теперь к примерам.
DirectX. Для работы с графикой тебе необходимо по одной инстанции интерфейсов IDirect3D и IDirect3DDevice. Эти две инстанции декларируют глобальное состояние программы. Инстанции всех буферов, текстур, шейдеров и поверхностей создаются с использованием этих инстанций. Разумным будет предоставить доступ к инстанциям DirectX через подход синглтона.
OpenGL старых добрых версий. Процедурный интерфейс OpenGL как бы намекает на отсутствие необходимости в глобальном состоянии. Но не тут то было. Для работы с OpenGL необходимо не просто создать контекст, но еще и помнить поток, в котором этот контекст связан с поверхностью вывода. В многопоточной среде контекстов может быть несколько для параллельной загрузки ресурсов. В этом случае помнить надо уже два потока и два контекста (минимум). Само собой, в синглтоне это глобальное состояние смотрится удобнее.
Sockets. Не важно какие. Когда твое приложение представляет собой MMO проект и у тебя гора подсистем, постоянно и обособленно общающихся с сервером, сетевое подключение разумно оформить в виде синглтона.
Assets/Resources — они бывают разные, кешируемые и нет, доступные из сети, с жесткого диска, из подсистемы пререндеринга. Опять же, я несколько раз видел боль и страдания от неоднородного контроля ресурсов без соответствующей подсистемы. А сама подсистема управления ресурсами всегда централизована и лучше всего реализуется именно на синглтоне.

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

Вопрос по design-patterns, java, singleton &#8211 Шаблон проектирования синглтона: подводные камни [закрыто]

В настоящее время я был очень заинтересован в этом «дизайн шаблона «, Однако я не уверен, что при использовании этой строгой реализации глобального состояния есть недостатки. Итак, когда вы думаете, чтобы не практиковать синглтон в приложении?

Вот хорошо читать синглтон, являющийся анти-паттерном.

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

Решение состоит в том, чтобыпросто создайте один экземпляр объекта и передать его своим пользователям через внедрение зависимостей. DI рамки, такие какGuiceоблегчить определение хорошего вида синглетонов (в Guice просто аннотируйте класс с помощью @Singleton). Был похожий Tech Talk под названиемДон»Ищи вещи! которые обсуждали DI больше.

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

@Nate, это звучит интересно. Не могли бы вы привести конкретный пример?

@Pacerieroracle.com/technetwork/articles/java/singleton-1577166.html Отдельные JRE, использующие отдельные экземпляры-одиночки, — это ожидаемый случай — проблема в том, что один JRE может иметь несколько загрузчиков классов и, следовательно, несколько экземпляров-одиночек.

Существуют ли авторитетные источники для вашей заявки? Почему бы неt отдельные экземпляры JRE используют отдельные экземпляры синглетонов?


как правило, плохая идея, если вы проводите модульное тестирование, и, как правило, плохая идея не проводить модульное тестирование (или BDD, или приемочное тестирование).

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

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

Идеальный метод — использовать контейнер IoC / DI (Spring, Guice и т. Д.) Для запроса объектов. Эти контейнеры часто имеют способы отображения объектов в видеОдиночки но у них также есть способы изменить это поведение в зависимости от ситуации (то есть модульное тестирование по сравнению с кодом вашего домена).

Конечно, все зависит от размера вашей проблемы. Если ты’взломав вместе испытательный стенд 4-го класса, чтобы попробовать что-то, а затем использовать Singleton. Тем не менее, как только этот проект возродится и станет более масштабным и сложным, начнется рефакторинг Singleton.

Я чувствую запах тролля .

Так вы имеете в виду, что одиночныеникогда пригодны ли для использования в реальных проектах (а обнаружение синглтона в реальном проекте автоматически приравнивается к запаху кода независимо от варианта использования)?

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

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

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

Паттерн Singleton в C#

IT блог — Паттерн Singleton в C#

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

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

В этой статье мы рассмотрим пример, когда параметры не требуются. Как правило, требование Singleton состоит в том, чтобы экземпляр создавался лениво (lazy) — т.е. экземпляр не создается до тех пор, пока он не понадобится.

Существуют различные способы реализации Singleton в C#. Я приведу некоторые из них здесь в обратном порядке элегантности, начиная с наиболее часто встречающихся. Все эти реализации имеют четыре общие характеристики:

Единый конструктор, который является закрытым (модификатор private) и без параметров. Это предотвратит создание других экземпляров (что было бы нарушением паттерна).

Мастер Йода рекомендует:  Произвольные типы записей для бокового меню

Класс должен быть запечатаннным (модификатор sealed). Строго говоря это является необязательным условием, исходя из вышеизложенных концепций Singleton, но позволяет JIT-компилятору улучшить оптимизацию.

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

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

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

Первая версия — не потокобезопасная

Вышеуказанная реализация не является потокобезопасной. Два разных потока могли бы пройти условие if (source == null), создав два экземпляра, что нарушает принцип Singleton. Обратите внимание, что на самом деле экземпляр, возможно, уже был создан до того, как условие будет пройдено, но модель памяти не гарантирует, что новое значение экземпляра будет видно другим потокам, если не будут приняты соответствующие блокировки.

Вторая версия — простая защита от потоков

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

Обратите внимание, что вместо блокировки типа typeof(Singleton), как это делают в некоторых реализациях Singleton, я блокирую значение статической переменной, которая является закрытой (private) внутри класса. Блокировка объектов, к которым могут обращаться другие классы, ухудшает производительность и вносит риск взаимоблокировки. Я использую простой стиль — по возможности нужно блокировать объекты, специально созданные с целью блокировки. Обычно такие объекты должны быть использовать модификатор private.

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

Как вы можете заметить, это действительно очень простая реализация — но почему она является потокобезопасной и как в данном случае работает ленивая загрузка? Статические конструкторы в C# вызываются для выполнения только тогда, когда создается экземпляр класса или ссылается на статический член класса, и выполняются только один раз для AppDomain. Эта версия будет быстрее предыдущей, т.к. отсутствует дополнительная проверка на значение null. Однако в данной реализации есть несколько недочетов:

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

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

Четвертая версия — полностью ленивая загрузка

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

Пятый вариант — с использованием типа Lazy

Если вы используете версию .NET Framework 4 (или выше), вы можете использовать тип System.Lazy , чтобы реализовать ленивую загрузку очень просто. Все, что вам нужно сделать, это передать делегат конструктору, который вызывает конструктор Singleton, которому передается лямбда-выражение:

Это довольная простая реализация, которая хорошо работает. Она также позволяет вам проверить, был ли экземпляр создан с использованием свойства IsValueCreated, если вам это нужно.

Одиночка


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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Зачем мне нужен шаблон проектирования Singleton?

Я пытаюсь выучить шаблон проектирования, но действительно очень сложно понять основную идею OOD. Я создал свое программное обеспечение с классическим методом. С другой стороны, я хочу изучить OOD. Зачем мне нужен синглтон и другие? я написал простую программу: одна из них классическая (мой стиль), другая — синглтон. Пожалуйста, научите меня, зачем мне синглтон. мой метод лучше и понятнее, чем он 🙂

10 ответов

Шаблоны проектирования не являются методологией проектирования или разработки. Это словарь : они помогают вводить имена в повторяющиеся шаблоны, встречающиеся в архитектурах программного обеспечения. Исходя из моего опыта, разработка программного обеспечения для шаблонов FROM заканчивается сложным программным обеспечением с множеством одноцелевых классов, что увеличивает количество вещей, которые должен иметь в виду программист (а разработка программного обеспечения достаточно сложна, чтобы избежать наполнения вашего мозга шумом). ).

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

Например, предположим, что ваша проблема связана с наборами данных. В какой-то момент вы создали свои структуры данных и алгоритмы. Теперь вам (или кому-то еще) необходимо получить доступ к своим данным на более высоком уровне. Это типичный случай, когда могут применяться шаблоны Iterator или Visitor. Так было сделано в C ++ STL, где все классы коллекции понимают итераторы. Однако вам не нужно заранее думать о шаблонах, которые вы можете или не можете применять здесь или там, всегда есть время для рефакторинга, как только шаблоны или потребности были определены.

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

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

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

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