На шаблонах проектирования: когда я должен использовать синглтон?

448

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

Дайте мне сценарии, кроме старого доброго логгера, где имеет смысл использовать синглтон.

Setori
источник
4
Поскольку я изучаю эрланг, я предпочитаю такой подход, а именно неизменность и передачу сообщений.
Setori
209
Что не является конструктивным в этом вопросе? Я вижу конструктивный ответ ниже.
mk12
3
Структура внедрения зависимостей - это очень сложный синглтон, который выдает объект….
Ян Рингроз
1
Синглтон может использоваться в качестве управляющего объекта между экземплярами других объектов, поэтому должен существовать только один экземпляр синглтона, где все остальные экземпляры должны взаимодействовать через экземпляр синглтона.
Левент Дивилиоглу
У меня есть побочный вопрос: любая реализация Singleton также может быть реализована с использованием «статического» класса (с методом «factory» / «init») - без фактического создания экземпляра класса (можно сказать, что статический класс является своего рода реализация Singleton, но ...) - почему следует использовать фактический Singleton (единственный экземпляр класса, который гарантирует его единство) вместо статического класса? Единственная причина, по которой я могу придумать, это, возможно, «семантика», но даже в этом смысле сценарии использования Singleton на самом деле не требуют отношения «класс-> экземпляр» по определению ... так ... почему?
Ювал А.

Ответы:

358

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

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

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

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

Читайте больше в Синглтоне, я люблю тебя, но ты меня опускаешь.

CodingWithoutComments
источник
3
@ArneMertz Я думаю, это один.
Нападение
1
Почему вы не можете просто использовать глобальный объект? Почему это должен быть синглтон?
Чистка
1
Я думаю, что статический метод для утилиты регистрации?
Skynet
1
Одиночки лучше всего подходят, когда вам нужно управлять ресурсами. Например, соединения Http. Вы не хотите устанавливать 1 миллион HTTP-клиентов одному клиенту, это безумно расточительно и медленно. Таким образом, синглтон с http-клиентом, подключенным к пулу, будет намного быстрее и удобнее для ресурсов.
Cogman
3
Я знаю, что это старый вопрос, и информация в этом ответе отличная. Однако мне трудно понять, почему это принятый ответ, когда ОП четко указал: «Дайте мне сценарии, кроме старого доброго регистратора, где имеет смысл использовать синглтон».
Франциско К.
124

Кандидат-одиночка должен соответствовать трем требованиям:

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

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

Например, спулер принтера вряд ли будет вызываться из более чем одного места (меню «Печать»), поэтому вы можете использовать мьютексы для решения проблемы одновременного доступа.

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

metao
источник
3
Я не согласен с пунктом 2. Пункт 3 на самом деле не является причиной (просто потому, что вы можете, это не значит, что вы должны), и 1 - это хорошая точка, но я все еще не вижу смысла в этом. Допустим, общий ресурс - это дисковод или кэш базы данных. Вы можете добавить другой диск или иметь кэш базы данных, ориентированный на другое (например, кэш для специализированной таблицы для одного потока, а другой - более общего назначения).
15
Я думаю, что вы пропустили слово «кандидат». Кандидат-одиночка должен соответствовать трем требованиям; только то, что что-то соответствует требованиям, не означает, что это должен быть Singleton. Могут быть и другие факторы дизайна :)
metao
45

Чтение файлов конфигурации, которые следует читать только во время запуска, и их инкапсуляция в Singleton.

Пол Кроаркин
источник
8
Похоже на Properties.Settings.Default.NET.
Ник Бедфорд
9
@Paul, «Лагерь без синглов» будет утверждать, что объект конфигурации должен просто передаваться в функции, которые в нем нуждаются, вместо того, чтобы делать его глобально доступным (он же синглтон).
Pacerier
2
Не согласен. Если конфигурацию перенести в базу данных, все испорчено. Если путь к конфигурации зависит от чего-либо за пределами этого синглтона, эти вещи также должны быть статичными.
rr-
3
@PaulCroarkin Можете ли вы расширить это и объяснить, как это выгодно?
AlexG
1
@ rr- если конфигурация перемещается в базу данных, она все равно может быть инкапсулирована в объект конфигурации, который будет передан в функции, которые в ней нуждаются. (PS Я не в лагере "без единого").
Уилл Шеппард
36

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

Или соединение с базой данных или файловый менеджер и т. Д.

Винсент Рамдани
источник
30
Я слышал этот пример диспетчера очереди печати, и я думаю, что он немного отстойный. Кто сказал, что я не могу иметь более одного спулера? Какого черта это спулер принтера в любом случае? Что если у меня есть принтеры разных типов, которые не могут конфликтовать или использовать разные драйверы?
1800 ИНФОРМАЦИЯ
6
Это просто пример ... для любой ситуации, которую кто-либо использует в качестве примера, вы сможете найти альтернативный дизайн, который делает этот пример бесполезным. Предположим, что спулер управляет одним ресурсом, который используется несколькими компонентами. Оно работает.
Винсент Рамдани
2
Это классический пример «Банды четырех». Я думаю, что ответ с реальным опробованным вариантом использования был бы более полезным. Я имею в виду ситуацию, когда вы действительно почувствовали, что Синглтон - лучшее решение.
Андрей Ваджна II
Общий ресурс, по моему мнению, является слишком широким примером. Как бы вы проверили, что объекты, использующие диспетчер очереди печати, работают правильно перед лицом неправильно функционирующего диспетчера очереди, если вы не можете внедрить неправильно функционирующую реализацию «диспетчера очереди печати»? хотя краткий и неинформативный принятый ответ является гораздо более безопасным подходом в моей книге
Rune FS
Какого черта это спулер принтера?
RayLoveless
23

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

Мартин Беккет
источник
4
Пользовательский язык может быть только однозначным, если предположить, что только один пользователь может использовать систему.
Самуэль Ослунд
... и что один пользователь говорит только на одном языке.
спектры
17

Управление подключением (или пулом подключений) к базе данных.

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

Федерико А. Рампони
источник
2
Разве генератор соединений с базой данных не будет примером Фабрики?
Кен
3
@ Кен, ты бы хотел, чтобы этот завод был синглтоном почти во всех случаях.
Крис Марисик
2
@Federico, «Лагерь без синглов» скажет, что эти соединения с базой данных должны просто передаваться в функции, которые в них нуждаются, вместо того, чтобы делать их глобально доступными (иначе говоря, синглтон).
Pacerier
3
Вам не нужен синглтон для этого. Это может быть введено.
Нестор Ледон
11

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

Также подумайте о ситуации, когда у вас есть приложение с множеством окон / потоков / и т. Д., Но для которого требуется одна точка связи. Однажды я использовал один для управления заданиями, которые я хотел, чтобы мое приложение запускалось. Синглтон отвечал за сериализацию работ и отображение их статуса в любой другой части программы, которая была заинтересована. В этом сценарии вы можете рассматривать одиночный объект как своего рода «серверный» класс, работающий внутри вашего приложения ... HTH

Дейв Маркл
источник
3
Регистраторы чаще всего являются синглетонами, поэтому нет необходимости передавать объекты регистрации. Любая достойная реализация потока журнала гарантирует невозможность одновременной записи, независимо от того, является ли это Singleton или нет.
Метао
10

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

При использовании Singletons вы должны убедиться, что вы случайно не скрываете зависимости. В идеале синглеты (как и большинство статических переменных в приложении) должны быть установлены во время выполнения кода инициализации приложения (static void Main () для исполняемых файлов C #, static void main () для исполняемых файлов java) и затем переданы в все другие классы, которые создаются, которые требуют этого. Это помогает вам поддерживать тестируемость.

Адам Несс
источник
8

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

daalbert
источник
6

Практический пример синглтона можно найти в Test :: Builder , классе, который поддерживает практически все современные модули тестирования Perl. Синглтон Test :: Builder хранит и обрабатывает состояние и историю процесса тестирования (исторические результаты теста, подсчитывает количество выполненных тестов), а также такие вещи, как, например, результаты теста. Все это необходимо для координации нескольких модулей тестирования, написанных разными авторами, для совместной работы в одном сценарии тестирования.

История Test :: Builder's singleton является образовательной. Вызов new()всегда дает вам один и тот же объект. Во-первых, все данные были сохранены как переменные класса, и в самом объекте ничего не было. Это работало, пока я не хотел протестировать Test :: Builder сам с собой. Затем мне понадобилось два объекта Test :: Builder, один из которых был настроен как фиктивный, для захвата и тестирования его поведения и вывода, а другой - для реального тестового объекта. В этот момент Test :: Builder был преобразован в реальный объект. Объект-одиночка хранился как данные класса и new()всегда возвращал его. create()был добавлен, чтобы сделать свежий объект и включить тестирование.

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

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

Schwern
источник
5

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

Дин Дж
источник
2
Почему бы вам не просто загрузить данные один раз и передать необходимый объект конфигурации?
Lagweezle
что с прохождением ??? Если бы мне нужно было обойти каждый
нужный
@Enerccio Если у вас есть объекты, основанные на 20 других объектах без инкапсуляции, у вас уже есть серьезные проблемы с дизайном.
спектры
@spectras ли я? Если я реализую графический интерфейс, мне понадобятся: хранилище, локализация, данные сеанса, данные приложения, родительский виджет, данные клиента, менеджер разрешений и, возможно, больше. Конечно, вы можете объединить некоторые, но почему? Лично я использую Spring и аспекты, чтобы просто автоматически связать все эти зависимости в класс виджета, и это разделяет все.
Enerccio
Если у вас есть такое большое состояние, вы можете рассмотреть возможность реализации фасада, давая представление о соответствующих аспектах для конкретного контекста. Почему? Потому что это позволило бы создать чистый дизайн без использования синглтоновых или 29-аргументных конструкторов-антипаттернов. На самом деле сам факт, что ваш графический интерфейс обращается ко всем этим вещам, кричит «нарушение принципа единой ответственности».
спектры
3

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

Один конкретный пример, который я видел, - это поисковый индекс Lucene Search Writer.

Майк
источник
1
И все же, IndexWriter - это не синглтон ...
Марк
3

Вы можете использовать Singleton при реализации шаблона State (как показано в книге GoF). Это связано с тем, что конкретные классы State не имеют собственного состояния и выполняют свои действия в контексте контекстного класса.

Вы также можете сделать Abstract Factory синглтоном.

Эмиль Кормье
источник
Это тот случай, с которым я сейчас имею дело в проекте. Я использовал шаблон состояния, чтобы удалить повторяющийся условный код из методов контекста. У государства нет собственных переменных экземпляра. Тем не менее, я нахожусь на заборе в отношении того, должен ли я сделать их одиночными. Каждый раз, когда состояние переключается, создается новый экземпляр. Это кажется расточительным, потому что экземпляр не может отличаться от другого (потому что нет переменных экземпляра). Я пытаюсь понять, почему я не должен использовать это.
kiwicomb123
1
@ kiwicomb123 Попытайтесь привлечь к setState()ответственности за принятие решения о создании государства. Это помогает, если ваш язык программирования поддерживает шаблоны или шаблоны. Вместо Singleton вы можете использовать шаблон Monostate , в котором создание экземпляра объекта состояния заканчивается повторным использованием одного и того же объекта глобального / статического состояния. Синтаксис для изменения состояния может остаться неизменным, так как ваши пользователи не должны знать, что созданное состояние является Monostate.
Эмиль Кормье
Итак, в моих состояниях я мог бы просто сделать все методы статичными, поэтому, когда создается новый экземпляр, он не имеет одинаковых издержек? Я немного сбит с толку, мне нужно прочитать о моностате.
kiwicomb123
@ kiwicomb123 Нет, Monostate не предназначен для того, чтобы все члены были статичными. Лучше, чтобы вы прочитали об этом, затем проверьте SO для связанных вопросов и ответов.
Эмиль Кормье
Я чувствую, что это должно иметь больше голосов. Абстрактная фабрика достаточно распространена, и поскольку фабрики не имеют состояния, стабильны и не могут быть реализованы статическими методами (в Java), которые не переопределяются, использование singleton должно быть нормальным.
DPM
3

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

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

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

$gb->config['hostname']

или все языковые значения в массиве, такие как:

$gb->lang['ENTER_USER']

В конце выполнения кода для страницы вы получите, скажем, зрелый код:

$template

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

Большим преимуществом этого является то, что вы можете выполнять ЛЮБУЮ постобработку, что вам нравится. Вы можете передать все языковые значения в google translate или другую службу перевода и получить их обратно, а также заменить их на свои места, например, переведенные. или, вы можете заменить в структуре страницы, или, строки содержимого, как вы хотите.

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

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

В этом случае вы берете зависимость от инфраструктуры, чтобы упростить использование библиотеки и избежать ненужной сложности.

smaclell
источник
0

Я использую его для объекта, инкапсулирующего параметры командной строки при работе с подключаемыми модулями. Основная программа не знает, какие параметры командной строки используются для загружаемых модулей (и даже не всегда знает, какие модули загружаются). например, основные нагрузки A, которые сами по себе не нуждаются ни в каких параметрах (поэтому, почему он должен брать дополнительный указатель / ссылку / что-либо, я не уверен - выглядит как загрязнение), затем загружают модули X, Y и Z. Два из них, скажем, X и Z, нужны (или принимают) параметры, поэтому они перезванивают в синглтон командной строки, чтобы сообщить ему, какие параметры принимать, и во время выполнения они перезванивают, чтобы узнать, действительно ли пользователь указал какой-либо из них.

Во многих отношениях синглтон для обработки параметров CGI будет работать аналогично, если вы используете только один процесс на запрос (другие методы mod_ * не делают этого, поэтому было бы плохо - таким образом, аргумент, который говорит, что вы не должны ' t использовать синглтоны в мире mod_cgi на случай, если вы портируете на mod_perl или любой другой мир).

Tanktalus
источник
-1

Пример с кодом, возможно.

Здесь ConcreteRegistry - это одиночная игра в покер, которая позволяет поведению по всему дереву пакетов получать доступ к нескольким основным интерфейсам игры (т. Е. К фасадам модели, вида, контроллера, среды и т. Д.):

http://www.edmundkirwan.com/servlet/fractal/cs1/frac-cs40.html

Издание


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

1 - комментарий к первому ответу:

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

2 - Я стараюсь не создавать синглтон вручную. Я просто создаю простой объект с конструкторами, которые позволяют мне вводить коллабораторов в объект. Если бы мне нужен был синглтон, я бы использовал инфраструктуру инъекций зависимостей (Spring.NET, Unity для .NET, Spring для Java) или некоторые другие.

bloparod
источник
11
Вы должны прокомментировать ответы непосредственно, нажав на ссылку внизу ответа; так легче читать. Кроме того, ответ, который вы видели сверху, вероятно, не первый. Ответы переупорядочиваются все время.
Росс
зачем вам нужна регистрация модульных тестов?
Enerccio
Существует огромная разница между «статическим классом Logger» и статическим экземпляром Logger . Шаблон синглтона не говорит «сделайте ваш класс статичным», он говорит, чтобы сделать доступ к экземпляру объекта статическим. Например, ILogger logger = Logger.SingleInstance();если этот метод является статическим и возвращает статически сохраненный экземпляр ILogger. Вы использовали пример «структуры внедрения зависимостей». Почти все DI-контейнеры являются синглетонами; их конфигурации определены статически и в конечном итоге доступны из / сохранены в едином интерфейсе поставщика услуг.
Джон Дэвис,