Я думаю, что среди всех шаблонов объектно-ориентированного проектирования у Синглтона худшая репутация. Их называют « злыми », « лжецами » и даже « глупыми », и по веским причинам:
- синглтоны являются глобальными точками доступа, поэтому клиентские API скрывают от них свои зависимости
- синглтоны усложняют тестирование, главным образом потому, что их сложно издеваться и вводить
Это верно, но только когда Singletons реализованы и используются «традиционным» способом:
- Синглеты не могут быть расширены (обратите внимание, что класс является окончательным, а конструктор закрытым)
public final class MySingleton {
private static final MySingleton INSTANCE = new MySingleton();
public static MySingleton instance() {
return INSTANCE;
}
private MySingleton() {}
public void doSomething() {
// something here
}
} - Синглтоны называются в любом месте вашего кода
public void someMethod() {
MySingleton.instance().doSomething();
// keep doing stuff
}
Нет ничего злого
Как и все остальное, нет ничего хорошего или плохого. Я использовал Singletons в следующей версии FEST-Assert таким образом, чтобы избежать проблем, упомянутых ранее:
- Не делайте Singleton финальным, делая возможным создание макетов из Singleton (кстати, Mockito может макетировать неконечные классы с помощью частных конструкторов.)
- Защищать конструктор Синглтона. Это удобно, если ваш синглтон зависит от других классов, которые нужно проверять во время тестирования синглтона. Таким образом, в своем тесте вы можете изменить состояние нового экземпляра Singleton без изменения статического состояния (то есть состояния экземпляра Singleton класса).
- Внедряйте Singletons, как и любую другую зависимость, полностью избегая проблемы «глобальной точки доступа».
Звучит неплохо. Покажи мне код!
Следующий код похож на то, что мы сделали в FEST, но намного короче и проще. Вы также можете взглянуть на код в репозитории github .
Вот синглтон:
public class MySingleton {
private static final MySingleton INSTANCE = new MySingleton();
public static MySingleton instance() {
return INSTANCE;
}
@VisibleForTesting MySingleton() {}
public void doSomething() {
// something here
}
}
В настоящее время я делаю ручное внедрение зависимостей, потому что считаю проект довольно маленьким и стараюсь свести внешние зависимости к минимуму. Конечно, вы можете (и, вероятно, должны) использовать Google Guice ?
public class SomeClient {
@VisibleForTesting final MySingleton mySingleton;
public SomeClient() {
this(MySingleton.instance());
}
@VisibleForTesting SomeClient(MySingleton mySingleton) {
this.mySingleton = mySingleton;
}
}
Как вы уже могли догадаться, конструктор SomeClient (MySingleton) используется в тестах, передавая имитированный MySingleton (и, конечно, я также проверяю, что конструктор по умолчанию использует экземпляр Singleton.) Любой не тестовый код просто вызывает конструктор по умолчанию SomeClient ().
Вывод
Несмотря на плохую репутацию, которую имеют Singletons, они все еще могут быть довольно полезными. Пока синглтоны:
- растяжимый
- впрыскивается, и
- не имеют статического изменяемого состояния (состояние само по себе неплохое)
они так же хороши, как и любой другой инструмент.