我在 SO 中看到有人评论说单例模式是一种反模式。我想知道为什么?
6 回答
测试
一个原因是单例不容易通过单元测试来处理。您无法控制实例化,并且它们的本质可能会在调用之间保留状态。
出于这个原因,依赖注入的原理很流行。每个类都被注入(配置)了它们需要运行的类(而不是通过单例访问器派生),因此测试可以控制要使用的依赖类实例(并在需要时提供模拟)。
Spring 等框架会控制其对象的生命周期,并且经常会创建单例,但这些对象是由框架注入到它们的依赖对象中的。因此,代码库本身不会将对象视为单例。
例如而不是这个(例如)
public class Portfolio {
private Calculator calc = Calculator.getCalculator();
}
你会注入计算器:
public class Portfolio {
public Portfolio(Calculator c) {
this.calc = c;
}
}
因此,该Portfolio
对象不知道/关心存在多少个实例Calculator
。Calculator
测试可以注入一个使测试变得容易的虚拟对象。
并发
通过将自己限制在一个对象的一个实例上,线程的选项是有限的。可能必须保护对单例对象的访问(例如通过同步)。如果您可以维护这些对象的多个实例,那么您可以根据正在运行的线程调整实例数量,并增加代码库的并发能力。
我个人的看法是,它违反了单一责任原则。单例对象负责它们的目的和控制它们产生的实例数量,我认为这是错误的。
这就是为什么很多人将控制权委托给工厂对象的原因。
[可变] Singleton 是一种反模式的反模式。
重要的底层反模式是全局状态(或环境状态)。使用全局状态,您可以在整个程序中拥有一个很大的依赖关系博客。这确实会影响测试,但这只是不良编程后果的一部分。
static
在此基础上,与简单地声明可变字段相比,Singleton 增加了完全不必要的复杂性。
像这样的单例不一定是反模式,但它们只有很少的好处,并且在使用错误时会成为反模式(这种情况经常发生)。
通常,单例根本不是单例,而是“伪装的全局变量”。此外,当“只有一个实例”属性实际上不是优势时,通常会使用它们。(这再次被许多次实施同时错误的事实所平衡)。
除此之外,考虑到多线程实现它们可能会很棘手(通常做错或效率低下),如果你想控制它们的实例化,它们就会失去大部分好处。
如果您想控制实例化,您需要在程序早期的某个时间点手动完成,但是您也可以只创建一个普通对象的实例并继续传递。
如果破坏顺序有任何问题,您也需要手动实现它。主函数中的单个自动对象更加简洁和容易。
您可能对单例有许多要求:
- 延迟初始化;
- 妥善处置;
- 作用域(例如,每个线程一个)。
通常,您的应用程序中还会有很多单例,并且单例模式不允许可重用代码。因此,如果您想为所有单例实现所有这些问题,您将立即看到它的反模式质量。
奇怪的。似乎单例的错误实现是一种“反模式”,而不是单例本身。
我想我们忘记了每个程序都必须从某个地方开始。每个抽象都必须有一个具体的实现,最终每个依赖项最终都会得到解决,否则您的应用程序将没有多大用处。
大多数 DI 框架允许将类实例化为单例,它只是为您处理。如果你选择自己做 DI,注入单例不是问题。Singleton 也是可测试的,如果您使用 DI 注入它,不会使类变得不稳定。
IMO,就像其他所有模式(包括 DI 和 IoC)一样,它是一个工具。有时适合,有时不适合。