Is Singleton Really Evil?

软件设计 · godsme · 于 发布 · 最后由 asj回复 · 956 次阅读
816

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本身并不必然会增加系统的耦合度。糟糕的类设计,以及类本身并不必然是个单例,这两个不稳定的设计,才会导致系统的耦合度的增加。

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


「软件匠艺社区」旨在传播匠艺精神,通过分享好的「工作方式」和「习惯」以帮助程序员更加快乐高效地编程。
共收到 1 条回复
377
asj · #1 ·

Sure, it is evil. Sometime it is a necessary evil.

需要 登录 后回复方可回复, 如果你还没有账号你可以 注册 一个帐号。