Comments 25
Имхо использование enum для синглетон - это костыль и анти-паттерн, хотя работает отлично.
Также мне нравится концепция Spring, где класс отвечает за то, что он должен делать, а не то как он инстанцируется. Singleton или нет - это вынесено: Spring bean scopes.
Ох не соврать бы, вроде enum Singleton были "нормальными" в java 1.4 или 1.5. И я сейчас даже не могу припомнить почему. В 2024 году сам шаблон Singleton основательно потускнел, ну или по крайней мере его смысл поменялся (говорим спасибо облакам, кластерам, докеру). Как верно написал @n43jl сейчас мы мыслим категориями scope (контекст). Контекст, а в 99% случаях это Spring Context, сам определяет кого, сколько и в каких случаях инстанцировать. Даже зная что скоуп по умолчанию в спринге Singleton, при проектировании приложений нам необходимо закладывать множество инстансов приложения запущенных параллельно.
С моей колокольни Singleton был очень популярен 15-20 лет назад, потому что очень часто не нужно больше одного инстанса и все писали свои велосипеды: Logger, DB connection pool, Caching, Any State Management, File System Management, etc. И к сожалению написать правильно синглетон, который будет работать в многопоточке - не так уж и просто, от туда и пошли ужасные костыли вида Double-Checked-Locking Singleton о которых меня даже спрашивали на собеседовании в районе 2010. Enum - решает все вопросы многопоточки, где single-instance гарантируется JVM. Более того использовать Enum для синглетона - это рекомендация из книги Effective Java (имхо анти-рекомендация).
Но потом года с 2010 начали говорить про зло Global state, про Testability, и Spring начал становиться довольно популярным - Singleton стал анти-паттерном.
Более того использовать Enum для синглетона - это рекомендация из книги Effective Java (имхо анти-рекомендация).
Основная беда enum-ов в качестве синглтонов - это неленивость и протекание ненужных сервису enum-методов. И первое, и второе решается тем, что enum должен быть приватным классом в синглтон-сервисе, а методы сервиса делегируют вызовы единственной константе в этом enum-е.
Spring начал становиться довольно популярным
И теперь его, не думая, пихают везде где нужно и где не нужно.
Singleton стал анти-паттерном
И именно поэтому его использует вышеупомянутый Spring? Синглтон - это просто объект-одиночка в рамках, как правило, приложения. Вне зависимости от механизма реализации данного поведения, будь-то spring, CDI, micronaut, etc.
В том что вы предлагаете также несколько других проблем:
Сломанная семантика: то что мы делаем синглетоном, очень часто никакого отношения к enumeration не имеет
Double responsibility - мы привязываем сервис и его жизненный цикл в один coupled кусок
И теперь его, не думая, пихают везде где нужно и где не нужно.
Это совершенно другой топик
Синглтон - это просто объект-одиночка в рамках, как правило, приложения. Вне зависимости от механизма реализации
Это понятно. Имхо анти-паттерн - это не class-lifecycle-management, а анти-паттерн связывание бизнес-логики (сервиса) и class-lifecycle-management вместе. Поэтому как GoF его преподносит, и как Effective Java рекомендует делать синглетоны - это имхо антипаттерн. А вот именно Spring class-lifecycle-management - это то, что позволяет нормально тестировать бизнес-логику не привязываясь сколько инстансов реально будет в проде.
очень часто никакого отношения к enumeration не имеет
Именно поэтому enum приватный и напрямую наружу не торчит, потому что его использование обусловлено лишь гарантией единственности экземпляра со стороны самой jvm.
Например в гугловой гуаве до появления лямбд через подобный механизм реализованы некоторые функции (Functions.toStringFunction()
, Functions.identity()
, ...)
GoF его преподносит
Ничего GoF не преподносит. Вообще эта книжка про шаблоны проектирования, и там приведены лишь возможные реализации этих самых шаблонов конкретно на языке java. Нигде в ней не говорится, что это единственно верный вариант.
То, что эти примеры начали бездумно копировать, полагаясь на авторитет авторов, говорит лишь о том, что люди по своей сути ленивы и, если можно не думать, предпочитают именно так и поступать.
как Effective Java рекомендует делать синглетоны
Но это единственно рабочий "изкоробочный" способ сделать синглтон без использования сторонних библиотек/фреймворков с минимальными усилиями.
public class SingletonImpl {
private static class Holder {
private static final SingletonImpl HOLDER_INSTANCE = new SingletonImpl();
}
public static SingletonImpl getInstance() {
return Holder.HOLDER_INSTANCE;
}
private SingletonImpl() {}
public void doSomething() {}
}
а этот вариант через класс холдер чем плох?
readResolve() не хватает ?
HOLDER_INSTANCE
создается и инициализируется в момент загрузки класса?
Этот вариант плох тем, что у него double-responsibilty:
бизнес логика сервиса
instantiation/lifecycle management
Это может вызвать проблемы в тестировании, особенно когда тестам не имеет значения синглетон наш бизнес класс или нет.
В 2024 году сам шаблон Singleton основательно потускнел
LoggerFactory.getLogger(MyService.class)
из большинства активно используемых логеров передаёт привет.
да практически любой Config с get only методами точно также передает привет
Это да, но оно не везде есть именно в таком виде. А вот без логеров продакшн-кода я не видел уже давно.
Конфиги как синглетон - это проблема для тестирования: одному тесту ты передашь один конгфиг, другому второй, третьему третий. И ранишь все эти тесты параллельно, как это будет работать с синглетоном? Завязывать бизнес логики (конфигов), с их жизненным циклом (синглетон) - это антипаттерн.
Я не говорю, что приложению нужно несколько инстансов конфигов, я говорю что как gof, effective Java, предлагают это делать - это антипаттерн создающий проблемы в тестировании
Конфиги как синглетон - это проблема для тестирования: одному тесту ты передашь один конгфиг, другому второй, третьему третий. И ранишь все эти тесты параллельно, как это будет работать с синглетоном?
т.е. проблема ускоренного тестирования протекает в абстракции приложения, не предназначенные для его (приложения) работы? То есть тестируется приложение в режиме, в котором оно работать не будет.
На эту проблему можно смотреть с разных углов. Вы ее формулируете так, я бы скорее ее сформулировал, что: "Глобальное требование иметь один инстанс протекает в бизнес логику конкретного сервиса, которому не имеет значение сколько инстансов ранится". Что вытекает из-того, что "догматичная" имплементация синглетона - это double-responsibility: strong-coupling бизнес логики и lifecycle/instantiating management. Если их разделить и сделать single-responsibility, то и не будет проблем протестировать отдельно бизнес логику в столько потоков во сколько душе угодно, и протестировать синглетон в интеграционных тестах, что он синглетон.
Мне кажется мы говорим об одном и том же, просто разными словами. Тонкий момент в том, что двойная ответственность синглетона наступает в момент его эксплуатации, той функциональностью, которая не должна знать о технической реализации передаваемого источника настроек (как пример). По моему мнению, сам по себе синглетон не виноват в том, что он отражает вполне конкретные сущности приложения, и если некоторой функции нужно только имя хоста, а не все настойки, то значит пусть она через формальные параметры его и принимает. А не так, что по быстрячку прокидываем синглетон, а потом "а как же тесты? ооо...плохой сингтелон, плохой".
И конфиги - синглетоны, это чистой воды оптимизация ресурсов. Ничего не поменяется если конфиг просто будет Immutable data class, и мы будем создавать новый такой же класс по какому-нибудь чиху.
Если конфиги реализованы синглетоном как предлагают GoF или Effective Java, то это протекание оптимизации ресурсов приложения в бизнес логику. И вообще то, что я считаю анти-паттерном синглетона - это протекание "lifecycle management" в бизнес логику.
Если они используются как глобальные объекты, становится сложно выбрать конфигурацию для тестовой среды.
Подскажите пожалуйста, а что это за приложения, которые требуют изменения тестовой среды, во время работы этого приложения, и при этом, не требуют смены настроек в режиме продуктовой эксплуатации?
Если я правильно понимаю, enum Singleton не ленивый (как и final Singleton).
блокировка с двойной проверкой (имейте в виду, что это может работать не так, как ожидается, из-за оптимизаций компилятора)
Все будет работать именно так, как ожидается, если понимать что именно нужно ожидать. Вот статья, которая ответит на все вопросы: https://shipilev.net/blog/2014/safe-public-construction/
EnumSingleton.INSTANCE.getInstance();
Что за бредятина? Зачем нужен getInstance()
, ежели и так имеется доступ к экземпляру?
"статический фабричный метод для получения экземпляра " - не совсем. Это "Статический фабричный метод для единоразового создания экземпляра, инициализации и получения этого экземпляра (в дальнейшем сколько угодно). В этом отличие синглетона (не отмечено в статье) от глобальной переменной. Поэтому и сравнение синглетона с глобальной переменной некорректно, как и обвинение синглетона в грехах глобальной переменной.
Поэтому я также против применения перечислений для решения задачи синглетона. Назначение перечислений - перечисление (внезапно) констант. Не нужно делать из детской коляски велосипед, хотя технически это и возможно.
Также в статье не обсуждено создание синглетонов thread-local, хотя это крайне необходимо. Об этом, кстати, в статье упоминается, но не развивается - о проблемах синглетонов в контексте потоков.
Имхо, давно пора в Java на уровне языка ввести соответствующую конструкцию.
Синглтоны в Java