37

我在 SO 中看到有人评论说单例模式是一种反模式。我想知道为什么?

4

6 回答 6

51

测试

一个原因是单例不容易通过单元测试来处理。您无法控制实例化,并且它们的本质可能会在调用之间保留状态。

出于这个原因,依赖注入的原理很流行。每个类都被注入(配置)了它们需要运行的类(而不是通过单例访问器派生),因此测试可以控制要使用的依赖类实例(并在需要时提供模拟)。

Spring 等框架会控制其对象的生命周期,并且经常会创建单例,但这些对象是由框架注入到它们的依赖对象中的。因此,代码库本身不会将对象视为单例。

例如而不是这个(例如)

public class Portfolio {
   private Calculator calc = Calculator.getCalculator();
}

你会注入计算器:

public class Portfolio {
   public Portfolio(Calculator c) {
      this.calc = c;
   }
}

因此,该Portfolio对象不知道/关心存在多少个实例CalculatorCalculator测试可以注入一个使测试变得容易的虚拟对象。

并发

通过将自己限制在一个对象的一个​​实例上,线程的选项是有限的。可能必须保护对单例对象的访问(例如通过同步)。如果您可以维护这些对象的多个实例,那么您可以根据正在运行的线程调整实例数量,并增加代码库的并发能力。

于 2012-07-02T10:47:45.520 回答
22

我个人的看法是,它违反了单一责任原则。单例对象负责它们的目的和控制它们产生的实例数量,我认为这是错误的。

这就是为什么很多人将控制权委托给工厂对象的原因。

于 2012-07-02T10:47:49.267 回答
11

[可变] Singleton 是一种反模式的反模式。

重要的底层反模式是全局状态(或环境状态)。使用全局状态,您可以在整个程序中拥有一个很大的依赖关系博客。这确实会影响测试,但这只是不良编程后果的一部分。

static在此基础上,与简单地声明可变字段相比,Singleton 增加了完全不必要的复杂性。

于 2012-07-02T11:30:43.643 回答
8

像这样的单例不一定是反模式,但它们只有很少的好处,并且在使用错误时会成为反模式(这种情况经常发生)。

通常,单例根本不是单例,而是“伪装的全局变量”。此外,当“只有一个实例”属性实际上不是优势时,通常会使用它们。(这再次被许多次实施同时错误的事实所平衡)。

除此之外,考虑到多线程实现它们可能会很棘手(通常做错或效率低下),如果你想控制它们的实例化,它们就会失去大部分好处。

如果您想控制实例化,您需要在程序早期的某个时间点手动完成,但是您也可以只创建一个普通对象的实例并继续传递。
如果破坏顺序有任何问题,您也需要手动实现它。主函数中的单个自动对象更加简洁和容易。

于 2012-07-02T10:56:00.787 回答
7

您可能对单例有许多要求:

  1. 延迟初始化;
  2. 妥善处置;
  3. 作用域(例如,每个线程一个)。

通常,您的应用程序中还会有很多单例,并且单例模式不允许可重用​​代码。因此,如果您想为所有单例实现所有这些问题,您将立即看到它的反模式质量。

于 2012-07-02T10:54:35.047 回答
5

奇怪的。似乎单例的错误实现是一种“反模式”,而不是单例本身。

我想我们忘记了每个程序都必须从某个地方开始。每个抽象都必须有一个具体的实现,最终每个依赖项最终都会得到解决,否则您的应用程序将没有多大用处。

大多数 DI 框架允许将类实例化为单例,它只是为您处理。如果你选择自己做 DI,注入单例不是问题。Singleton 也是可测试的,如果您使用 DI 注入它,不会使类变得不稳定。

IMO,就像其他所有模式(包括 DI 和 IoC)一样,它是一个工具。有时适合,有时不适合。

于 2013-09-11T16:29:46.370 回答