所谓的单件模式,就是说在系统中,一个类只存在唯一的实例,同时提供一个唯一的访问方法。
在我们的开发中,经常会发生使用唯一对象的情况,例如Web开发中,要记录网站访问人数的对象;程序属性配置的对象;网络编程中,只能建立一个连接的对象等等,类似于这些应用场景,都可以应用单件模式来处理。
下面举个例子来说明单件模式的使用,例子的功能是程序的配置信息管理,需要提供一个对配置的添加及读取方法,同时,只允许创建一个配置管理类的实例,代码如下:
class
SingletonConfig
{
private
Dictionary
<
string
,
string
>
lsConfig
=
new
Dictionary
<
string
,
string
>
();
///
<summary>
///
私有构造函数,防止外部程序调用创建新实例
///
</summary>
private
SingletonConfig()
{
}
public
void
Add(
string
key,
string
value)
{
lsConfig.Add(key, value);
}
public
string
Get(
string
key)
{
if
(lsConfig.ContainsKey(key))
{
return
lsConfig[key];
}
else
{
return
null
;
//
也可抛出异常
}
}
private
static
SingletonConfig _instance;
///
<summary>
///
配置类实例访问对象
///
</summary>
public
static
SingletonConfig Instance
{
get
{
if
(_instance
==
null
)
{
_instance
=
new
SingletonConfig();
}
return
_instance;
}
}
}
上面的代码有两个需要注意的地方,一是构造函数申明为私有类型,这可以预防代码通过构造函数的方法来生成新的实例,如下面的代码将会引起编译器错误:
SingletonConfig s = new SingletonConfig();
第二是后面的全局静态变量“Instance”,这个地方保证了外部访问到的,是唯一的配置实例。在代码使用的时候,就可以保证在任何地方调用配置类的方法,都只有Instance这个入口,保证了数据的唯一性。如下面的调用方法,不论在哪调用,都可以保证配置信息能正确的加入。
static
void
Main(
string
[] args)
{
SingletonConfig.Instance.Add(
"
Base
"
,
"
Base Value
"
);
TestA();
TestB();
Console.WriteLine(SingletonConfig.Instance.Get(
"
A
"
));
Console.WriteLine(SingletonConfig.Instance.Get(
"
B
"
));
Console.ReadLine();
}
private
static
void
TestA()
{
SingletonConfig.Instance.Add(
"
A
"
,
"
A Value
"
);
}
private
static
void
TestB()
{
SingletonConfig.Instance.Add(
"
B
"
,
"
B Value
"
);
}
单件模式的另一种用处,是用于控制多个实例的情况,例如需要做一个数据库连接池管理的操作,可以控制数据库连接池的大小,根据请求动态的分配连接对象。此时,可以这样设计单件类,如下:
class
DataConnectionPool
{
private
int
_id;
private
DataConnectionPool(
int
id)
{
this
._id
=
id;
}
public
string
DataProcess(
string
key)
{
Release(_id);
return
"
Process:
"
+
key;
}
private
static
Dictionary
<
int
, DataConnectionPool
>
lsInstance;
private
static
readonly
int
POOL_NUM
=
5
;
private
static
Queue
<
int
>
lsFree
=
new
Queue
<
int
>
();
//
当前可用队列
//
将使用完的进程放入队列待用
private
static
void
Release(
int
i)
{
lsFree.Enqueue(i);
}
public
static
DataConnectionPool Instance
{
get
{
if
(lsInstance
==
null
)
{
//
根据配置的连接池实例化对象
for
(
int
i
=
0
; i
<
POOL_NUM; i
++
)
{
lsInstance.Add(i,
new
DataConnectionPool(i));
lsFree.Enqueue(i);
}
}
if
(lsFree.Count
>
0
)
{
//
取出可以使用的实例
return
lsInstance[lsFree.Dequeue()];
}
else
{
return
null
;
//
或者抛出异常
}
}
}
}
上面程序只是简单的演示,实际应用可以根据情况进行演变,不知道这样说明,大家明白了么。设计模式书里面还有一个单件子类的说明,所谓的单件子类,是指单件返回的实体类型可能会有多个子类,因此,需要有一种方式来决定返回哪个子类的实例或者动态改变当前实例的方法,其实简单点说就是返回实现相同接口的不同实例的功能。打个简单比方来说,一家卖包子的店,虽然它卖的是包子,不过可能会卖菜包,也可能卖肉包,也有可能是糖包等等,不过不管卖什么包子,都只有一个买包子的入口,这样说不知道大家明白么?
抽象工厂模式(Abstract Factory)
抽象工厂,按字面含义来理解,就是一个不存在的工厂,只是抽象出来的一个概念工厂,反应到代码中,可以理解为定义了固定操作接口的一个抽象类,这个类不完成任何事(特殊情况也可以完成某些生产操作),而是交由它的子类来进行实际的操作。
理解概念总是困难的,因此用比喻的方式总是容易让人理解,这里我举个例子来说明抽象工厂,在我们写代码的过程中,经常会牵扯到界面元素的变动,比如常见的什么Xp样式风格、Office2003风格、Vista风格等等,例子中,我以生成文本框、复选框为例,来说明抽象工厂模式。
不论什么风格,它们都是控件,因此都会有创建文本框及复选框的方法,这些方法就是工厂方法,代码如下:
abstract
class
ControlFactory
{
public
abstract
TextBox CreateTextbox();
public
abstract
CheckBox CreateCheckbox();
}
class
StandardControlFactory : ControlFactory
{
public
StandardControlFactory() { }
public
override
TextBox CreateTextbox()
{
return
new
TextBox();
}
public
override
CheckBox CreateCheckbox()
{
return
new
CheckBox();
}
}
StandardControlFactory类是创建标准控件的一个实现工厂,同样我们要实现XP样式的文本框创建,则重新继承一下抽象工厂类即可,代码如下:
class
XpControlFactory : ControlFactory
{
public
XpControlFactory() { }
public
override
TextBox CreateTextbox()
{
TextBox tb
=
new
TextBox();
//
对tb对象进行XP样式修改
return
tb;
}
public
override
CheckBox CreateCheckbox()
{
CheckBox ck
=
new
CheckBox();
//
对ck对象进行XP样式修改
return
ck;
}
}
在客户端使用的时候,通过送入不同的实现工厂,即可实现对界面的修改,例如:
public
Panel CreateInterface(ControlFactory factory)
{
Panel p
=
new
Panel();
p.Controls.Add(factory.CreateCheckbox());
p.Controls.Add(factory.CreateTextbox());
return
p;
}
上面的代码中,要修改不同风格的控件,只需要送入不同的抽象工厂实例即可。
生成器(Builder)
生成器,顾名思义是负责生成内容的容器。先摘录一下书上对生成器的说明:将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。
初看这段话,还真有些迷糊,举个例子来说明下,现实中我们进行网络编程的时候,收到的数据包是不固定的,因此,并不知道何时能收到完整的数据包,因此,我们需要有一个过程来处理这些数据信息,而处理的过程并不适宜放在接收数据的程序模块处,因此,我们需要一个专门的类来处理这些数据包,并将其生成合适的数据包供第三方模块使用。而这个处理这些数据包的类就叫做生成器,负责将数据流生成实际需要的内容。
构建器代码示例如下(代码为了简单易读,数据流用字符串代替):
public
class
BuilderTest
{
private
StringBuilder sb
=
new
StringBuilder();
//
临时数据对象
private
Queue
<
string
>
strResult
=
new
Queue
<
string
>
();
//
结果队列
public
BuilderTest()
{
}
public
void
PubData(
string
data)
{
if
(data
==
Environment.NewLine)
//
如果是换行表示接收完对象
{
strResult.Enqueue(sb.ToString());
sb
=
new
StringBuilder();
}
sb.Append(data);
}
public
string
GetStringResult()
//
取得传送结果
{
return
strResult.Count
>
0
?
strResult.Dequeue() :
null
;
}
}
数据流调用关系如下图:

注:
上例中与书中所说略有不同,不能体现Builder的多步生产操作,而是将其多步生产合成为一个PutData函数了。
所谓的多步操作,举例来说生产一个布娃娃,可能需要布娃娃的头、身体、手和脚,而这四个部份都是分开提供的,因此Builder需要提供这四个部份进行加工,需要提供接收内容的操作方法,如:PutHead,PutBody,PubHand,PubFoot等,当都推入的部件符合一个布娃娃的生产条件则自动生成一个布娃娃。
如上图所示“ReceiverObject”在代码中扮演了内容提供者,BuilderTest则负责进行数据内容分析,组合成数据对象,“UserObject”则是最终使用用户。
此时再来理解一下书中说的那句定义:“将一个复杂对象的构建与它的表示分离,使得同样的构建过程可以创建不同的表示。“复杂对象”在此例中,就是网络传送的对象“Result”,复杂对象的构建则是指BuilderTest的处理操作。如果将BuilderTest的工作放在UserObject对象中来完成,那么在需要传送新的对象的时候,必然导致UserObject对象的修改,可实际上UserObject关心的并不是传送的数据流,而是最终的传送结果(Result),因此Builder实际上要做的事,就是将数据生成为最终使用者想要的结果。
Builder与Abstract Factory的区别:
刚开始的时候,我也觉得Builder和Abstract Factory差不多,深入了解后才发觉,它们之间也是有区别的,虽然它们都是生成新对象给使用者,也可以创建复杂对象,不过Abstract Factory是立即生成新的对象,有实时性,而Builder则有可能需要多种条件都符合的时候才能生成新的对象,有一定的延时性,按通俗的话说,Abstract Factory是原材料齐全的加工厂,有需求马上就能生产;而Builder则是等待其它部件或原材料都到位的时候才能生产出新的产品。