之前我们已经介绍完了创建型模式,它们分别为Factory Method,Abstract Factory,Singleton,Builder,Prototype。创建型模式是创建对象而不是直接实例化对象,这会使程序在判断给定情况下创建哪个对象时更为灵活。在C#中为new,在Pascal中为Create。
接下来我们要介绍结构型模式,这类模式可以将一组对象组合成更大的结构。对于结构型模式(Structural Pattern)来说主要分为两种组合方式,分别为类模式和对象模式,其主要区别为类模式描述的是如何使用继承提供更有用的程序接口,而对象模式描述的是通过使用对象组合或将对象包含在其他对象里,将对象组合成更大的结构。
下面我们来看看结构型的第一种模式适配器模式(Adapter Pattern)。
适配器模式可以将类的接口转变成客户需要的其他接口。这样做的好处就是能很好的复用已有的代码,比如很多公司都生产数据库,如Oracle,IBM,Microsoft,它们的产品各有各的特点,原生的开发方法也有很大的区别,比如Oracle提供OCI接口,Sql Server提供自己专用的API。如果都使用数据库厂商的原生开发方法,那么在Sql Server开发的软件就很难移植到其他的数据库平台上去,这无疑会限制产品的应用范围。为此很多厂商就提供了各种标准的开发接口,比如最早的ODBC,BDE,到现在的ADO.NET等等,有了这种标准的数据库开发接口,我们在一个平台上开发的产品就很容易移植到其他平台上去。
那么如何让数据库适应不同的标准API而无需大的改动呢,这就是通过适配器模式来实现了,微软最早的ODBC Driver和现在的ADO.NET就相当于一个适配器,可以让数据库适应不同的标准开发接口。这样原有的产品无需针对标准的API做任何改变,只要针对不同的开发API实现一个Dirver就可以了。
适配器模式的实现原理很简单,就是通过实现了客户指定接口的类来响应客户的请求,然后在适配器内部将请求转发给原有的API接口对应的具有类似功能的方法,然后将处理的结果整理后返回给客户。
适配器模式有类的适配器模式和对象的适配器模式两种。我们将分别讨论这两种Adapter模式。
类的Adapter模式的结构:
由图中可以看出,AdapteeXML类没有CreateFile方法,而客户期待这个方法。为了使客户能够使用AdapteeXML类,提供一个中间环节,即类Adapter类,Adapter类实现了ISerTarget接口,此接口中定义了CreateFile方法,并继承自AdapteeXML,Adapter类的CreateFile方法重新封装了AdapteeXMl的CreateXMLFile方法,实现了适配的目的。
因为Adapter与AdapteeXML是继承的关系,所以这决定了这个适配器模式是类的。
该适配器模式所涉及的角色包括:
目标(Target)角色:这是客户所期待的接口。因为C#不支持多继承,所以Target必须是接口,不可以是类,这里为ISerTarget。
源(Adaptee)角色:需要适配的类,这里为AdapteeXML。
适配器(Adapter)角色:把源接口转换成目标接口。这一角色必须是类。
以上例子主要是一个XML串行化实例(C#):
1
using
System;
2
using
System.IO;
3
using
System.Xml.Serialization;
4
using
System.Xml;
5
namespace
AdapterClass
6

{
7
/**//// <summary>
8
///============== Program Description==============
9
///Name:AdapterClass.cs
10
///Objective:AdapterClass
11
///Date:2006-05-04
12
///Written By coffee.liu
13
///================================================
14
/// </summary>
15
class Class1
16
{
17
/**//// <summary>
18
/// 应用程序的主入口点。
19
/// </summary>
20
[STAThread]
21
static void Main(string[] args)
22
{
23
ISerTarget target=new Adapter();
24
target.CreateFile();
25
Console.WriteLine("xml file created");
26
}
27
}
28
[Serializable]
29
public class Person
30
{
31
public string Name;
32
public string Sex;
33
public DateTime Brith;
34
private int PersonID;
35
public Person()
36
{}
37
public void SetID(int ID)
38
{PersonID=ID;}
39
}
40
41
interface ISerTarget
42
{
43
void CreateFile();
44
}
45
class Adapter:AdapteeXML,ISerTarget
46
{
47
public Adapter():base()
48
{
49
}
50
51
public void CreateFile()
52
{
53
this.FillData();
54
this.CreateXMLFile();
55
56
}
57
58
}
59
class AdapteeXML
60
{
61
FileStream aFile;
62
Person aPerson;
63
XmlSerializer aXML;
64
public AdapteeXML()
65
{
66
aXML=new XmlSerializer(typeof(Person));
67
aPerson=new Person();
68
}
69
public void FillData()
70
{
71
aPerson.Name="XML";
72
aPerson.Sex="男";
73
aPerson.Brith=DateTime.Now;
74
aPerson.SetID(1);
75
}
76
public void CreateXMLFile()
77
{
78
try
79
{
80
aFile=new FileStream("Person.xml",FileMode.Create);
81
}
82
catch(IOException e)
83
{
84
Console.WriteLine("some error happened!");
85
Console.WriteLine(e.ToString());
86
return;
87
}
88
aXML.Serialize(aFile,aPerson);
89
aFile.Close();
90
}
91
}
92
}
93
当然用Pascal也可以实现同样的功能,我们看看如何将上面的XML文件读取出来,这里只给出功能性代码,具体扩展希望读者自己思考。
代码(Pascal):
1
program AdapterClass;
2
//==============
Program Description
==============
3
//
Name:AdapterClass.dpr
4
//
Objective:AdapterClass
5
//
Date:
2006
-
05
-
04
6
//
Written
By
coffee.liu
7
//================================================
8
{$APPTYPE CONSOLE}
9

10
uses
11
SysUtils,msxmldom,XMLDoc, oxmldom, xmldom, XMLIntf,windows;
12
type ISerTarget
=
interface
13
function
ReadFile():string;
14
end
;
15
type AdapteeXML
=
class
16
xmlDocument1 :IXMLDocument;
17
node:IXMLNode;
18
count
:
Integer
;
19
constructor
Create
;
20
function
ReadFromXml(Node:IXMLNode;
Count
:
integer
):string;
21
end
;
22
type Adapter
=
class(AdapteeXML,ISerTarget)
23
private
24
FRefCount :
Integer
;
25
public
26
function
QueryInterface(const IID:TGUID;out Obj):HRESULT;stdcall;
27
function
_AddRef:
Integer
;stdcall;
28
function
_Release:
Integer
;stdcall;
//
这里让接口引用计数器来控制对象生存周期了,这里我为了少写代码没有人为的去控制对象的生存周期,当然读者也可以通过继承TInterfacedObject对象来完成IInterface。
function
ReadFile():string;
29
constructor
Create
;
30
end
;
31

32

33
{ AdapteeXML }
34

35
constructor AdapteeXML.
Create
;
36
begin
37
xmlDocument1 :
=
TXMLDocument.
Create
(nil);
38
xmlDocument1.LoadFromFile(
'
D:\coffee.liu\Delphi7\Projects\Person.xml
'
);
//
上面C#例子创建出的XML文件
39
XMLDocument1.Active:
=
true;
40
count
:
=
xmlDocument1.ChildNodes.
Count
;
41
node:
=
xmlDocument1.DocumentElement;
42
end
;
43

44
function
AdapteeXML.ReadFromXml(Node:IXMLNode;
Count
:
integer
):string;
45
var
46
i:
integer
;
47
begin
48
for
i :
=
1
to
Count
-
1
do
begin
49
if
(node
<>
nil)
then
50
result:
=
Node.ChildNodes
[
'Name'
]
.
Text
+
'
;
'
+
Node.ChildNodes
[
'Sex'
]
.
Text
+
'
;
'
+
Node.ChildNodes
[
'Brith'
]
.
Text
;
51

52
end
;
53
xmlDocument1.Active:
=
false;
54
end
;
55
{ Adapter }
56

57
function
Adapter._AddRef:
Integer
;
58
begin
59
Result :
=
InterlockedIncrement(FRefCount);
60
end
;
61

62
function
Adapter._Release:
Integer
;
63
begin
64
Result :
=
InterlockedDecrement(FRefCount);
65
if
Result
=
0
then
66
Destroy;
67
end
;
68

69
function
Adapter.QueryInterface(const IID: TGUID; out Obj): HRESULT;
70
begin
71
if
GetInterface(IID, Obj)
then
72
Result :
=
0
73
else
74
Result :
=
E_NOINTERFACE;
75
end
;
76
function
Adapter.ReadFile():string;
77
begin
78
self.ReadFromXml(self.node,self.
count
);
79
end
;
80
constructor Adapter.
Create
;
81
begin
82
inherited;
83
end
;
84
var
85
aAdapter:Adapter;
86
begin
87
aAdapter:
=
Adapter.
Create
;
88
Writeln(aAdapter.ReadFile);
89
end
.
90
对象的Adapter模式的结构:


从图中可以看出:客户端需要调用CreateFile方法,而AdapteeXML,AdapteeSoap,AdapteeBin没有该方法,为了使客户端能够使用AdapteeXXX类,需要提供一个包装(Wrapper)类Adapter。这个包装类包装AdapteeXXX的实例,从而将客户端与AdapteeXXX衔接起来。
由于Adapter与AdapteeXXX是委派关系,这决定了这个适配器模式是对象的。
该适配器模式所涉及的角色包括:
目标(Target)角色:这是客户所期待的接口。目标可以是具体的或抽象的类,也可以是接口,这里为ISerTarget接口。
源(Adaptee)角色:需要适配的类,这里为AdapteeXML,AdapteeSoap,AdapteeBin类。
适配器(Adapter)角色:通过在内部包装(Wrap)Adaptee对象,把源接口转换成目标接口。
以上的例子主要为将Person类串行化为不同的形式的例子,这里分别保存为xml,soap,binary形式。由于我们使用了Adapter进行了统一封装,这样用户可以不必知道具体的封装细节,运用统一的CreateFile方法即可将Person类串行化为不同的形式。
具体代码(C#):
1
using
System;
2
using
System.IO;
3
using
System.Xml.Serialization;
4
using
System.Xml;
5
using
System.Runtime.Serialization.Formatters.Binary;
6

/**/
/////运行时要将System.Runtime.Serialization.Formatters.Soap.dll的引用添加到项目中
7
using
System.Runtime.Serialization.Formatters.Soap;
8

9
namespace
AdapterObject
10

{
11
/**//// <summary>
12
///============== Program Description==============
13
///Name:AdapterObject.cs
14
///Objective:AdapterObject
15
///Date:2006-05-04
16
///Written By coffee.liu
17
///================================================
18
/// </summary>
19
class Class1
20
{
21
[STAThread]
22
static void Main(string[] args)
23
{
24
ISerTarget target=new Adapter("XML");
25
target.CreateFile();
26
Console.WriteLine("XML file created");
27
28
target=new Adapter("Soap");
29
target.CreateFile();
30
Console.WriteLine("Soap file created");
31
32
target=new Adapter("Bin");
33
target.CreateFile();
34
Console.WriteLine("Bin file created");
35
}
36
}
37
[Serializable]
38
public class Person
{
39
public string Name;
40
public string Sex;
41
public DateTime Brith;
42
private int PersonID;
43
public Person()
44
{}
45
public void SetID(int ID)
46
{PersonID=ID;}
47
}
48
49
interface ISerTarget
{
50
void CreateFile();
51
}
52
class Adapter:ISerTarget
{
53
private AdapteeXML aXML;
54
private AdapteeSoap aSoap;
55
private AdapteeBin aBin;
56
private string S;
57
public Adapter(string s)
{
58
S=s;
59
if (s=="XML")
60
{
61
aXML=new AdapteeXML();
62
aXML.FillData();
63
}
64
else
65
if (s=="Soap")
66
{
67
aSoap=new AdapteeSoap();
68
aSoap.FillData();
69
}
70
else if (s=="Bin")
71
{
72
aBin=new AdapteeBin();
73
aBin.FillData();
74
}
75
else
76
{
77
aXML=new AdapteeXML();
78
aXML.FillData();
79
}
80
}
81
82
public void CreateFile()
{
83
if (S=="XML")
84
{
85
aXML.CreateXMLFile();
86
}
87
else
88
if (S=="Soap")
89
{
90
aSoap.CreateSoapFile();
91
}
92
else if (S=="Bin")
93
{
94
aBin.CreateBinFile();
95
}
96
else
97
{
98
aXML.CreateXMLFile();
99
}
100
101
}
102
103
}
104
class AdapteeSoap
{
105
FileStream aFileSoap;
106
Person aPerson;
107
SoapFormatter aSoapFormatter;
108
public AdapteeSoap()
{
109
aSoapFormatter=new SoapFormatter();
110
aPerson=new Person();
111
}
112
public void FillData()
113
{
114
aPerson.Name="Soap";
115
aPerson.Sex="男";
116
aPerson.Brith=DateTime.Now;
117
aPerson.SetID(2);
118
}
119
public void CreateSoapFile()
{
120
try
121
{
122
aFileSoap=new FileStream("SoapPerson.xml",FileMode.OpenOrCreate);
123
aSoapFormatter.Serialize(aFileSoap,aPerson);
124
aFileSoap.Close();
125
}
126
catch(IOException e)
{
127
Console.WriteLine("some error happened");
128
Console.WriteLine(e.ToString());
129
return;
130
}
131
132
}
133
}
134
class AdapteeBin
135
{
136
FileStream aFileBin;
137
Person aPerson;
138
BinaryFormatter aBinaryFormatter;
139
public AdapteeBin()
140
{
141
aBinaryFormatter=new BinaryFormatter();
142
aPerson=new Person();
143
}
144
public void FillData()
145
{
146
aPerson.Name="Bin";
147
aPerson.Sex="男";
148
aPerson.Brith=DateTime.Now;
149
aPerson.SetID(3);
150
}
151
public void CreateBinFile()
152
{
153
try
154
{
155
aFileBin=new FileStream("BinPerson.data",FileMode.OpenOrCreate);
156
aBinaryFormatter.Serialize(aFileBin,aPerson);
157
aFileBin.Close();
158
}
159
catch(IOException e)
160
{
161
Console.WriteLine("some error happened");
162
Console.WriteLine(e.ToString());
163
return;
164
}
165
166
}
167
}
168
class AdapteeXML
{
169
FileStream aFile;
170
Person aPerson;
171
XmlSerializer aXML;
172
public AdapteeXML()
{
173
aXML=new XmlSerializer(typeof(Person));
174
aPerson=new Person();
175
}
176
public void FillData()
{
177
aPerson.Name="XML";
178
aPerson.Sex="男";
179
aPerson.Brith=DateTime.Now;
180
aPerson.SetID(1);
181
}
182
public void CreateXMLFile()
{
183
try
184
{
185
aFile=new FileStream("Person.xml",FileMode.Create);
186
}
187
catch(IOException e)
188
{
189
Console.WriteLine("some error happened!");
190
Console.WriteLine(e.ToString());
191
return;
192
}
193
aXML.Serialize(aFile,aPerson);
194
aFile.Close();
195
}
196
}
197
}
198
类适配器和对象适配器之间的差别:
1.当我们想匹配一个类和它所有子类时,类适配器将不能胜任,因为在创建子类时就已定义了派生它的基类。如上第二例,要想改为类适配器模式就必须使用三个Adapter来分别封装三个Adaptee,这样做不太实际。
2.类适配器允许适配器更改某些被匹配的类的方法,同时还允许使用其他未修改的方法。
3. 对象适配器通过将子类传递给构造函数而允许匹配所有子类,如上面的第二例。
4.对象适配器要求读者将希望使用的,被匹配对象的方法提到表面上来。