【问题标题】:Why use a singleton instead of static methods?为什么使用单例而不是静态方法?
【发布时间】:2011-02-15 10:36:54
【问题描述】:

对于这些关于帮助程序/实用程序类的简单问题,我从来没有找到好的答案:

  • 为什么要创建单例(无状态)而不是使用静态方法?
  • 如果对象没有状态,为什么还需要对象实例?

【问题讨论】:

标签: java design-patterns singleton


【解决方案1】:

根据 GoF 的书Design Patterns,“单例”一章,类操作与单例相比有以下缺点(我的粗体强调):

  1. 比类操作更灵活。 封装单例功能的另一种方法是使用类操作(即 C++ 中的静态成员函数或 Smalltalk 中的类方法)。但是这两种语言技术都使得很难改变设计以允许一个类的多个实例。此外,C++ 中的静态成员函数永远不是虚拟的,因此子类不能覆盖它们是多态的。

【讨论】:

    【解决方案2】:

    通常,单例用于向应用程序引入某种全局状态。 (老实说,这通常不是真正必要的,但这是另一个话题。)

    但是,在某些极端情况下,即使是 无状态 单例也是有用的:

    • 您希望在可预见的将来使用 state 扩展它。
    • 出于某些特定的技术原因,您需要一个对象实例
      示例:C# lock 或 Java synchronized 语句的同步对象。
    • 您需要继承,也就是说,您希望能够使用相同的接口但不同的实现轻松地将您的单例替换为另一个。
      示例:Java 中的Toolkit.getDefaultToolkit() 方法将返回一个其确切类型的单例取决于系统。
    • 您希望sentinel value引用相等
      示例:C# 中的DBNull.Value

    【讨论】:

    • 我会选择+1,尽管恕我直言,单例被误用来引入全局状态。单例的目的不是让一个对象全局可用,而是强制一个对象只被实例化一次。全局对象是必要的邪恶。除非真的需要,否则应该尽量不要使用它们,因为它们通常会导致高耦合,SomeSingleton.getInstance().someMethod() 到处都是。 :)
    • 它在您只需要一个渲染实例而不是多个渲染实例的游戏中很有用,或者在您设置一个安全通道的网络管道类中,一次只能有一个连接.
    • “轻松替换你的单例”部分是我认为最重要的一点。 Rest 非常接近于静态类的实现。
    • 单例对于 sum/product 类型和类似类型是有用的非 Null 标记值。例如,作为 Option 类型中的 None/Nothing 值或集合类型中的 Empty 值。您可以获得快速的无/空测试作为奖励,因为只需要引用相等。
    • @itsbruce:您的示例不是 singletons:空列表不是 List 类的唯一可能实例,并且 None 值不是唯一可能的实例选项类。但是,有一些示例,其中哨兵​​值 are 单例,我冒昧地在我的答案中添加了一个。感谢您的输入!
    【解决方案3】:

    对我来说“想要对象状态使用单例,想要函数使用静态方法”

    这取决于你想要什么。每当您想要对象状态时(例如像 Null 状态而不是 null 或默认状态的多态性),单例是适合您的选择,而当您需要函数时使用静态方法(接收输入然后返回输出)。

    我推荐单例的情况,它在实例化后应该总是相同的状态。它既不能克隆,也不能接收任何要设置的值(文件中的静态配置除外,例如 java 中的 properties 文件)。

    附:这两者之间的性能以毫秒为单位不同,因此请先关注架构

    【讨论】:

      【解决方案4】:

      在使用哪一个之间需要权衡取舍。单例可能有也可能没有状态,它们指的是对象。如果它们不保持状态并且仅用于全局访问,那么静态更好,因为这些方法会更快。但是如果你想利用对象和 OOP 概念(继承多态),那么单例更好。

      考虑一个例子:java.lang.Runtime 是 java 中的一个单例类。此类允许对每个 JVM 进行不同的实现。每个 JVM 的实现是单一的。如果这个类是静态的,我们就不能传递基于 JVM 的不同实现。

      我发现这个链接真的很有帮助:http://javarevisited.blogspot.com/2013/03/difference-between-singleton-pattern-vs-static-class-java.html?

      希望对你有帮助!

      【讨论】:

      • 这个答案很适合在现实世界中包含一个具体的例子。
      【解决方案5】:

      实际上,我找到了另一个此处未提及的答案:静态方法更难测试。

      似乎大多数测试框架都非常适合模拟实例方法,但它们中的许多都不能很好地处理静态方法的模拟。

      【讨论】:

      • 但是 Powermock 似乎可以这样做
      【解决方案6】:

      单例不是无状态的,它持有全局状态。

      我能想到使用 Singleton 的一些原因是:

      • 为了避免内存泄漏
      • 为应用程序中的所有模块提供相同的状态,例如数据库连接

      【讨论】:

      • 我知道,但实际上单例或多或少可以是无状态的...如果它不共享任何类属性...
      【解决方案7】:

      在大多数编程语言中,类都避开了很多类型系统。虽然具有静态方法和变量的类是一个对象,但它通常不能实现接口或扩展其他类。因此,它不能以多态方式使用,因为它不能是另一种类型的子类型。例如,如果你有一个接口IFooable,这是其他类的几个方法签名所需要的,那么类对象StaticFoo不能用来代替IFooable,而FooSingleton.getInstance()可以(假设FooSingleton实现IFooable)。

      请注意,正如我对 Heinzi 的回答所评论的那样,单例是一种控制实例化的模式。它将new Class() 替换为Class.getInstance(),这使Class 的作者可以更好地控制实例,从而防止创建不必要的实例。单例只是工厂模式的一个非常特殊的情况,应该这样对待。普遍使用使它成为全局注册表的特殊情况,这通常以糟糕的结果告终,因为不应随意使用全局注册表。

      如果您打算提供全局帮助函数,那么静态方法就可以正常工作。该类不会充当类,而只是充当名称空间。我建议,你保持高内聚,否则你可能会遇到最奇怪的耦合问题。

      问候
      back2dos

      【讨论】:

        【解决方案8】:

        我可以看到一个使用无状态单例而不是静态方法类的案例,即Dependency Injection

        如果你有一个你直接使用的实用函数的辅助类,它会创建一个隐藏的依赖;您无法控制谁可以使用它或在哪里使用它。通过无状态单例实例注入相同的帮助程序类可以让您控制它的使用位置和方式,并在需要时替换它/模拟它/等等。

        使它成为一个单例实例只是确保您不会分配超过必要类型的任何对象(因为您只需要一个)。

        【讨论】:

        • “你无法控制谁可以使用它,或者在哪里使用它。” 为什么有人需要它?
        • @hagrawal 用于测试目的,你应该可以模拟它
        猜你喜欢
        • 2011-02-27
        • 2018-10-27
        • 2011-12-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2013-02-25
        • 1970-01-01
        • 2021-02-16
        相关资源
        最近更新 更多