【问题标题】:Why implementing a Singleton pattern in Java code is (sometimes) considered an anti-pattern in Java world?为什么在 Java 代码中实现单例模式(有时)被认为是 Java 世界中的反模式?
【发布时间】:2012-07-02 10:45:34
【问题描述】:

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

【问题讨论】:

标签: java design-patterns singleton


【解决方案1】:

测试

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

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

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

例如而不是这个(例如)

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

你会注入计算器:

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

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

并发

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

【讨论】:

  • 那么使用单例的人是如何处理“测试”和“并发”问题的呢?
  • 有些困难。像 PowerMock 这样的工具可以提供覆盖单例方法的方法,但它们是依靠字节码操作的复杂工具
【解决方案2】:

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

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

【讨论】:

  • 我认为这只会使 [对 SRP 的解释] 可疑。
  • 怀疑这显然不是单例的问题,但 SRP 的解释表明它是。 SRP(或对它的这种解释)似乎是有罪的一方。
  • 单身人士具有混合的责任,因此降低了内聚力,这绝对是 imo 模式的问题。
  • 单身人士就像多神世界中的小神,你的神总是会互相争斗,因为他们的关注点没有明确定义,而且如果他们包含状态,他们可以改变他们的想法和他们的跨调用的行为变化
  • 在优化设计时,绝不能混用状态、配置和缓存原则。我经常在单身人士中看到这种情况。你怎么知道?因为当缓存独立于状态时,它将是透明的。更好的是,当 const 被配置类(甚至更好的接口)替换时,测试/开发变得更加容易
【解决方案3】:

[Mutable] Singleton 是一种反模式的反模式。

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

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

【讨论】:

  • 可变静态字段也有同样的问题,它是全局状态。我看不出使用静态字段比单例更好。有什么相关信息吗?
  • @JuanMendes 正如我所说,简单的可变静态比单例可变静态更好,因为它们没有毫无意义的复杂性。
  • 我应该在发表评论之前更好地查看答案。 100% 同意你的看法。它基本上是全局状态的委婉说法,所以称它为静态字段至少不会试图美化它。
【解决方案4】:

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

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

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

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

【讨论】:

    【解决方案5】:

    你可能对单例有很多要求:

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

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

    【讨论】:

      【解决方案6】:

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

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

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

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-05-05
        • 1970-01-01
        • 2023-04-10
        • 2012-09-27
        • 2019-06-17
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多