单态模式的好表兄——单例模式?

8

单例模式绝对是最被滥用和误用的设计模式之一。我们中的许多人在某个时候都感染了单例模式恶病。奇怪的是,它的近亲Monostate却不那么出名,也不那么常用。你对Monostate有什么看法?它是好还是邪恶的东西?它是否是使用单例模式的更好选择?您是否也会像对待单例模式一样反对使用它?


能否描述一下这两种模式,以便我们可以更准确地讨论? - Karl
8个回答

7

嗯,单态就是单例…所以它面临着完全相同的问题。

  • 测试
  • 隐藏依赖项
  • 不灵活
  • 线程安全
  • 全局状态使得确保正确性变得困难

4
单态(Monostate)不同于单例模式(singleton),否则我就不会提出这个问题。 - Boon
在区分单例模式和单态模式时,本文仅涉及技术细节 - 性能、多态性和语法 - 这些都是特定于Java实现的。它不涉及单例模式和单态模式之间的概念差异,因为它们并不存在。 - Iraimbilanja
缺乏弹性:你永远不知道你需要多少东西。今天只有一台打印机?明天就会有两台。 - Iraimbilanja
确保正确性:如果所有代码都可以访问一个对象,它往往会利用这种访问方式。你最终会发现状态被从看似不相关的模块中改变,这很快就会变成难以理解的地狱。由于单例没有访问控制,因此无法陈述不变量。 - Iraimbilanja
在SO上还要搜索单例q,已经有很多了。至于所讨论的单态Java实现:我认为它会掩盖代码效果(被错误地称为“透明性”),对我来说这足以避免使用它。不过它仍然只是单例模式的一个实现而已。 - Iraimbilanja
显示剩余3条评论

6
根据单态模式的定义,这里有一条注释:
"单态模式和单例模式一样邪恶。"
我并不完全同意。基本前提是单例模式和单态模式都是全局变量,而全局变量之所以邪恶,是因为它们是可以在本地范围之外被意外访问的变量,就像非类型语言通常允许通过引用来创建变量一样。
单例模式和单态模式无法造成同样的问题,因为几乎不可能“意外”引用全局静态变量,调用其实例方法,然后使用该实例。
换句话说,全局变量是有害的,因为它们会导致微妙且难以察觉的问题。单例和单态不会导致同样类型的问题,所以我不认为它们有同样的问题,这也是大多数人在这个争论中犯错的地方。
现在,单例和单态会引起其他类型的问题吗?当然会。显然,TDD团队讨厌它们,因为很难通过自动化测试正确地进行测试。好吧,但对于那些不使用TDD的人来说,这些问题并不存在。
当然,单例可能被误用。那些仅仅为了避免传递实例而使用它们的人是在误用它们。我认为单态比单例更好。
许多人提出使用工厂模式来替代单例,但我认为工厂只是花哨的单例生成器。不,它没有静态的"instance"方法,但它基本上做的是相同的事情(当一个工厂创建一个单一实例对象时)。Factory.Create与Singleton.Instance没有任何区别。

单例和单态的一个方面与全局变量相似,即它们是共享的,因此不是线程安全的。虽然如果您计划创建多线程应用程序,这是一个问题,但如果您知道这一点,可以采取措施来序列化访问共享对象。因此,这可能是唯一一个领域,通常可以认为所有三种类型都会引起问题。


说得好。不过,工厂并不一定是单例生成器。事实上,大多数情况下,工厂用于生成实际的类实例。 - Boon
这就是为什么我说“当一个工厂创建了一个单例对象时”。 - Erik Funkenbusch
99% 的情况下,当工厂被使用时,它被用作虚拟构造函数(例如,它根据传入的 id 返回其子类的实例之一),而不是单例生成器。因此,Factory.Create 几乎总是与 Singleton.Instance 不同。 - Boon

5

仅仅因为使用模式而使用它们如何?

更新:滥用单例的原因是人们没有充分理由就添加了这种模式。它通常会增加任意的限制,却没有任何好处。如果不使用模式也可以创建唯一实例而不会导致世界崩溃,那么就不要强制使用模式。


我不同意。如果软件要真正演变成为一个真正的构建块格式,那么我们需要标准化事物的处理方式,类似于结构工程师标准化桥梁或建筑设计的各个部分的方式。如果可能的话,应始终使用模式而不是自己编写代码。 - Erik Funkenbusch
1
这个问题并不建议人们为了使用模式而使用模式。模式不是一种工具,它是在软件构建中一遍又一遍出现的相同主题。如果你在日常代码设计中识别出这样的模式,并知道它的优缺点,那么它将帮助你打造更好的软件。 - Boon
“不要用模式来强制执行”似乎有些不妥,因为无论如何我们都是在使用模式来强制执行——在您的情况下,是“仅创建一个实例”的模式。仅创建一个实例也不是免费的,这可能意味着需要添加方法或方法签名来传递共享信息。 - Boon
我的方式很简单,创建另一个实例就像你决定让你的显示适配器代码支持第二个监视器一样。 - Dustin Getz
1
@MM:当更简单明了的模式可用时,应该使用模式而不是自己编写。 - Dustin Getz

5
我对Monostate最大的问题是它可能会导致使用者混淆。一般来说,如果您创建一个对象的新实例,您希望它只是一个新实例,具有自己的状态和生命周期。通常情况下,意外的行为被认为是不好的,因此我更喜欢单例模式,因为当您使用它时,您知道自己得到了什么。
至于单例模式是否邪恶,单例往往被滥用,但其他所有模式,如switch语句、for循环或几乎任何其他模式也可能被滥用。

4

你为什么假设人们会反对使用单例模式?对单例模式进行笼统的概括就像对goto语句、全局变量等进行笼统的概括一样。它们都有各自的用途,没有一种是“邪恶”的,它们被误用并不意味着不能使用,只是需要正确使用。


2
人们确实会反对单例模式,甚至谷歌都有一种工具可以将其从员工编写的任何Java代码中移除!当然,它们有它们的用途(主要是缓存/资源处理),但从表面上看,它们就是全局变量,使得测试代码成为一场噩梦。 - rjh
同上 - 如果使用不当,大多数功能/模式/功能都可以被滥用; 这并不意味着它们本质上是“邪恶”的。 - RobS
你不必完全坏才被称为邪恶。按照你的逻辑,原子弹在正确使用时并不邪恶。问题在于,能够正确使用原子弹的情况非常少,因此,将其归纳为邪恶是可以接受的,并且对于几乎所有讨论来说都足够了。 - Boon
2
Boon:这是一种错误的类比,并不是我想要表达的。将单例(Singleton)与能够导致数百万人死亡的事物进行比较,有点幼稚。当正确使用时,单例是很好的,没有人会因此而死亡。 - Tim Sullivan
1
我不是在概括,而是反对那些事情“邪恶”的概括。它们并不是邪恶的,只是经常被误用或过度使用。从实际上来说,这更像是一把刀而非一枚炸弹。当然,你可以用它杀人,但它也有合法的用途。将来不要那么夸张。 - Tim Sullivan
显示剩余4条评论

2
这是来自Gamma、Helm、Johnson和Vlissides的《设计模式:可复用面向对象软件的基础》第127页的摘录。
当以下情况发生时,请使用Singleton模式:
  • 必须有一个类的唯一实例,并且客户端必须能够从已知的访问点访问它
  • 当唯一实例应该通过子类化进行扩展,并且客户端应该能够在不修改其代码的情况下使用扩展实例时
仅仅因为单例被误用和误解,就把它们说成是邪恶的,这真的合适吗?

1

如何创建一个只有单个实例但没有全局访问权限的类:

public class SingleInstance
{
    private static boolean exhausted = false;

    public SingleInstance()
    {
        if (exhausted)
        {
            throw new IllegalStateException("only one instance allowed");
        }
        exhausted = true;

        [...]
    }

    [...]
}

这样做避免了 Singleton 和 MonoState 的问题,同时强制并清晰地传达只有一个实例。


1

Monostate的优点在于,你不需要了解对象的实现细节就可以使用它。换句话说,你可以省略一些按照单例模式调用getInstance方法的步骤(Singleton s = Singleton::getInstance; s.Method();),而是直接使用普通的语言结构(Monostate ms; ms.Method();)。这是一个微妙的区别。

每种编程语言结构都可能被贴上“邪恶”的标签,因为每种编程语言结构都可能被滥用。

当合理使用时,我认为两者都不是“邪恶”或“好的”。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接