Сопротивляйтесь использованию Singleton
(В оригинале - Resist the Temptation of the Singleton Pattern)
Паттерн Singleton может решить много проблем. Вы знаете, что вам необходим единственный экземпляр. Вы получаете гарантию, что этот экземпляр инициализируется перед использованием. Ваш дизайн остается простым благодаря глобальной точке доступа. Это все хорошо. Но что же не так с этим классическим паттерном?
Оказывается, проблем хватает. Опыт показывает, что большинство Singleton-ов приносят больше вреда, чем пользы. Они снижают возможность тестирования и повреждают сопровождаемость. Однако информация об этом распространена не настолько хорошо, насколько должна была бы, и в результате Singleton по-прежнему используют множество программистов. Цена же оказывается достаточно высокой:
- Очень часто необходимость в единственном экземпляре придуманная. Во множестве случаев то, что не будет нужно более чем одного экземпляра — лишь предположение. И если это делать слишком часто во множестве мест приложения — в какой-то момент могут начаться неприятности. Изменятся требования. Хороший дизайн с этим справится. А Singleton — нет.
- Singleton-ы вызывают неявную зависимость между концептуально независимыми модулями. У этой проблемы две стороны — зависимость получается скрытой, плюс добавляется связность между модулями. Проблема станет особенно острой, когда вы начнете писать юнит-тесты, предполагающие разрыв связи и возможность выборочно заменить реальный код тестовым. Singleton-ы затрудняют такую прямую подмену.
- Singleton-ы также вносят неявное состояние, что также мешает юнит-тестированию. Юнит-тесты должны быть независимы друг от друга, чтобы тесты могли запускаться в любом порядке и программа могла быть установлена в известное состояние перед выполнением каждого юнит-теста. Но как только у вас появятся Singleton-ы с изменяемым состоянием, добиться этого станет очень сложно. К тому же, такое глобально доступное состояние делает гораздо более сложным предсказание о поведении кода, особенно в многопоточных системах.
- Многопоточность добавляет Singleton-ам еще одну проблему. Поскольку прямая блокировка доступа не слишком эффективна, популярным стала так называемая блокировка с двойной проверкой. К сожалению, оказалось, что во многих языках блокировка с двойной проверкой не является потокобезопасной, и даже там, где она все же безопасна, все еще остается возможность ее нарушить.
И наконец, еще одна проблема:
- Не существует поддержки явного удаления Singleton-а, что может стать серьезной проблемой в некоторых контекстах. Например, в архитектуре plug-in, где plug-in может быть выгружен лишь после того, как будут удалены все его объекты.
- Порядок удаления Singleton-ов при выходе из приложения неопределен. Это может привести к проблемам в некоторых приложениях, содержащих Singleton-ы со взаимосвязями. При выходе из такого приложения один Singleton может получить доступ к другому, который к тому времени уже был удален.
- Некоторые из этих проблем могут быть решены с использованием дополнительных механизмов. Однако, это приводит к дополнительной сложности кода, которую можно избежать, выбрав альтернативный дизайн.
Поэтому ограничивайте применение шаблона Singleton только к тем классам, которые действительно не должны быть созданы в более чем одном экземпляре. Не используйте Singleton в качестве глобальной точки доступа из произвольного места в коде. Прямой доступ к Singleton-у должен быть только из небольшого количества хорошо определенных мест, откуда Singleton может передаваться через интерфейсы в другие части кода. Другие части кода не будут в таком случае зависеть от того, реализован интерфейс как Singleton или нет. Такой подход разрывает зависимости, мешающие юнит-тестированию, и улучшает сопровождаемость. В следующий раз, когда вы подумаете о реализации Singleton, сделайте паузу и подумайте еще раз.
Автор оригинала - Sam Saariste