单例模式是GoF模式书中一个完全付费的成员,但最近它似乎被开发人员世界孤立了。我仍然使用了相当多的单线程,特别是对于工厂类,虽然你必须对多线程问题(像任何类一样)小心一点,但我不明白为什么它们如此糟糕。
Stack Overflow似乎特别假设每个人都认为Singleton是邪恶的。为什么?
请用“事实、参考资料或具体专业知识”来支持您的回答
单例模式是GoF模式书中一个完全付费的成员,但最近它似乎被开发人员世界孤立了。我仍然使用了相当多的单线程,特别是对于工厂类,虽然你必须对多线程问题(像任何类一样)小心一点,但我不明白为什么它们如此糟糕。
Stack Overflow似乎特别假设每个人都认为Singleton是邪恶的。为什么?
请用“事实、参考资料或具体专业知识”来支持您的回答
当前回答
单例使用静态方法实现。静态方法是做单元测试的人所避免的,因为它们不能被嘲笑或拒绝。这个网站上的大多数人都是单元测试的支持者。避免这种情况的最普遍接受的惯例是使用控制模式反转。
其他回答
摘自Brian Button:
它们通常被用作全局实例,为什么如此糟糕?因为您将应用程序的依赖项隐藏在代码中,而不是通过接口公开它们。让一些东西全球化以避免传播是一种代码气味。他们违反了单一责任原则:因为他们控制自己的创作和生命周期。它们固有地导致代码紧密耦合。这使得在许多情况下,在测试中伪造它们相当困难。它们在应用程序的整个生命周期中携带状态。另一个对测试的打击,因为您可能会遇到一种需要订购测试的情况,这对于单元测试来说是一个很大的禁忌。为什么?因为每个单元测试都应该彼此独立。
当您使用单例(例如,记录器或数据库连接)编写代码时,然后发现您需要多个日志或多个数据库,您就遇到了麻烦。
单身者很难从他们身上转移到普通物体上。
此外,编写非线程安全的单例也太容易了。
您应该将所有需要的实用程序对象从一个函数传递到另一个函数,而不是使用单例。如果您将所有这些对象包装到一个辅助对象中,这可以简化,如下所示:
void some_class::some_function(parameters, service_provider& srv)
{
srv.get<error_logger>().log("Hi there!");
this->another_function(some_other_parameters, srv);
}
单线态的问题是范围增加,因此耦合的问题。不可否认,在某些情况下,您确实需要访问单个实例,并且可以通过其他方式实现。
我现在更喜欢围绕控制反转(IoC)容器进行设计,并允许容器控制生命周期。这为依赖于实例的类提供了好处,使它们不知道存在单个实例的事实。将来可以更改单例的生存期。我最近遇到的一个例子是从单线程到多线程的简单调整。
FWIW,如果你尝试单元测试它时它是一个PIA,那么当你尝试调试、修复或增强它时,它就会变成PIA。
我对单身汉如何糟糕的回答总是,“他们很难做对”。语言的许多基本组件都是单体(类、函数、名称空间甚至运算符),计算的其他方面(本地主机、默认路由、虚拟文件系统等)也是如此,这并非偶然。虽然它们会时不时地带来麻烦和挫折,但也能让很多事情变得更好。
我看到的两个最大的错误是:将其视为一个全球性的问题&未能定义Singleton闭包。
每个人都把辛格尔顿说成是全球性的,因为他们基本上是全球性的。然而,在一个全球性的世界中,很多(不幸的是,并非所有)糟糕的地方并不是来自于全球化,而是来自于你如何使用它。辛格尔顿也是如此。事实上,“单个实例”并不意味着“全局可访问”。它更多的是一种自然的副产品,而且考虑到我们所知道的所有负面影响,我们不应该急于利用全球可访问性。一旦程序员看到Singleton,他们似乎总是通过它的实例方法直接访问它。相反,您应该像导航任何其他对象一样导航到它。大多数代码甚至不应该意识到它正在处理Singleton(松耦合,对吗?)。如果只有一小部分代码像访问全局对象一样访问该对象,那么很多危害都会消除。我建议通过限制对实例函数的访问来实施它。
辛格尔顿的背景也非常重要。Singleton的定义特征是“只有一个”,但事实是它在某种上下文/名称空间中是“唯一的”。它们通常是以下之一:每个线程、进程、IP地址或集群一个,但也可以是每个处理器、机器、语言名称空间/类加载器/任何东西、子网、Internet等。
另一个不太常见的错误是忽视单身汉的生活方式。仅仅因为只有一个,并不意味着Singleton是某种无所不能的“一直都是,永远都会是”,也不是普遍可取的(没有开始和结束的对象违反了代码中所有有用的假设,只应在最绝望的情况下使用。
如果你避免了这些错误,辛格尔顿仍然可以成为一名医院,但它已经做好了准备,可以看到许多最严重的问题得到了显著的缓解。想象一个Java Singleton,它被明确定义为每个类加载器一次(这意味着它需要线程安全策略),具有定义的创建和销毁方法,以及指定何时和如何调用它们的生命周期,其“实例”方法具有包保护,因此通常可以通过其他非全局对象访问。仍然是潜在的麻烦来源,但麻烦肯定要少得多。
可悲的是,他们没有教好如何做单身汉的例子。我们教坏的例子,让程序员暂时不用它们,然后告诉他们它们是坏的设计模式。
Singleton不是关于单个实例!
与其他答案不同,我不想谈论Singleton有什么问题,而是想告诉你,如果使用得当,它们是多么强大和可怕!
问题:在多线程环境中,Singleton可能是一个挑战解决方案:使用单线程引导过程来初始化单例的所有依赖项。问题:很难模仿单身汉。解决方案:使用工厂模式进行模拟
您可以将MyModel映射到继承它的TestMyModel类,每当注入MyModel时,您都会得到TestMyModel instread。-问题:单线程可能会导致内存泄漏,因为它们从未处理过。解决方案:好吧,把它们处理掉!在你的应用程序中实现回调以正确处理一个单件,你应该删除所有链接到它们的数据,最后:从工厂中删除它们。
正如我在标题中所说的,singleton不是关于单个实例的。
singleton提高了可读性:您可以查看您的类,看看它注入了什么样的singleton,以确定它的依赖项是什么。singleton改进了维护:一旦你从一个类中删除了一个依赖项,你就删除了一些单例注入,你就不需要去编辑其他类的一个大链接,这些类只是移动了你的依赖项(这是我的臭代码@Jim Burger)Singleton提高了内存和性能:当应用程序中发生了一些事情,并且需要很长的回调链才能传递时,您正在浪费内存和性能,通过使用Singleton,您正在削减中间人,并提高性能和内存使用率(通过避免不必要的局部变量分配)。