Синглтон: как его использовать - PullRequest
288 голосов
/ 17 сентября 2008

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

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

Я вижу синглтоны как шаблон дизайна (хороший и плохой).

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

Итак, основные вопросы, на которые необходимо ответить:

  • Когда следует использовать синглтон
  • Как правильно реализовать Singleton

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


Так что качайте шарик:
Я подниму руку и скажу, что это то, что я использую, но, вероятно, у меня проблемы.
Мне нравится, как Скотт Майерс рассматривает эту тему в своих книгах "Эффективный C ++"

Хорошие ситуации для использования синглетонов (не много):

  • Каркасы логирования
  • Нитки для утилизации бассейнов
/*
 * C++ Singleton
 * Limitation: Single Threaded Design
 * See: http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf
 *      For problems associated with locking in multi threaded applications
 *
 * Limitation:
 * If you use this Singleton (A) within a destructor of another Singleton (B)
 * This Singleton (A) must be fully constructed before the constructor of (B)
 * is called.
 */
class MySingleton
{
    private:
        // Private Constructor
        MySingleton();
        // Stop the compiler generating methods of copy the object
        MySingleton(MySingleton const& copy);            // Not Implemented
        MySingleton& operator=(MySingleton const& copy); // Not Implemented

    public:
        static MySingleton& getInstance()
        {
            // The only instance
            // Guaranteed to be lazy initialized
            // Guaranteed that it will be destroyed correctly
            static MySingleton instance;
            return instance;
        }
};

OK. Давайте вместе возьмем критику и другие реализации.
: -)

Ответы [ 24 ]

165 голосов
/ 18 сентября 2008

Вы все не правы. Прочитайте вопрос. Ответ:

Используйте синглтон, если:

  • В системе должен быть один и только один объект типа

Не используйте Singleton, если:

  • Вы хотите сохранить память
  • Вы хотите попробовать что-то новое
  • Вы хотите показать, как много вы знаете
  • Потому что все остальные делают это (см. Программист культа грузов в Википедии)
  • В виджетах пользовательского интерфейса
  • Это должен быть кеш
  • В строках
  • В сессиях
  • Я могу идти весь день

Как создать лучший синглтон:

  • Чем меньше, тем лучше. Я минималист
  • Убедитесь, что это потокобезопасный
  • Убедитесь, что оно никогда не равно нулю
  • Убедитесь, что он создан только один раз
  • Ленивая или системная инициализация? До ваших требований
  • Иногда ОС или JVM создают для вас синглтоны (например, в Java каждое определение класса является одиночным)
  • Предоставьте деструктор или как-нибудь выясните, как распоряжаться ресурсами
  • Используйте мало памяти
70 голосов
/ 17 апреля 2009

Синглтоны дают вам возможность комбинировать две плохие черты в одном классе. Это неправильно почти во всех отношениях.

Синглтон дает вам:

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

Номер один прост. Глобалы вообще плохие. Мы никогда не должны делать объекты глобально доступными, если нам действительно это не нужно.

Номер два может звучать так, как будто это имеет смысл, но давайте подумаем об этом. Когда в последний раз вы ** случайно * создавали новый объект вместо ссылки на существующий? Поскольку это тег C ++, давайте использовать пример из этого языка. Вы часто случайно пишете

std::ostream os;
os << "hello world\n";

Когда вы намеревались написать

std::cout << "hello world\n";

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

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

Ограничение «возможен только один экземпляр» на самом деле не защищает нас от возможных ошибок. Но это действительно делает наш код очень сложным для рефакторинга и сопровождения. Потому что довольно часто мы узнаем позже , что нам нужно более одного экземпляра. У нас do имеется более одной базы данных, у нас do имеется более одного объекта конфигурации, нам нужно несколько регистраторов. Наши модульные тесты могут захотеть создавать и воссоздавать эти объекты каждый тест, чтобы взять общий пример.

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

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

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

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

35 голосов
/ 17 сентября 2008

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

1) Синглтоны предоставляют механизм глобального доступа к объекту. Хотя они могут быть несколько более поточно-ориентированными или более надежными в языках без четко определенного порядка инициализации, это использование по-прежнему является моральным эквивалентом глобальной переменной. Это глобальная переменная, одетая в какой-то неуклюжий синтаксис (скажем, foo :: get_instance () вместо g_foo), но она служит той же цели (единственный объект, доступный во всей программе) и имеет точно такие же недостатки.

2) Синглтоны предотвращают множественные экземпляры класса. Это редкость, IME, что такого рода функции должны быть включены в класс. Обычно это намного более контекстная вещь; Многие вещи, которые рассматриваются как «один-единственный-один», на самом деле просто-таки-могут быть только один. IMO более подходящее решение - просто создать только один экземпляр - пока вы не поймете, что вам нужно более одного экземпляра.

26 голосов
/ 17 сентября 2008

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

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

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

Это может усложнить отслеживание зависимостей . Когда все зависит от вашего синглтона, его сложнее изменить, разделить на два и т. Д. Обычно вы застряли с этим. Это также препятствует гибкости. Изучите некоторые рамки Dependency Injection , чтобы попытаться решить эту проблему.

12 голосов
/ 17 сентября 2008

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

В частности, Java использует синглеты в качестве замены глобальных переменных, поскольку все должно содержаться в классе. Наиболее близкими к глобальным переменным являются публичные статические переменные, которые могут использоваться, как если бы они были глобальными с import static

C ++ имеет глобальные переменные, но порядок вызова конструкторов глобальных переменных класса не определен. Таким образом, синглтон позволяет вам отложить создание глобальной переменной до тех пор, пока эта переменная не понадобится в первый раз.

Такие языки, как Python и Ruby, очень мало используют синглтоны, потому что вместо них можно использовать глобальные переменные внутри модуля.

Так когда же хорошо / плохо использовать синглтон? Почти точно, когда было бы хорошо / плохо использовать глобальную переменную.

6 голосов
/ 17 апреля 2009
  • Как правильно реализовать Singleton

Есть одна проблема, о которой я никогда не упоминал, с которой я столкнулся на предыдущей работе. У нас были синглтоны C ++, которые были разделены между DLL, и обычная механика обеспечения того, чтобы единственный экземпляр класса просто не работал. Проблема в том, что каждая DLL получает свой собственный набор статических переменных вместе с EXE. Если ваша функция get_instance встроенная или является частью статической библиотеки, каждая DLL будет иметь свою собственную копию «singleton».

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

6 голосов
/ 18 сентября 2008

Современный C ++ Design Александреску имеет поточно-ориентированный наследуемый универсальный синглтон.

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

5 голосов
/ 17 сентября 2008

Первый пример не является потокобезопасным - если два потока вызывают getInstance одновременно, то static будет PITA. Некоторая форма мьютекса поможет.

4 голосов
/ 25 сентября 2009

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

Некоторые полезные аспекты синглетонов:

  1. Ленивая или искренняя реализация
  2. удобно для объекта, который требует настройки и / или состояния

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

Наконец, фабрика поддается таким технологиям внедрения зависимостей, как Spring и т. Д.

3 голосов
/ 17 апреля 2009

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

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