Синглтон: как его использовать


291

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

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

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

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

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

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

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


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

Хорошие ситуации, чтобы использовать Singletons (не много):

  • Каркасы логирования
  • Бассейны для утилизации ниток
/*
 * 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;
        }
};

ХОРОШО. Давайте получим некоторую критику и другие реализации вместе.
:-)


36
Что если позже вы решите, что хотите использовать несколько регистраторов? Или несколько потоков? Если вам нужен только один регистратор, создайте только один экземпляр и сделайте его глобальным. Синглтоны хороши только тогда, когда вам абсолютно НУЖНО быть только когда-либо, и им НУЖНО быть глобальным, ИМХО.

3
Кто сказал, что у фреймворка может быть только 1 экземпляр логгера. Один сингелтон, представляющий Framework. Framwork может затем дать вам конкретные регистраторы.
Мартин Йорк,

Да. Я бы не использовал синглтонг как пул потоков. Просто выбрасываю идеи, чтобы зажечь ответы.
Мартин Йорк,

@ Дэн Синглтон, который реализует шаблон стратегии. Поведение абстрагируется от синглтона. Синглтон является единой точкой входа. Не имейте двух регистраторов, есть один регистратор, который может решить, как войти. Вы не можете выводить только в один журнал одновременно, нет необходимости иметь два.
Ли Лувьер

5
Xaade: что делать, если вы хотите войти в два файла? Или в базу данных? Или сетевой сокет? Или графический виджет? Дело в том, что не добавляйте искусственные ограничения - в этом нет необходимости. Как часто вы когда-нибудь создавали два цикла for вместо одного? Если вам нужен только один регистратор, создайте только один.

Ответы:


182

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

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

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

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

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

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

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

14
На самом деле, я думаю, вы тоже не совсем правы. Я бы перефразировал это так: «Если вам нужен один и только один объект типа в системе, и вам нужен глобальный доступ к нему». Акцент на потребности - мой. Не делайте это, если это удобно, только если вы ДОЛЖЕН иметь это.

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

4
Закрыто для модификации, открыто для расширения. Проблема в том, что вы не можете расширить синглтон на дуотон или триплетон. Он застрял как синглтон.
Ли Лувьер

2
@ enzom83: Синглтон с заглавной буквы S включает код для обеспечения его единства. Если вам нужен только один экземпляр, вы можете потерять этот код и просто создать один экземпляр самостоятельно ... что даст вам экономию памяти для одного экземпляра, а также экономию благодаря исключению кода, обеспечивающего единство действий, - что также означает, что нельзя жертвовать возможность создания второго экземпляра, если когда-либо ваши требования изменятся.
Цао

4
«Если вам нужен один и только один объект типа в системе» - «... и никогда не хотите издеваться над этим объектом в модульном тесте».
Cygon

72

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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


3
или вы можете сделать его глобальным и получить только один из недостатков синглтона. В случае синглтона вы одновременно ограничиваетесь одним экземпляром этой базы данных. Зачем это делать? Или вы можете посмотреть, почему у вас так много зависимостей, что список экземпляров становится «очень длинным». Они все необходимы? Должны ли некоторые из них быть делегированы другим компонентам? Возможно, некоторые из них могли бы быть упакованы вместе в структуру, чтобы мы могли передать их как один аргумент. Есть много решений, все они лучше, чем одиночные.
Джалф

6
Да, синглтон может быть оправдан там. Но я думаю, что вы только что доказали мою точку зрения, что это необходимо только в довольно экзотических случаях. Большая часть программного обеспечения не имеет отношения к оборудованию для вспашки снега. Но я все еще не убежден. Я согласен, что в вашем действительном заявлении вы хотите только один из них. Но как насчет ваших юнит-тестов? Каждый из них должен работать изолированно, поэтому в идеале он должен создать свой собственный SpreaderController - что сложно сделать с одиночкой. Наконец, зачем вашим коллегам создавать несколько экземпляров? Это реалистичный сценарий защиты от?
Джалф

3
И момент, который вы упустили, состоит в том, что, хотя ваши последние два примера, возможно, оправдывают ограничение «только один экземпляр», они не делают ничего, чтобы оправдать «глобально доступный». С какой стати вся кодовая база должна иметь доступ к административному блоку вашего телефонного коммутатора? Смысл в синглтоне - дать вам обе черты. Если вам нужен только один или другой, вы не должны использовать синглтон.
Джалф

2
@ jalf - Моя цель состояла в том, чтобы просто дать вам пример того, как синглтон полезен в дикой природе, поскольку вы не могли себе этого представить; Я полагаю, вы не видите много раз, чтобы применить это ваше текущее направление работы. Я переключился на программирование снегоочистителей из бизнес-приложений исключительно потому, что это позволило бы мне использовать Singleton. :) Я / я согласен с вашей предпосылкой, что есть лучшие способы сделать это, вы дали мне много думать. Спасибо за обсуждение!
Дж. Полфер

2
Использовать «шаблон» синглтона ( AHEM! ), Чтобы люди не запускали больше экземпляров, просто глупо, просто для того, чтобы люди не сделали этого. Когда в моей маленькой функции есть локальная переменная foo1 типа Foo, и мне нужна только одна переменная, я не беспокоюсь о том, что кто-то собирается создать вторую переменную Foo foo2 и использовать ее вместо исходной.
Томас Эдинг

36

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

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

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


6
Согласовано. Согласно некоторым, в реальном мире два несправедливости могут исправить. Но в программировании смешивание двух плохих идей не приводит к хорошей.
Джалф

27

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

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

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

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


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

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

1
+1 за указание на то, какую боль испытывают синглтоны.
DevSolar

1
@jalf Не разрешать кому-либо создавать более одного экземпляра класса - это не плохо. Если в действительности существует только один экземпляр экземпляра класса, который обеспечивает соблюдение этого требования. Если кто-то позже решит, что ему нужно создать еще один экземпляр, он должен рефакторинг его, потому что он никогда не должен был быть одиночным.
Уильям

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

13

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

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

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

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

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


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

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

Мне нравится слой косвенности в этом ответе: «когда было бы хорошо / плохо использовать глобал». И DevSolar, и Lee Louviere получают ценность, с которой они согласны, хотя во время ответа не было известно, кто будет комментировать.
Праксеолит

6

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

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


6
  • Как правильно реализовать Singleton?

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

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


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

@ Ева, да что-то в этом роде. Я не создавал проблему, я просто должен был как-то заставить ее работать.
Марк Рэнсом

5

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


Да, это отмечено в комментариях выше: * Ограничение: однопоточный дизайн * См .: aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf * Для проблем, связанных с блокировкой в ​​многопоточных приложениях
Мартин Йорк

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

даже в с ++ 11 или позже?
hg_git

5

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

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

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

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

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


3

Singletons удобны, когда у вас много кода, запускаемого при инициализации и объекте. Например, когда вы используете iBatis при настройке объекта персистентности, он должен прочитать все конфиги, проанализировать карты, убедиться, что все правильно, и т. Д., Прежде чем перейти к вашему коду.

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


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

3

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

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


Сами лесорубы не должны быть одиночками. Общая «широковещательная» система обмена сообщениями должна быть. Сами регистраторы являются подписчиками широковещательных сообщений.
CashCow

Пулы потоков также не должны быть синглетонами. Общий вопрос: хотите ли вы когда-нибудь больше одного из них? Да. Когда я в последний раз использовал их, у нас было 3 разных пула потоков в одном приложении.
CashCow

3

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


3

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



2

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

#include<iostream>
#include<mutex>

using namespace std;
std::mutex mtx;

class MySingleton{
private:
    static MySingleton * singletonInstance;
    MySingleton();
    ~MySingleton();
public:
    static MySingleton* GetInstance();
    MySingleton(const MySingleton&) = delete;
    const MySingleton& operator=(const MySingleton&) = delete;
    MySingleton(MySingleton&& other) noexcept = delete;
    MySingleton& operator=(MySingleton&& other) noexcept = delete;
};

MySingleton* MySingleton::singletonInstance = nullptr;
MySingleton::MySingleton(){ };
MySingleton::~MySingleton(){
    delete singletonInstance;
};

MySingleton* MySingleton::GetInstance(){
    if (singletonInstance == NULL){
        std::lock_guard<std::mutex> lock(mtx);
        if (singletonInstance == NULL)
            singletonInstance = new MySingleton();
    }
    return singletonInstance;
}

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


2
Это определенно не лучше. 1: Вы не определяете семантику владения с помощью указателя. Вы никогда не должны использовать указатели в C ++, если вы не готовы управлять ими. 2: Ваше использование двойной проверки блокировки устарело, и есть гораздо лучшие современные способы сделать это. 3: Ваши комментарии на разрушение наивны. Восстановление памяти - это не точка деструктора, это вопрос очистки. Предложения для лучшей версии: посмотрите на вопрос. Представленная там версия уже намного лучше.
Мартин Йорк,

1

Я использую Singletons в качестве собеседования.

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


45
Жесткие и быстрые правила найма заставят вас упустить широкий круг потенциальных сотрудников.
Карл

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

3
Для книги рекордов - мой ответ был насмешливым. В моем реальном процессе собеседования я пытаюсь оценить, нужно ли нам обучать кого-то на C ++, и насколько это будет сложно. Некоторые из моих любимых кандидатов - это люди, которые не знают C ++ изнутри и снаружи, но я смог с ними пообщаться об этом.
Мэтт Круикшанк

4
Вниз голосование. Исходя из моего личного опыта - программист может не назвать другие шаблоны, кроме Singleton, но это не значит, что он использует Singletons. Лично я использовал синглтоны в своем коде ДО того, как я когда-либо слышал о них (я назвал их «умнее глобалов» - я знал, что такое глобал). Когда я узнал о них, когда я узнал об их плюсах и минусах - я перестал их использовать. Внезапно, когда я остановился, модульное тестирование стало для меня гораздо интереснее ... Это сделало меня хуже программиста? Пффф ...
Паулюс

3
Я также принижаю за бессмысленный вопрос "назовите некоторые шаблоны проектирования". Проектирование - это понимание того, как применять шаблоны проектирования, а не просто умение разыгрывать их имена. Хорошо, это может не оправдать понижение голоса, но этот ответ - тролль-иш.
CashCow

0

Anti-Usage:

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


Понижено голосование по двум причинам: 1. Синглтон может внутренне использовать полиморфные экземпляры (например, глобальный Logger использует полиморфные стратегии нацеливания) 2. Для одноэлементного имени может быть typedef, поэтому код фактически зависит от typedef.
toptime gamedev

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

0

Я думаю, что это самая надежная версия для C #:

using System;
using System.Collections;
using System.Threading;

namespace DoFactory.GangOfFour.Singleton.RealWorld
{

  // MainApp test application

  class MainApp
  {
    static void Main()
    {
      LoadBalancer b1 = LoadBalancer.GetLoadBalancer();
      LoadBalancer b2 = LoadBalancer.GetLoadBalancer();
      LoadBalancer b3 = LoadBalancer.GetLoadBalancer();
      LoadBalancer b4 = LoadBalancer.GetLoadBalancer();

      // Same instance?
      if (b1 == b2 && b2 == b3 && b3 == b4)
      {
        Console.WriteLine("Same instance\n");
      }

      // All are the same instance -- use b1 arbitrarily
      // Load balance 15 server requests
      for (int i = 0; i < 15; i++)
      {
        Console.WriteLine(b1.Server);
      }

      // Wait for user
      Console.Read();    
    }
  }

  // "Singleton"

  class LoadBalancer
  {
    private static LoadBalancer instance;
    private ArrayList servers = new ArrayList();

    private Random random = new Random();

    // Lock synchronization object
    private static object syncLock = new object();

    // Constructor (protected)
    protected LoadBalancer()
    {
      // List of available servers
      servers.Add("ServerI");
      servers.Add("ServerII");
      servers.Add("ServerIII");
      servers.Add("ServerIV");
      servers.Add("ServerV");
    }

    public static LoadBalancer GetLoadBalancer()
    {
      // Support multithreaded applications through
      // 'Double checked locking' pattern which (once
      // the instance exists) avoids locking each
      // time the method is invoked
      if (instance == null)
      {
        lock (syncLock)
        {
          if (instance == null)
          {
            instance = new LoadBalancer();
          }
        }
      }

      return instance;
    }

    // Simple, but effective random load balancer

    public string Server
    {
      get
      {
        int r = random.Next(servers.Count);
        return servers[r].ToString();
      }
    }
  }
}

Вот оптимизированная версия .NET :

using System;
using System.Collections;

namespace DoFactory.GangOfFour.Singleton.NETOptimized
{

  // MainApp test application

  class MainApp
  {

    static void Main()
    {
      LoadBalancer b1 = LoadBalancer.GetLoadBalancer();
      LoadBalancer b2 = LoadBalancer.GetLoadBalancer();
      LoadBalancer b3 = LoadBalancer.GetLoadBalancer();
      LoadBalancer b4 = LoadBalancer.GetLoadBalancer();

      // Confirm these are the same instance
      if (b1 == b2 && b2 == b3 && b3 == b4)
      {
        Console.WriteLine("Same instance\n");
      }

      // All are the same instance -- use b1 arbitrarily
      // Load balance 15 requests for a server
      for (int i = 0; i < 15; i++)
      {
        Console.WriteLine(b1.Server);
      }

      // Wait for user
      Console.Read();    
    }
  }

  // Singleton

  sealed class LoadBalancer
  {
    // Static members are lazily initialized.
    // .NET guarantees thread safety for static initialization
    private static readonly LoadBalancer instance =
      new LoadBalancer();

    private ArrayList servers = new ArrayList();
    private Random random = new Random();

    // Note: constructor is private.
    private LoadBalancer()
    {
      // List of available servers
      servers.Add("ServerI");
      servers.Add("ServerII");
      servers.Add("ServerIII");
      servers.Add("ServerIV");
      servers.Add("ServerV");
    }

    public static LoadBalancer GetLoadBalancer()
    {
      return instance;
    }

    // Simple, but effective load balancer
    public string Server
    {
      get
      {
        int r = random.Next(servers.Count);
        return servers[r].ToString();
      }
    }
  }
}

Вы можете найти этот шаблон на dotfactory.com .


3
Вы могли бы вырезать части, которые не имеют отношения к Singletons, чтобы облегчить чтение кода.
Мартин Йорк

Кроме того, ваша первая версия не является поточно-ориентированной из-за возможного переупорядочения чтения / записи. Видеть stackoverflow.com/questions/9666/...
Thomas Danecker

5
Э-э ... неправильный язык? Вопрос довольно явно помечен C ++ .
DevSolar

0

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

Singleton - это реализация глобально доступного объекта (GAO отныне), хотя не все GAO являются синглетонами.

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

Ленивая загрузка / отложенная оценка - это другое понятие, и синглтон, как правило, также реализует это. У него много собственных проблем, в частности, безопасности потоков и проблем, если он терпит неудачу с исключениями, так что то, что казалось хорошей идеей в то время, оказывается не таким уж великим. (Немного похоже на реализацию COW в строках).

Имея это в виду, GOA можно инициализировать так:

namespace {

T1 * pt1 = NULL;
T2 * pt2 = NULL;
T3 * pt3 = NULL;
T4 * pt4 = NULL;

}

int main( int argc, char* argv[])
{
   T1 t1(args1);
   T2 t2(args2);
   T3 t3(args3);
   T4 t4(args4);

   pt1 = &t1;
   pt2 = &t2;
   pt3 = &t3;
   pt4 = &t4;

   dostuff();

}

T1& getT1()
{
   return *pt1;
}

T2& getT2()
{
   return *pt2;
}

T3& getT3()
{
  return *pt3;
}

T4& getT4()
{
  return *pt4;
}

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

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


0

Я до сих пор не понимаю, почему синглтон должен быть глобальным.

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

Я не понимаю, почему эта функциональность была бы плохой.


Я не понимаю, почему вы думаете, что это должно быть глобальным.
Мартин Йорк,

Согласно этой ветке, все говорили, что синглтон должен быть глобальным
Захарий Краус

1
Нет. Поток указывает, что сингелтон имеет глобальное состояние. Не то чтобы это глобальная переменная. Решение, которое вы предлагаете, имеет глобальное состояние. Решение, которое вы предлагаете, также использует глобальную переменную; статический член класса является объектом «Static Storage Duration», глобальная переменная является объектом «Static Storage Duration». Таким образом, эти два понятия в основном одно и то же с немного различной семантикой / областями действия.
Мартин Йорк,

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

1
Примечание: вы пропустили мой намеренно ни один не заявленный бит. Ваш дизайн с использованием статического «частного» члена не так плох, как синглтон. Потому что это не вводит "глобальное изменяемое состояние". Но это тоже не синглтон. Singleton - это класс, который спроектирован так, что может существовать только один экземпляр объекта. То, что вы предлагаете, это единое общее состояние для всех объектов класса. Другая концепция.
Мартин Йорк,

0

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

Я уверен, что есть и другие решения, но я считаю это очень полезным и простым в реализации.


0

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

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


-1

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

Изменить - конечно, они должны быть только для чтения!


Но это напрашивается на вопрос; почему язык пользователя и путь к файлу справки должны быть случаи , метод вообще ?
DrPizza

2
У нас есть глобалы для этого. Там нет необходимости делать их синглтоны
Jalf

Глобальные переменные - тогда как их сериализовать из реестра / базы данных? Класс Гобала - тогда как вы гарантируете, что есть только один из них?
Мартин Беккет

@mgb: вы сериализуете их, считывая значения из реестра / базы данных и сохраняя их в глобальных переменных (это, вероятно, следует делать в верхней части вашей основной функции). Вы гарантируете, что есть только один объект класса, создавая только один объект класса ... действительно ... это трудно 'grep -rn "new \ + global_class_name".' ? действительно?
Паулюс

7
@mgb: с какой стати я должен гарантировать, что есть только один? Мне просто нужно знать, что один экземпляр всегда представляет текущие настройки. но нет причин, по которым мне нельзя разрешать иметь другие объекты настроек по всему месту. Например, «настройки, которые пользователь в настоящее время определяет, но еще не применил». Или один для «конфигурации, которую пользователь сохранил ранее, чтобы он мог вернуться к ним позже». Или по одному для каждого из ваших юнит-тестов.
Джалф

-1

Еще одна реализация

class Singleton
{
public:
    static Singleton& Instance()
    {
        // lazy initialize
        if (instance_ == NULL) instance_ = new Singleton();

        return *instance_;
    }

private:
    Singleton() {};

    static Singleton *instance_;
};

3
Это действительно ужасно. Смотрите: stackoverflow.com/questions/1008019/c-singleton-design-pattern/… для лучшей ленивой инициализации и, что более важно, гарантированного детерминированного уничтожения.
Мартин Йорк,

Если вы собираетесь использовать указатели, Instance()следует возвращать указатель, а не ссылку. Внутри вашего .cppфайла инициализации экземпляра пустое значение: Singleton* Singleton::instance_ = nullptr;. И Instance()должны быть реализованы в виде: if (instance_ == nullptr) instance_ = new Singleton(); return instance_;.
Деннис
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.