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

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

Проблема

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

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

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

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


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

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

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

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

Решение

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

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

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

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

Структура



    Одиночка определяет статический метод 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).

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

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

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

22.10.2015
23:12

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

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

Пример реализация паттерна Singleton на C++

Технически реализовать объект-Синглтон и использовать его в приложении довольно просто (что подкупает):

#include class MySingleton { public: // Функция-член для доступа к единственному экземпляру static MySingleton* getInstance() { static MySingleton instance; return &instance; } // Наполняем полезным функционалом, как и любой другой класс void test() { std::cout << "Singleton test" << std::endl; } private: // Объявляем конструктор закрытым, чтобы нельзя было // создавать экземпляры класса извне MySingleton() { } }; int main() { // Используем Синглтон MySingleton::getInstance()->test(); // А это работать не будет, поскольку конструктор - закрытый // MySingleton singleton; return 0; }

При желании объект-Синглтон можно адаптировать к многопоточной среде выполнения с помощью мьютексов.

Реклама

Всегда думайте перед созданием Синглтона

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

Рассмотрим пример. Может показаться хорошей идеей создать класс для управления настройками приложения в виде Синглтона. Тогда все компоненты приложения смогут видеть необходимые опции и легко их использовать. С одной стороны, идея кажется довольно неплохой. Конфигурация приложения действительно может быть представлена уникальной сущностью. А свободный доступ к Синглтону упростит использование конфигурации.

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

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

Хотя есть и вполне безобидные применения Синглтонов. Например, при реализации другого паттерна: Абстрактная Фабрика.

Также использование Синглтона оправдано для представления физически уникальных ресурсов компьютера. Например, систему слежения за подключением/отключением USB-устройств уместно реализовать в виде Синглтона.

Начнём с главного: его код синглтона не потокобезопасен ! Разбор:

If(instance != null) return instance; // (*) Monitor.Enter(s_lock); Singleton temp = new Singleton(); Interlocked.Exchange(ref instance, temp); Monitor.Exit(s_lock); return instance;

Пусть поток 1 начинает выполнять приведённый код. instance в этот момент есть null , так что первая проверка проходит, и выполнение доходит до строки (*).

Пусть в этот момент произойдёт переключение контекста (это ведь возможно, правильно?), и начинает выполняться второй поток. Он точно так же проверяет instance , проходит начальную проверку, пробегает через (*), получает lock , создаёт экземпляр синглтона, записывает его в instance , отпускает lock и выходит. Второй поток получает ссылку на instance только что созданного синглтона.

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

Катастрофа.

Теперь по мелочам. Разработчики.NET не просто так сделали lock(obj) синонимом Monitor.Enter(obj, ref lockTaken) , а не просто Monitor.Enter(obj) . Вариант с Monitor.Enter(obj) работает неправильно в случае, если возможны исключения. Поэтому замена lock на явный Monitor.Enter - ухудшение.

Ещё большее ухудшение - отказ от try/catch. Без базара, без try/catch быстрее, только код получается неправильный. (Впрочем, если правильный код нам не нужен, без lock "а было бы ещё быстрее.) Если по каким-то причинам конструктор Singleton "а выбросит исключение (а это может быть не только деление на 0, но и TypeLoadException , например), то s_lock останется залоченным навсегда - deadlock.

Конструктор может быть только статичным. Это особенность компилятора - если конструткор не статичен, то тип будет помечен как beforefieldinit и readonly создадутся одновременно со static-ами.

Это фактически неверно, автор судя по всему просто не понял, что именно написал Jon. У синглтона есть нестатический конструктор, просто явный статический конструктор должен присутствовать . Между «статический конструктор должен присутствовать» и «конструктор может быть только статичным» огромная разница.

Правильная, остроумная, каноническая реализация синглтона с вложенным классом удостоилась лишь замечания «Недостатки у него те же самые, что у любого другого кода, который использует nested-классы». Мне неизвестны недостатки вызванные одним лишь наличием вложенных классов. Если мне кто-либо сообщит, готов изменить своё мнение.

Наконец, самое концептуально верное решение с Lazy критикуется за то, что оно «не работает с потоками», а используют структуры языка, которые «обманывают интерпретатор». Здесь неверно вообще всё, кроме союзов и предлогов. Начнём с того, что C# - компилируемый язык, интерпретатора C# не существует. Затем, никакого обмана нет: Lazy работает строго как обещает его документация. То, что его поведение отличается от поведения других классов - не обман и не трюк. При желании то же самое можно заимплементировать самому вручную.

Затем, правильным при реализации синглтона является не многопоточность или прочие технические мелочи, а простой инвариант: как бы синглтон не использовался, всегда гарантировано наличие не более одного экземпляра синглтона на AppDomain. Именно это гарантируется классом Lazy . Используются ли при этом специальные механизмы для поддержки многопоточности или свойства языка, никому не интересно. Главное - чтобы семантика единственного экземпляра была выдержана. Именно это гарантирует класс Lazy , абстрагируя нас от деталей реализации. И именно поэтому такая имплементация - самая правильная.

Вывод: не все статьи на Хабре одинаково полезны.

Class Singleton { private static $PrS="init private"; public static $PuS="init public"; public static function PrS($A=false) { if($A!==false) self::$PrS=$A; else return self::$PrS; } public static function PuS($A=false) { if($A!==false) self::$PuS=$A; else return self::$PuS; } } echo Singleton::PrS(); echo "\n"; echo Singleton::PuS(); // выведет init private // init public echo "\n -- \n"; $D = new Singleton(); echo $D->PrS(); // также выведет init private echo "\n"; // init public echo $D->PuS(); echo "\n -- \n SET them all!"; // А вот здесь Singleton::PrS("changed private"); // меняем переменные класса Singleton::PuS("changed public"); // используя статическую ссылку echo "\n"; // и попробуем проверить их из "созданного" класса (хотя это просто ссылка копия) echo $D->PrS(); // разумеется, выведет: changed private echo "\n"; echo $D->PuS(); // changed public

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

Слово static для функции говорит о том, что в глобальной таблице при компиляции ей уже выделен адрес - жестко выделен. Так же и со статическими переменными - их адрес также статичен. А НЕстатические переменные (классы) не существуют в адресном пространстве, пока их не определят (оператором new). Обращаться некуда. Для статических адрес уже есть - и к нему (к переменной) можно обратиться всегда - someStaticClass::value

Хотите использовать статический класс для работы с БД - заведите внутри приватную статическую переменную DB_handler. Надо работать с несколькими соединениями (несколько БД) - заведите еще по необходимости. Можно даже соорудить нечто статического массива. Почему нет? Появится необходимость слишком извернуться - перепишите класс. Т.е. копии статических классов вообще не различаются (при изготовлении их оператором new), пока в них не появится хотя бы одна НЕстатическая переменная. Правда, и после этого различаться они будут только этой НЕстатической переменной. Правда, при этом, управлять этой переменной уже получится уже только из изготовленного класса.

Public static function getInstance() { if (is_null(self::$instance)) { self::$instance = new Singleton; } return self::$instance; }

Вот этот кусок кода как раз и возвращает копию ссылки на адрес статического класса Singleton. Это то же самое, что написать Singleton::(и там что-то)

Вот об этом и был вопрос - "ЗАЧЕМ?". Ответ простой - да, НЕЗАЧЕМ:)

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

И вот я например, тоже не вижу разницы между сложным Singleton наворотом и простым Singleton::doAction(). Вообще, статические классы (со статическими переменными) чрезвыйчано удобны еще и тем, что они предоставляют как бы "глобальные" переменные для любой области видимости. И хэндлер для БД тому яркий пример.

Расскажу сегодня про паттерн проектирования Singleton (одиночка). Цель: создать класс, у которого будет только ОДИН объект. Это значит, что сколько бы раз к нему не обращались, возвращаться будет один и тот же объект, который был создан первый раз. Это удобная вещь и необходимая во многих местах, не зря ее внедряют во фреймворки. Применение:
  • Например необходимо подключить базу данных в проект и класс, который будет отвечать за соединение с ней. Один раз создается соединение и нет нужны создавать его снова и снова
  • Application settings - класс отвечающий за настройки отружения, которые нужны для приложения: хост и порт базы данных и т.д. Они создаются один раз и используются всё время работы приложения.
  • есть еще множество примеров, о которых я не сказал, поэтому пишите в комментариях свои варианты! =)
После этого вступления, как я понимаю можно показать уже пример этого класса: (Хотя я уверен, что каждый из нас сможет придумать реализацию этого) Вот самый простой пример, когда мы ставим приватным конструктор, т.е. нельзя создавать явно объект. И есть статический метод getInstance() , который предоставляет объект. public class Singleton { private static Singleton instance; private Singleton () { } public static Singleton getInstance () { if (instance == null) { instance = new Singleton () ; } return instance; } } Есть проблемы с многопоточностью и тогда можно поставить метод getInstance() маркер synchronized: public class Singleton { private static Singleton instance; private Singleton () { } public static synchronized Singleton getInstance () { if (instance == null) { instance = new Singleton () ; } return instance; } } В конце, как обычно, хочу сказать, что если вы думаете иначе или нашли у меня ошибку - пишите в комментариях! Мы все обсудим, с удовольствием:) Если Вам понравилась статья, пишите "+" и я буду это знать. Это для меня важно:) P.S. Добавляю еще реализации: По мнению Joshua Bloch ’а это лучший способ реализации шаблона Enum Singleton public enum Singleton { INSTANCE; } Double Checked Locking & volatile public class Singleton { private static volatile Singleton instance; public static Singleton getInstance () { Singleton localInstance = instance; if (localInstance == null) { synchronized (Singleton. class ) { localInstance = instance; if (localInstance == null) { instance = localInstance = new Singleton () ; } } } return localInstance; } } И еще On Demand Holder idiom: public class Singleton { public static class SingletonHolder { public static final Singleton HOLDER_INSTANCE = new Singleton () ; } public static Singleton getInstance () { return SingletonHolder. HOLDER_INSTANCE; } } + Ленивая инициализация + Высокая производительность - Невозможно использовать для не статических полей класса Будут вопросы/предложения - пишите в комментарии!