第五章 单件(例)模式

这一站 是单件模式(Singleton Pattern) 用来创建独一无二的,只能有一个实例的对象的入场券。
单件模式的类图可以说是所有模式的类图中最简单的,事实上,它的类图上只有一个类!尽管从类设计的视角来说它很简单,但是实现上还是会遇到相当多的波折。
所以,系好安全带,出发了!

独一无二

问:什么!整章的内容就是如何实例化“一个对象“吗?
答:这可是“唯一”的对象呀!

问:这有什么用处?
答:有一些对象其实我们只需要一个,比方说:线程池(threadpool)、缓存(cache)、对话框、处理偏好设置和注册表(registry)的对象、日志对象。
事实上类对象只能有一个实例,如果制造出多个实例,就会导致许多问题产生,例如:程序的行为异常、资用过量,或者是不一致的结果。

问:好吧!或许的确有一些类应该只存在一个实例,但这需要花整个章节的篇幅来说明吗?难道能靠程序员之间的约定或是利用全局变量做到?
答:许多时候,的确通过程序员之间的约定就可以办到。但如果有更好的做法,大家应该都乐意接受别忘了,就跟其他的模式一样,单件模式是经得起时间考验的方法,可以确保只有一个实例会被创建。
单件模式也给了我们一个全局的访问点,和全局变量一样方便,又没有全局变量的缺点。

问:什么缺点?
答:举例来说:如果将对象赋值给一个全局变量,那么你必须在程序一开始就创建好对象,对吧?万这个对象非常耗费资源,而程序在这次的执行过程中又一直没用到它,不就形成浪费了吗?稍后你会看到利用单件模式,我们可以在需要时才创建对象。

诱导问答

其实单例模式大家应该都很熟悉,但是那,咱还是从一开始开始讲起

如何创建一个对象?
new MyObject();

万一另一个对象想创建MyObject会怎样?可以再次new MyObject吗?
是的,当然可以。

所以,一旦有一个类,我们是否都能多次地实例化它?
如果是公开的类,就可以。

如果不是的话,会怎样?
如果不是公开类,只有同一个包内的类可以实例化它,但是仍可以实例化它多次。

嗯!有意思!你知道可以这么做吗?

1
2
3
4
public MyClass {
private MyClass(){)
}


我没想过。但是,这是合法的定义,有一定的道理。

怎么说呢?
我认为含有私有的构造器的类不能被实例化。

有可以使用私有的构造器的对象吗?
嗯,我想MyClass内的代码是唯一能调用此构造器的代码。但是这又不太合乎常理。

为什么?
因为必须有MyClas类的实例才能调用MyClas造器,但是因为没有其他类能够实例化MyClas所以我们得不到这样的实例。
这是“鸡生蛋生鸡”的问题。我可以在MyClass类型的对多使用MyClass构造器,但是在这之前,必须有个MyClass实例。在产生MyCalss实例之前,又须在MyClass实例内才能调用私有的构造器

嘿!我有个想法。你认为这样如何?

1
2
3
4
public MyClass {
public static MyClass getInstance (){}
}


MyClass有一个静态方法。我们可以这样调用这个方法:MyClass.getInstance();

为何调用的时候用MyClass的类名,而不是用对象名?
因为getInstance()是一个静态方法,换句话说是一个“类”方法。引用一个静态方法,你要使用类名。

有意思。假如把这些合在一起“是否”就可以初始化一个MyClass?

1
2
3
4
5
6
7
8
9
10
11
public MyClass {

private MyClass(){}

public static MyClass getInstance()
{
return new MyClass();
}

}


当然可以。

好了,你能想出第二种实例化对象的方式吗?
MyClass.getInstance();

你能够完成代码使MyClass只有一个实例被产生吗?
嗯,大概可以吧……

单件模式

剖析经典的单件模式实现

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
 public class Singleton
{
//利用一个静态变量来记录Singleton类的唯一实例。
private static Singleton instance;

//把构造器声明为私有的,只有自Singleton类内才可以调用构造器。
private Singleton()
{

}

//用getInstance()方法实例化对象,并返回这个实例
public static Singleton GetInstance()
{
if (instance == null)
{
instance = new Singleton();
}

return instance;
}
}

单件访谈

今天我们很高兴专访单件对象。一开始,不妨先介绍一下你自己。
单件:关于我,我只能说我很独特,我是独一无二的。
独一无二?
单件:是的,独一无二。我是利用单件模式构造出来的,这个模式让我在任何时刻都只有一个对象。
这样不会有点浪费吗?毕竟有人花了这么多时间写了类的代码,而这个类竟然只产生一个对象。
单件:不,一点儿也不浪费!“一个”的威力很强大呢!比方说,如果有一个注册表设置(registsetting)的对象,你不希望这样的对象有多个拷贝吧?那会把设置搞得一团乱。利用像我这样的单件对象,你可以确保程序中使用的全局资源只有一份。
请继续……
单件:嗯!我擅长许多事。有时候独身是有些好处的。我常常被用来管理共享的资源,例如数据库接或者线程池。
那么,请允许我这么问,你怎么能确定只有一个你?说不定别人也会利用new产生多个你呢。
单件:不可能,我是独一无二的。
该不会要每个开发人员都发毒誓绝对不会实例化多个你吧?
单件:当然不是,真相是……唉呀!这牵扯到个人隐私……其实……我没有公开的构造器
没有公开的构造器!!噢!抱歉!我太激动了。没有公开的构造器?
单件:是的,我的构造器是声明为私有的。
这怎么行得通?你“究竟”是怎样被实例化的?
单件:外人为了要取得我的实例,他们必须“请求”得到一个实例,而不是自行实例化得到一个实例。我的类有一个静态方法,叫做getinstance()。调用这个方法,我就立刻现身,随时可以工作。事实上,我可能是在这次调用的时候被创建出来的,也可能是以前早就被创建出来了.

定义单件模式

现在你脑海中已经有了单件的经典实现,先看看单件模式的简要定义:

单件模式确保一个类只有一个实例,并提供一个全局访问点。

这定义一点儿都不让人吃惊,但是让我们更深入一点儿:
1.到底怎么回事?我们正在把某个类设计成自己管理的一个单独实例,同时也避免其他类再自行产生实例。要想取得单件实例,通过单件类是唯一的途径。
2.我们也提供对这个实例的全局访问点:当你需要实例时,向类查询,它会返回单个实例。前面的例子利用延迟实例化的方式创建单件,这种做法对资源敏感的对象特别重要。
好吧!来看看类图:

单件Q&A

问: 单件模式只有一个类应该是很简单的模式,但是问题似乎不少。
答:唉呀!我们只是提前警告,读者不要因为这点儿问题而泄气。固然正确地实现单件模式需要一点技巧,但是在阅读完本章之后,你已经具备了用正确的方法实现单件模式的能力。当你需要控制实例个数时,还是应当使用单件模式。

问:难道我不能创建一个类,把所有的方法和变量都定义为静态的,把类直接当做一个单件?
答:如果你的类自给自足,而且不依赖于复杂的初始化,那么你可以这么做。但是,因为静态初始化的控制权是在C#手上,这么做有可能导致混乱,特别是当有许多类牵涉其中的时候。这么做常常会造成一些微妙的、不容易发现的和初始化的次序有关的bug。除非你有绝对的必要使用类的单件,否则还是建议使用对象的单件,比较保险。

问:我所受到的教育一直是:类应该做一件事,而且只做一件事。类如果能做两件事,就会被认为是不好的OO设计。单件有没有违反这样的观念呢?
答:你说的是“一个类,一个责任”原则。没错,你是对的,单件类不只负责管理自己的实例(并提供全局访问),还在应用程序中担当角色,所以也可以被视为是两个责任。尽管如此,由类管理自已的实例的做法并不少见。这可以让整体设计更简单。更何况,许多开发人员都已经熟悉了单件模式的这种做法。

问:我想把单件类当成超类,设计出子类,但是我遇到了问题:究竟可以不可以继承单件类?
答:继承单件类会遇到的一个问题,就是构造器是私有的。你不能用私有构造器来扩展类。所以你必须把单件的构造器改成公开的或受保护的。但是这么一来就不算是“真正的”单件了,因为别的类也可以实例化它。
如果你果真把构造器的访问权限改了,还有另一个问题会出现。单件的实现是利用静态变量,直接继承会导致所有的派生类共享同一个实例变量,这可能不是你想要的。

尾章 复习

来到第5章的结尾,我们的设计原则又多了一些东西…· 从头捋一下 先说我们的OO设计原则
封装变化
多用组合,少用继承
针对接口编程,不针对实现编程
为对象之间的松耦合而努力
对扩展开放,对修改关闭
依赖抽象,不要依赖具体类

再说我们的00设计模式
单件模式–确保一个类只有例一个实例,并提供全局访问点。

要点
1.单件模式确保程序一个类最多只有一个实例。
2.单件模式也提供访问这个实例的全局点。
3.在C#中实现单件模式需要私有的构造器、一个静态方法和一个静态变量。