与其他答案不同,我不想谈论单例模式的问题,而是向您展示当正确使用时它们是多么强大和棒极了!
您可以将MyModel
映射到继承自它的TestMyModel
类中,每当注入MyModel
时,您将得到TestMyModel
。
正如我在标题中所述,单例不是指拥有单个实例。
垄断是魔鬼,非只读/可变状态的单例模式是“真正”的问题...
在阅读《单例是病态的说谎者》(由jason的答案建议)后,我发现这个小贴士提供了一个最好的例子来说明单例模式通常被滥用的方法。
全局变量不好,因为:
- a. 它会导致命名空间冲突
- b. 它以不合适的方式暴露状态
当谈到单例模式时
- a. 显示调用它们的OO方法可以防止冲突,所以问题a不是个问题
- b. 没有状态的单例模式(像工厂一样)不是一个问题。有状态的单例模式又可以分为两种,那些是不可变的或者写入一次并多次读取的(config/property文件)。这些不是坏的。可变单例模式,就像是引用持有器,才是你所说的问题。
在最后一句话中,他提到了博客概念中的“单例模式是说谎者”。
这如何应用于垄断?
要开始玩垄断游戏,首先:
除了单例模式的明显线程安全和同步问题之外,如果你有一组数据可以被多个不同源并发读取/操作,并且在应用程序运行期间存在,那么现在是时候退后一步,问问自己“我是否在这里使用了正确的数据结构类型”。
就我个人而言,我曾经见过一个程序员滥用单例模式,将其用作应用程序中扭曲的跨线程数据库存储。通过直接处理代码,我可以证明它很慢(因为需要所有线程锁定以使其线程安全)并且很难处理(因为同步错误的不可预测/间歇性本质),在“生产”条件下几乎不可能进行测试。当然,可以开发系统使用轮询/信号来克服一些性能问题,但这无法解决测试问题,而且为什么要这样做,既然一个“真正”的数据库可以以更加强大/可伸缩的方式完成相同的功能。
只有在需要单例提供的情况下,单例模式才是一个选择。写入-读取实例对象。同样的规则也应该适用于对象的属性/成员。
Singleton.getInstance()
会出现一些问题。支持反射的语言可以通过设置存储“单例实例”的字段来解决这个问题。但我认为,一旦你开始在另一个类的私有状态上进行修改,测试就变得不那么可信了。 - cHao一些人认为单例模式是反面模式,他们觉得在不需要实际上只有一个类实例的情况下过度使用它会引入不必要的限制。[1][2][3][4]
参考文献(仅列出相关文献)
单例模式本身并不是坏的,但GoF设计模式有问题。唯一真正有效的论点是,GoF设计模式在测试方面不太适用,特别是如果测试是并行运行的。
只要在代码中应用以下方法,使用类的单个实例就是有效的构造:
确保将用作单例的类实现接口。这允许使用相同接口实现存根或模拟
确保单例是线程安全的。这是必须的。
单例应该简单明了,不要过于复杂。
在应用程序的运行时期间,需要将单例传递给给定对象时,请使用一个类工厂来构建该对象,并让类工厂将单例实例传递给需要它的类。
在测试期间并为确保确定性行为,请将单例类创建为单独的实例,作为实际类本身或实现其行为的存根/模拟,并将其原样传递给需要它的类。不要在测试期间使用创建需要单例的测试对象的类因子,因为它将传递它的单个全局实例,这违反了目的。
我们在解决方案中使用了单例模式,并取得了巨大的成功,这些单例模式是可测试的,确保在并行测试运行流中具有确定性行为。
我希望能够讨论已接受答案中提到的四个问题,希望有人能解释一下我错在哪里。
为什么在代码中隐藏依赖关系是不好的?已经存在很多隐藏的依赖关系(C运行时调用、操作系统API调用、全局函数调用),而单例依赖关系很容易找到(搜索instance())。
“将某些内容设置为全局变量以避免传递它是代码坏味道。”为什么传递某些内容以避免将其作为单例也是一种代码坏味道?
如果您通过 10 个函数在调用堆栈中传递一个对象只是为了避免使用单例,那真的好吗?
单一职责原则:我认为这有点模糊,取决于您对职责的定义。一个相关的问题是,为什么要将此特定的“职责”添加到一个类中?
为什么将对象传递给类会使其与单例的使用相比更紧密地耦合?
为什么它会改变状态持续的时间?单例可以手动创建或销毁,因此仍然有控制权,并且可以使其寿命与非单例对象的寿命相同。
关于单元测试:
getInstance()
方法。它要求只有一个实例存在。它管理着唯一的实例,并禁止第二个实例的存在——并通过创建另一个实例(并希望不会破坏OTI)来防止其他人覆盖其唯一性。如果你不是在谈论这个,那么你只是在谈论全局变量或服务定位器。 - cHao文森特·哈斯顿提出了下列标准,我认为这些标准很合理:
只有当满足以下三个条件时才应该考虑使用单例模式:
- 无法合理地分配单个实例的所有权
- 希望进行惰性初始化
- 没有其他方式提供全局访问
如果单个实例的所有权、初始化的时间和方式以及全局访问不是问题,那么单例模式就不够有趣。
从纯粹主义的角度来看,单例模式是不好的。
从实际角度来看,单例模式是在开发时间和复杂性之间做出的一种权衡。
如果你知道你的应用程序不会有太大变化,那么单例模式完全可以使用。只需知道,如果您的要求以意想不到的方式改变(在大多数情况下还是可以接受的),您可能需要进行重构。
单例模式有时也会使单元测试变得更加复杂。
假设该模式确实只用于模型中真正单一的某个方面,那么该模式本身并没有任何问题。
我认为人们对它的反感是由于它被过度使用了,而这又是因为它是最容易理解和实现的模式。