首先,让我们区分Single Object和Singleton。后者是前者众多可能的实现之一。而且 Single Object 的问题与 Singleton 的问题不同。单一对象本身并不是坏事,有时是做事的唯一方法。简而言之:
- 单个对象 - 我只需要程序中的一个对象实例
- Singleton - 创建一个具有静态字段的类。添加返回此字段的静态方法。在第一次调用时懒惰地实例化一个字段。始终返回相同的对象。
public class Singleton {
private static Singleton instance;
private Singleton() {}
public static Singleton instance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
如您所见,规范形式的“单例”模式对测试不太友好。不过,这很容易解决:只需让 Singleton 实现一个接口。我们称它为“可测试的单例”:)
public class Singleton implements ISingleton {
private static Singleton instance;
private Singleton() {}
public static ISingleton instance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
现在我们可以模拟 Singleton,因为我们通过接口使用它。其中一项索赔消失了。让我们看看我们是否可以摆脱另一个声明 - 共享全局状态。
如果我们剥离单例模式,它的核心就是延迟初始化:
public static ISingleton instance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
这就是它存在的全部原因。 这就是单对象模式。我们把它拿走,放到工厂方法中,例如:
public class SingletonFactory {
private static ISingleton instance;
// Knock-knock. Single Object here
public static ISingleton simpleSingleton() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
我们的可测试单例有什么不同?没有none,因为这是单对象模式的精髓——无论您将其实现为单例、工厂方法还是服务定位器都没有关系。你仍然有一些共享的全局状态。如果从多个线程访问它,这可能会成为一个问题。您必须使simpleSingleton() 同步并处理所有多线程问题。
再一次:无论您选择哪种方法,您都必须支付单一对象的价格。使用依赖注入容器只是将复杂性转移到必须处理单个对象固有问题的框架上。
回顾:
- 大多数提到 Singleton 的人都是指 Single Object
- 一种流行的实现方式是单例模式
- 它有可以缓解的缺陷
- 然而,Singleton 的大部分复杂性源于 Single Object 的复杂性
- 无论您如何实例化单个对象,它仍然存在,无论是服务定位器、工厂方法还是其他东西
- 您可以将复杂性转移到经过(希望)经过良好测试的 DI 容器
- 有时使用 DI 容器很麻烦 - 想象一下为每个类注入一个 LOGGER