Когда подходит Синглтон? [закрыто]

Ответы:

32

Две основные критики синглетонов делятся на два лагеря из того, что я наблюдал:

  1. Менее способные программисты злоупотребляют и используют синглтоны, поэтому все становится одноэлементным, и вы видите код, заваленный ссылками Class :: get_instance (). Вообще говоря, есть только один или два ресурса (например, соединение с базой данных), которые подходят для использования шаблона Singleton.
  2. Синглтоны - это, по сути, статические классы, использующие один или несколько статических методов и свойств. Все статические вещи представляют реальные, ощутимые проблемы, когда вы пытаетесь выполнить модульное тестирование, потому что они представляют тупики в вашем коде, которые нельзя смоделировать или заглушить. В результате, когда вы тестируете класс, который использует Singleton (или любой другой статический метод или класс), вы тестируете не только этот класс, но и статический метод или класс.

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

Ной Гудрич
источник
6
Я просто хотел бы сказать, что вы можете высмеивать статические методы в Java с помощью JMockit ( code.google.com/p/jmockit ). Это очень удобный инструмент.
Майкл К
3
Некоторые мысли: контейнер - это Синглтон, так что вы не избавились от Синглетонов. Кроме того, если вы пишете библиотеку с открытым API, вы не можете заставить пользователя вашего API использовать ваш контейнер. Если вам нужен глобальный менеджер ресурсов в вашей библиотеке (например, для защиты доступа к одному физическому ресурсу), вам нужно использовать Singleton.
Скотт Уитлок
2
@ Скотт Уитлок, почему он должен быть синглтоном? Только потому, что есть только один экземпляр, это не так. Любая библиотека IoC справится с такой зависимостью ...
MattDavey
Это решение предложило его также известный как Toolbox
cregox
41

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

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

Магнус Вольффелт
источник
Хотелось бы, чтобы я проголосовал не раз!
MattDavey
34

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

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

dsimcha
источник
3
Другими словами, вы против любого использования базы данных в программе.
Кендалл Хельмштеттер Гелнер
Есть известное видео о технологиях Google, которое повторяет ваше мнение youtube.com/watch?v=-FRm3VPhseI
Мартин Йорк,
2
@KendallHelmstetterGelner: есть разница между состоянием и вторичным хранилищем. Вряд ли вы можете хранить целую БД в памяти.
Мартин Йорк,
7

Почему люди используют это?

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

  1. Единственный экземпляр .

    «Используйте один экземпляр класса C во всем приложении».

    Это разумное требование, например, для «подключения к базе данных по умолчанию». Это не означает, что вы никогда не создадите второе соединение с БД, это просто означает, что вы обычно работаете с соединением по умолчанию.

  2. Единый экземпляр .

    «Не позволяйте создавать экземпляр класса C более одного раза (для каждого процесса, для каждого запроса и т. Д.)».

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

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

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

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

  3. Глобальный доступ.

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

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

  4. Ленивый экземпляр.

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

Типичная реализация

Типичная реализация - это класс с закрытым конструктором, статическая переменная экземпляра и статический метод getInstance () с отложенной реализацией.

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

Заключение

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

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

  • Один экземпляр (но не один экземпляр)
  • Глобальный доступ (потому что вы работаете с рамками бронзового века)
  • Ленивый экземпляр (потому что это просто приятно иметь)

Итак, вот что вы можете сделать в этом случае:

  • Создайте класс C, который вы хотите создать, с помощью открытого конструктора.

  • Создайте отдельный класс S со статической переменной экземпляра и статическим методом S :: getInstance () с отложенной реализацией, который будет использовать класс C для экземпляра.

  • Устраните все побочные эффекты из конструктора C. Вместо этого поместите эти побочные эффекты в метод S :: getInstance ().

  • Если у вас есть более одного класса с вышеуказанными требованиями, вы можете рассмотреть возможность управления экземплярами класса с помощью небольшого локального сервисного контейнера и использовать статический экземпляр только для контейнера. Итак, S :: getContainer () предоставит вам контейнер службы с отложенным созданием экземпляра, а вы получите другие объекты из контейнера.

  • Старайтесь не вызывать статические getInstance (), где вы можете. Вместо этого используйте внедрение зависимостей, когда это возможно. Особенно, если вы используете контейнерный подход с несколькими объектами, которые зависят друг от друга, то ни один из них не должен вызывать S :: getContainer ().

  • При желании создайте интерфейс, который реализует класс C, и используйте его для документирования возвращаемого значения S :: getInstance ().

(Мы все еще называем это синглтоном? Я оставляю это в разделе комментариев ..)

Выгоды:

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

  • Управление экземплярами отделено от самого класса -> разделение интересов, принцип единой ответственности.

  • Было бы довольно просто позволить S :: getInstance () использовать другой класс для экземпляра или даже динамически определить, какой класс использовать.

Дон Кихот
источник
1

Лично я буду использовать синглтоны, когда мне понадобится 1, 2 или 3 или какое-то ограниченное количество объектов для конкретного класса. Или я хочу сообщить пользователю моего класса, что я не хочу, чтобы несколько экземпляров моего класса создавались для его правильной работы.

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

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

Что касается того, когда им не пользоваться, см. Выше 3 и измените их на противоположные.

Брайан Р. Бонди
источник
3
-0 но я почти -1. Я предпочел бы передать его везде в своем коде, вместо того, чтобы использовать одноэлементное НО, если я действительно ленив, я могу это сделать. Вместо этого я бы использовал глобальные переменные, если я могу или статические члены. глобальные переменные действительно намного предпочтительнее (для меня), чем одиночные. Глобалы не лгут