Design patterns 单例模式的缺点是什么?

Design patterns 单例模式的缺点是什么?,design-patterns,singleton,Design Patterns,Singleton,是一个完全付费的会员,但是最近它似乎被开发者世界孤立了。我仍然使用了很多单例,特别是对于,虽然您必须对多线程问题(实际上像任何类一样)小心一点,但我不明白为什么它们如此糟糕 堆栈溢出似乎特别假设每个人都同意单例是邪恶的。为什么? 请用“事实、参考或特定专业知识”来支持您的答案,因为它们基本上是面向对象的全局变量,您通常可以这样设计类,这样您就不需要它们了 它很容易(ab)用作全局变量 依赖于单例的类相对来说更难单独进行单元测试 有些人还认为它是一种反模式,他们认为它被过度使用,在实际不需要类的

是一个完全付费的会员,但是最近它似乎被开发者世界孤立了。我仍然使用了很多单例,特别是对于,虽然您必须对多线程问题(实际上像任何类一样)小心一点,但我不明白为什么它们如此糟糕

堆栈溢出似乎特别假设每个人都同意单例是邪恶的。为什么?


请用“事实、参考或特定专业知识”来支持您的答案,因为它们基本上是面向对象的全局变量,您通常可以这样设计类,这样您就不需要它们了

  • 它很容易(ab)用作全局变量
  • 依赖于单例的类相对来说更难单独进行单元测试
  • 有些人还认为它是一种反模式,他们认为它被过度使用,在实际不需要类的唯一实例的情况下引入了不必要的限制。[1][2][3][4]

    参考文献(仅文章中的相关参考文献)

  • ^亚历克斯·米勒,2007年7月
  • ^斯科特·登斯莫尔,2004年5月
  • ^史蒂夫·耶格,2004年9月
  • ^J.B.Rainsberger,IBM,2001年7月

  • 假设该模式用于模型的某个方面,而该方面是真正单一的,那么该模式本身并没有什么问题


    我相信这种反弹是由于它的过度使用,而这反过来又是因为它是最容易理解和实现的模式。

    谷歌的Misko Hevery有一些关于这个主题的有趣文章

    有一个单元测试示例,演示了单例如何使找出依赖链和启动或测试应用程序变得困难。这是一个相当极端的虐待例子,但他提出的观点仍然有效:

    单身汉只不过是一个全球性的国家。全局状态使您的对象可以秘密地获取未在其API中声明的内容,因此,单例使您的API成为病态说谎者


    指出依赖项注入使得向需要实例的构造函数获取实例变得容易,这缓解了第一篇文章中所述的糟糕的全局单例背后的潜在需求。

    单例使用静态方法实现。进行单元测试的人避免使用静态方法,因为它们不能被模仿或存根。这个网站上的大多数人都大力支持单元测试。最普遍接受的避免它们的惯例是使用这种模式。

    一些编码势利者将它们视为一种美化的全局模式。就像许多人讨厌goto声明一样,也有其他人讨厌使用全球网络的想法。我见过一些开发人员为了避免一次全球性的失败而不遗余力,因为他们认为使用一次失败是一种承认。奇怪但真实


    实际上,单例模式只是一种编程技术,它是概念工具包中有用的一部分。有时,你可能会发现它是理想的解决方案,因此请使用它。但是,仅仅为了吹嘘使用设计模式而使用它,就如同拒绝使用它一样愚蠢,因为它只是一个全局模式。

    摘自Brian Button:

  • 它们通常被用作一个全局实例,为什么这么糟糕?因为您在代码中隐藏了应用程序的依赖项,而不是通过接口公开它们。将某些东西全球化以避免其传播是一项艰巨的任务

  • 它们违反了规则:因为它们控制着自己的创造和生命周期

  • 它们固有地导致代码紧密地连接在一起。这使得在许多情况下,在测试中伪造它们相当困难

  • 在应用程序的生命周期中,它们始终保持状态。另一个对测试的冲击是,您可能会遇到需要订购测试的情况,这对于单元测试来说是一个很大的禁忌。为什么?因为每个单元测试应该相互独立


  • 关于单例的一个相当糟糕的事情是,你不能很容易地扩展它们。如果你想改变他们的行为,你基本上必须加入一些类似的东西。另外,如果有一天你想用多种方法来完成一件事,那么根据你如何布局你的代码,改变可能是相当痛苦的

    需要注意的一点是,如果您使用单例,请尝试将它们传递给需要它们的人,而不是让他们直接访问它。。。否则,如果您选择使用多种方式来完成singleton所做的事情,那么很难进行更改,因为如果每个类直接访问singleton,那么每个类都会嵌入一个依赖项

    所以基本上:

    公共MyConstructor(单例单例){
    this.singleton=singleton;
    }
    
    而不是:

    public MyConstructor(){
    this.singleton=singleton.getInstance();
    }
    
    我相信这种模式被称为,通常被认为是一件好事


    就像任何模式一样。。。想想看,如果它在给定的情况下使用不当或不…规则通常是被打破的,不应该毫无思想地任意应用。

    当您使用单例(例如,记录器或数据库连接)编写代码,然后发现您需要多个日志或多个数据库时,您会遇到麻烦

    单例使得从它们移动到常规对象非常困难

    而且,编写非线程安全的单例代码太容易了

    您应该将所有需要的实用程序对象从一个函数传递到另一个函数,而不是使用单例。如果将所有对象包装到辅助对象中,则可以简化此操作,如下所示:

    void some_class::some_function(parameters, service_provider& srv)
    {
        srv.get<error_logger>().log("Hi there!");
        this->another_function(some_other_parameters, srv);
    }
    
    void some_class::some_函数(参数、服务提供程序和srv)
    {
    srv.get().log(“你好!”;
    此->另一个函数(一些其他参数,srv)
    
    public class SingletonDao {
        // songleton's static variable and getInstance() method etc. omitted
        public void writeXYZ(...){
            synchronized(...){
                // some database writing operations...
            }
        }
    }