Is Singleton Really Evil?

作者: _袁英杰_ | 来源:发表于2016-06-24 20:29 被阅读634次

    GOF23OO设计模式中,在现实项目,尤其是C++项目里,最为常见的当属Singleton

    之所以出现这样的现象,是因为它的简单。完全不需要什么OO思想就可以熟练使用。

    因此,对于从面向过程程序员转过来的C++程序员,更多的把Singleton当做一个普通C函数的集合,而这个集合往往是一个职责不明的上帝类:名字往往包含:Manager, Controller, Layer等。

    正是因为如此,Singleton一直背负着恶名。其中最为人诟病的是它增加了耦合度

    Singleton的问题究竟在哪?

    Singleton作为一个创建型设计模式,其客户代码一般会这么写:

    Foo::getInstance().doSomething();
    

    这种情况下,一个Singleton与普通的类的唯一差别是,其客户会同时依赖两项知识:

    1. 类所提供的服务;
    2. 类是一个单例。

    而普通的类,只会造成第一类耦合。

    我们知道耦合最大的问题,在于被依赖方的变化会导致依赖方跟着变化。因而,当Foo某天从单实例变为多实例时,就会造成Foo的所有客户代码都会跟着修改。

    怎样降低耦合?

    既然Singleton仅仅是在第二点处增加了耦合,那么降低其耦合的手段也就呼之欲出:让客户不依赖类是一个单例这个事实

    具体做法则是:依赖注入。即让客户的工厂将Singleton实例注入给客户。比如:

    struct Client
    {
      Client(Foo* foo) : foo(foo) {}
      
      void f() 
      {
         foo->doSomething();
      }
    private:
      Foo* foo;
    };
    
    struct ClientFactory
    {
        static Client* create()
        {
            return new Client(Foo::getInstance());
        }
    };
    

    Or DON'T...

    这样的做法确实降低了ClientSingleton之间的耦合度。但同时,这也增加了实现的复杂度,另外,也会让每个Client实例都会增加一个指针开销。得失之间,我们需要Think Twice

    我们知道耦合所带来的危害:即被依赖方的变化导致依赖方的变化。但我们也同样知道,按照向着稳定的方向依赖原则,如果被依赖方是稳定的,那么客户代码也不会受到变化的影响。

    因而,如果一个Singleton从本质上就是单例,从可以设想的范围内,这项知识不会变化。那么系统的耦合度事实上并未因此而增加。比如:

    Log::getInstance().trace("blah, blah...");
    

    全局变量

    另外,Foo::getInstance()这样的写法,和一个全局变量g_Foo的写法无异。我们都知道全局变量是邪恶的,因为它们会大大增加系统的耦合度。

    但是,全局变量必然会增加系统的耦合度吗?

    让我们再次回到同样的出发点:只有不稳定的依赖才会真正增加耦合度

    而很多全局变量,都是把实现细节,而不是对问题的抽象暴露给其客户。

    同时,作为全局变量,整个系统随处均可访问,从而造成大面积的对于实现细节的耦合。一旦这些不稳定的实现细节发生变化,则会导致整个系统大面积的受到影响。这正是全局变量原罪的由来。

    而作为一个Singleton,客户对其造成的耦合点:

    1. 类的服务接口;
    2. 类是一个单例。

    如果这两点都是稳定的,那么用户对其的依赖并不会造成真正的问题。

    在很多现实项目中,正是因为设计素养的缺乏,从而造成Singleton本身是一个职责不明的上帝类,因而其服务接口本身就是不稳定的;同时也违背的缩小依赖范围原则,强迫客户依赖了很多它并不真正需要的接口。这种耦合给客户造成的恶劣影响,要远远大于类是一个Singleton所带来的影响。

    另外,一个上帝类自身也是高度不稳定的,这会更加恶化系统的正交度。

    而反过来,如果Singleton类本身已经对实现细节做了很好的信息隐藏,同时也做到了单一职责,其API定义又非常合理,那么客户对于类本身接口的依赖就是稳定的。

    另外,如果从本质上看,这个类本来就应该只有一个实例,那么客户对于第二点的依赖也是稳定的。

    结论

    Singleton本身并不必然会增加系统的耦合度。糟糕的类设计,以及类本身并不必然是个单例,这两个不稳定的设计,才会导致系统的耦合度的增加。

    因而,我们运用任何一个具体规则时,都要回到更高层,更本质的层面去衡量,这样才能看清每个现象背后的原因,从而有助于我们做好每一个设计决策。

    相关文章

      网友评论

      • 刘光聪:作者每次都看透问题的本质,知道所以然才能正确在合适的场合加以运用。
      • 落9天:好文。单例模式最常出现的问题是不好写测试,另一个问题是Java的服务类往往在单例模式和静态类/方法之间难以抉择。读了大作,似有所悟。
      • MagicBowen:拨乱反正!

      本文标题:Is Singleton Really Evil?

      本文链接:https://www.haomeiwen.com/subject/klaodttx.html