Некоторые считают, что синглтон-паттерн всегда является анти-паттерном. Как вы думаете?
Некоторые считают, что синглтон-паттерн всегда является анти-паттерном. Как вы думаете?
Ответы:
Две основные критики синглетонов делятся на два лагеря из того, что я наблюдал:
В результате обоих из них, общий подход состоит в том, чтобы использовать создание широкого объекта контейнера для хранения одного экземпляра этих классов, и только объект контейнера изменяет эти типы классов, в то время как многим другим классам может быть предоставлен доступ к ним для использования из контейнерный объект.
Я согласен, что это анти-шаблон. Почему? Потому что он позволяет вашему коду лгать о его зависимостях, и вы не можете доверять другим программистам, чтобы они не вводили изменяемое состояние в ваши ранее неизменяемые синглтоны.
Класс может иметь конструктор, который принимает только строку, так что вы думаете, что он создается отдельно и не имеет побочных эффектов. Однако, молча, он взаимодействует с каким-то общедоступным, глобально доступным одноэлементным объектом, так что всякий раз, когда вы создаете экземпляр класса, он содержит разные данные. Это большая проблема не только для пользователей вашего API, но и для тестируемости кода. Чтобы правильно выполнить модульное тестирование кода, вам необходимо микроуправлять и знать глобальное состояние в синглтоне, чтобы получить согласованные результаты тестирования.
Шаблон Singleton - это просто лениво инициализированная глобальная переменная. Глобальные переменные обычно и справедливо считаются злыми, поскольку они допускают жуткие действия на расстоянии между, казалось бы, не связанными частями программы. Тем не менее, IMHO, нет ничего плохого в глобальных переменных, которые устанавливаются один раз, из одного места, как часть процедуры инициализации программы (например, путем чтения файла конфигурации или аргументов командной строки) и впоследствии обрабатываются как константы. Такое использование глобальных переменных отличается только по буквам, а не по духу, от именованной константы, объявленной во время компиляции.
Точно так же мое мнение о синглетонах заключается в том, что они плохие тогда и только тогда, когда они используются для передачи изменчивого состояния между внешне не связанными частями программы. Если они не содержат изменяемого состояния или если изменяемое состояние, которое они содержат, полностью инкапсулировано, так что пользователям объекта не нужно знать об этом даже в многопоточной среде, то с ними все в порядке.
Я видел довольно много синглетонов в мире PHP. Я не помню ни одного случая использования, где бы я нашел образец оправданным. Но я думаю, что я получил представление о мотивации, почему люди использовали это.
Единственный экземпляр .
«Используйте один экземпляр класса C во всем приложении».
Это разумное требование, например, для «подключения к базе данных по умолчанию». Это не означает, что вы никогда не создадите второе соединение с БД, это просто означает, что вы обычно работаете с соединением по умолчанию.
Единый экземпляр .
«Не позволяйте создавать экземпляр класса C более одного раза (для каждого процесса, для каждого запроса и т. Д.)».
Это актуально только в том случае, если создание экземпляра класса будет иметь побочные эффекты, которые конфликтуют с другими экземплярами.
Часто таких конфликтов можно избежать, изменив дизайн компонента, например, исключив побочные эффекты из конструктора класса. Или они могут быть решены другими способами. Но могут все еще быть некоторые законные случаи использования.
Вам также следует подумать о том, действительно ли «только одно» требование означает «одно на процесс». Например, для параллелизма ресурсов требование скорее «один на всю систему, между процессами», а не «один на процесс». А для других вещей это скорее для «контекста приложения», и у вас просто бывает один контекст приложения на процесс.
В противном случае нет необходимости применять это предположение. Применение этого также означает, что вы не можете создать отдельный экземпляр для модульных тестов.
Глобальный доступ.
Это допустимо только в том случае, если у вас нет надлежащей инфраструктуры для передачи объектов туда, где они используются. Это может означать, что ваш фреймворк или окружающая среда - отстой, но это не в ваших силах исправить это.
Цена - это тесная связь, скрытые зависимости и все плохое в глобальном состоянии. Но вы, вероятно, уже страдаете этим.
Ленивый экземпляр.
Это не обязательная часть синглтона, но кажется наиболее популярным способом их реализации. Но в то время как ленивое создание - хорошая вещь, вам не нужен синглтон для достижения этой цели.
Типичная реализация - это класс с закрытым конструктором, статическая переменная экземпляра и статический метод getInstance () с отложенной реализацией.
В дополнение к проблемам, упомянутым выше, это связано с принципом единой ответственности , потому что класс действительно контролирует свою собственную реализацию и жизненный цикл , в дополнение к другим обязанностям, которые класс уже имеет.
Во многих случаях вы можете достичь того же результата без единого и глобального состояния. Вместо этого вы должны использовать внедрение зависимостей, и вы можете рассмотреть контейнер внедрения зависимостей .
Тем не менее, существуют случаи использования, когда у вас остаются следующие действующие требования:
Итак, вот что вы можете сделать в этом случае:
Создайте класс C, который вы хотите создать, с помощью открытого конструктора.
Создайте отдельный класс S со статической переменной экземпляра и статическим методом S :: getInstance () с отложенной реализацией, который будет использовать класс C для экземпляра.
Устраните все побочные эффекты из конструктора C. Вместо этого поместите эти побочные эффекты в метод S :: getInstance ().
Если у вас есть более одного класса с вышеуказанными требованиями, вы можете рассмотреть возможность управления экземплярами класса с помощью небольшого локального сервисного контейнера и использовать статический экземпляр только для контейнера. Итак, S :: getContainer () предоставит вам контейнер службы с отложенным созданием экземпляра, а вы получите другие объекты из контейнера.
Старайтесь не вызывать статические getInstance (), где вы можете. Вместо этого используйте внедрение зависимостей, когда это возможно. Особенно, если вы используете контейнерный подход с несколькими объектами, которые зависят друг от друга, то ни один из них не должен вызывать S :: getContainer ().
При желании создайте интерфейс, который реализует класс C, и используйте его для документирования возвращаемого значения S :: getInstance ().
(Мы все еще называем это синглтоном? Я оставляю это в разделе комментариев ..)
Выгоды:
Вы можете создать отдельный экземпляр C для модульного тестирования, не касаясь какого-либо глобального состояния.
Управление экземплярами отделено от самого класса -> разделение интересов, принцип единой ответственности.
Было бы довольно просто позволить S :: getInstance () использовать другой класс для экземпляра или даже динамически определить, какой класс использовать.
Лично я буду использовать синглтоны, когда мне понадобится 1, 2 или 3 или какое-то ограниченное количество объектов для конкретного класса. Или я хочу сообщить пользователю моего класса, что я не хочу, чтобы несколько экземпляров моего класса создавались для его правильной работы.
Также я буду использовать его только тогда, когда мне нужно будет использовать его почти везде в моем коде, и я не хочу передавать объект в качестве параметра каждому классу или функции, которые в этом нуждаются.
Кроме того, я буду использовать только синглтон, если он не нарушает ссылочную прозрачность другой функции. Это означает, что при некотором входе он всегда будет давать один и тот же результат. Т.е. я не использую это для глобального состояния. Если, возможно, это глобальное состояние не инициализируется один раз и никогда не изменяется.
Что касается того, когда им не пользоваться, см. Выше 3 и измените их на противоположные.