之前我们已经介绍完了创建型模式,它们分别为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方法,实现了适配的目的。<?xml:namespace prefix = o />
因为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
![ExpandedBlockStart.gif](/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
7![ExpandedSubBlockStart.gif](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <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![ExpandedSubBlockStart.gif](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
17![ExpandedSubBlockStart.gif](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <summary>
18
/// 应用程序的主入口点。
19
/// </summary>
20
[STAThread]
21
static void Main(string[] args)
22![ExpandedSubBlockStart.gif](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
23
ISerTarget target=new Adapter();
24
target.CreateFile();
25
Console.WriteLine("xml file created");
26
}
27
}
28
[Serializable]
29
public class Person
30![ExpandedSubBlockStart.gif](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
31
public string Name;
32
public string Sex;
33
public DateTime Brith;
34
private int PersonID;
35
public Person()
36![ExpandedSubBlockStart.gif](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{}
37
public void SetID(int ID)
38![ExpandedSubBlockStart.gif](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{PersonID=ID;}
39
}
40![InBlock.gif](/Images/OutliningIndicators/InBlock.gif)
41
interface ISerTarget
42![ExpandedSubBlockStart.gif](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
43
void CreateFile();
44
}
45
class Adapter:AdapteeXML,ISerTarget
46![ExpandedSubBlockStart.gif](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
47
public Adapter():base()
48![ExpandedSubBlockStart.gif](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
49
}
50![InBlock.gif](/Images/OutliningIndicators/InBlock.gif)
51
public void CreateFile()
52![ExpandedSubBlockStart.gif](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
53
this.FillData();
54
this.CreateXMLFile();
55
56
}
57
58
}
59
class AdapteeXML
60![ExpandedSubBlockStart.gif](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
61
FileStream aFile;
62
Person aPerson;
63
XmlSerializer aXML;
64
public AdapteeXML()
65![ExpandedSubBlockStart.gif](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
66
aXML=new XmlSerializer(typeof(Person));
67
aPerson=new Person();
68
}
69
public void FillData()
70![ExpandedSubBlockStart.gif](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
71
aPerson.Name="XML";
72
aPerson.Sex="男";
73
aPerson.Brith=DateTime.Now;
74
aPerson.SetID(1);
75
}
76
public void CreateXMLFile()
77![ExpandedSubBlockStart.gif](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
78
try
79![ExpandedSubBlockStart.gif](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
80
aFile=new FileStream("Person.xml",FileMode.Create);
81
}
82
catch(IOException e)
83![ExpandedSubBlockStart.gif](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
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
![None.gif](/Images/OutliningIndicators/None.gif)
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
![None.gif](/Images/OutliningIndicators/None.gif)
32
![None.gif](/Images/OutliningIndicators/None.gif)
33
{ AdapteeXML }
34
![None.gif](/Images/OutliningIndicators/None.gif)
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
![None.gif](/Images/OutliningIndicators/None.gif)
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
![None.gif](/Images/OutliningIndicators/None.gif)
52
end
;
53
xmlDocument1.Active:
=
false;
54
end
;
55
{ Adapter }
56
![None.gif](/Images/OutliningIndicators/None.gif)
57
function
Adapter._AddRef:
Integer
;
58
begin
59
Result :
=
InterlockedIncrement(FRefCount);
60
end
;
61
![None.gif](/Images/OutliningIndicators/None.gif)
62
function
Adapter._Release:
Integer
;
63
begin
64
Result :
=
InterlockedDecrement(FRefCount);
65
if
Result
=
0
then
66
Destroy;
67
end
;
68
![None.gif](/Images/OutliningIndicators/None.gif)
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
![ExpandedBlockStart.gif](/Images/OutliningIndicators/ExpandedBlockStart.gif)
/**/
/////运行时要将System.Runtime.Serialization.Formatters.Soap.dll的引用添加到项目中
7
using
System.Runtime.Serialization.Formatters.Soap;
8
![None.gif](/Images/OutliningIndicators/None.gif)
9
namespace
AdapterObject
10
![ExpandedBlockStart.gif](/Images/OutliningIndicators/ExpandedBlockStart.gif)
{
11![ExpandedSubBlockStart.gif](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
/**//// <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![ExpandedSubBlockStart.gif](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
21
[STAThread]
22
static void Main(string[] args)
23![ExpandedSubBlockStart.gif](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
24
ISerTarget target=new Adapter("XML");
25
target.CreateFile();
26
Console.WriteLine("XML file created");
27![InBlock.gif](/Images/OutliningIndicators/InBlock.gif)
28
target=new Adapter("Soap");
29
target.CreateFile();
30
Console.WriteLine("Soap file created");
31![InBlock.gif](/Images/OutliningIndicators/InBlock.gif)
32
target=new Adapter("Bin");
33
target.CreateFile();
34
Console.WriteLine("Bin file created");
35
}
36
}
37
[Serializable]
38![ExpandedSubBlockStart.gif](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public class Person
{
39
public string Name;
40
public string Sex;
41
public DateTime Brith;
42
private int PersonID;
43
public Person()
44![ExpandedSubBlockStart.gif](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{}
45
public void SetID(int ID)
46![ExpandedSubBlockStart.gif](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{PersonID=ID;}
47
}
48![InBlock.gif](/Images/OutliningIndicators/InBlock.gif)
49![ExpandedSubBlockStart.gif](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
interface ISerTarget
{
50
void CreateFile();
51
}
52![ExpandedSubBlockStart.gif](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
class Adapter:ISerTarget
{
53
private AdapteeXML aXML;
54
private AdapteeSoap aSoap;
55
private AdapteeBin aBin;
56
private string S;
57![ExpandedSubBlockStart.gif](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public Adapter(string s)
{
58
S=s;
59
if (s=="XML")
60![ExpandedSubBlockStart.gif](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
61
aXML=new AdapteeXML();
62
aXML.FillData();
63
}
64
else
65
if (s=="Soap")
66![ExpandedSubBlockStart.gif](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
67
aSoap=new AdapteeSoap();
68
aSoap.FillData();
69
}
70
else if (s=="Bin")
71![ExpandedSubBlockStart.gif](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
72
aBin=new AdapteeBin();
73
aBin.FillData();
74
}
75
else
76![ExpandedSubBlockStart.gif](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
77
aXML=new AdapteeXML();
78
aXML.FillData();
79
}
80
}
81![InBlock.gif](/Images/OutliningIndicators/InBlock.gif)
82![ExpandedSubBlockStart.gif](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public void CreateFile()
{
83
if (S=="XML")
84![ExpandedSubBlockStart.gif](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
85
aXML.CreateXMLFile();
86
}
87
else
88
if (S=="Soap")
89![ExpandedSubBlockStart.gif](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
90
aSoap.CreateSoapFile();
91
}
92
else if (S=="Bin")
93![ExpandedSubBlockStart.gif](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
94
aBin.CreateBinFile();
95
}
96
else
97![ExpandedSubBlockStart.gif](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
98
aXML.CreateXMLFile();
99
}
100
101
}
102
103
}
104![ExpandedSubBlockStart.gif](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
class AdapteeSoap
{
105
FileStream aFileSoap;
106
Person aPerson;
107
SoapFormatter aSoapFormatter;
108![ExpandedSubBlockStart.gif](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public AdapteeSoap()
{
109
aSoapFormatter=new SoapFormatter();
110
aPerson=new Person();
111
}
112
public void FillData()
113![ExpandedSubBlockStart.gif](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
114
aPerson.Name="Soap";
115
aPerson.Sex="男";
116
aPerson.Brith=DateTime.Now;
117
aPerson.SetID(2);
118
}
119![ExpandedSubBlockStart.gif](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public void CreateSoapFile()
{
120
try
121![ExpandedSubBlockStart.gif](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
122
aFileSoap=new FileStream("SoapPerson.xml",FileMode.OpenOrCreate);
123
aSoapFormatter.Serialize(aFileSoap,aPerson);
124
aFileSoap.Close();
125
}
126![ExpandedSubBlockStart.gif](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
catch(IOException e)
{
127
Console.WriteLine("some error happened");
128
Console.WriteLine(e.ToString());
129
return;
130
}
131![InBlock.gif](/Images/OutliningIndicators/InBlock.gif)
132
}
133
}
134
class AdapteeBin
135![ExpandedSubBlockStart.gif](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
136
FileStream aFileBin;
137
Person aPerson;
138
BinaryFormatter aBinaryFormatter;
139
public AdapteeBin()
140![ExpandedSubBlockStart.gif](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
141
aBinaryFormatter=new BinaryFormatter();
142
aPerson=new Person();
143
}
144
public void FillData()
145![ExpandedSubBlockStart.gif](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
146
aPerson.Name="Bin";
147
aPerson.Sex="男";
148
aPerson.Brith=DateTime.Now;
149
aPerson.SetID(3);
150
}
151
public void CreateBinFile()
152![ExpandedSubBlockStart.gif](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
153
try
154![ExpandedSubBlockStart.gif](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
155
aFileBin=new FileStream("BinPerson.data",FileMode.OpenOrCreate);
156
aBinaryFormatter.Serialize(aFileBin,aPerson);
157
aFileBin.Close();
158
}
159
catch(IOException e)
160![ExpandedSubBlockStart.gif](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
161
Console.WriteLine("some error happened");
162
Console.WriteLine(e.ToString());
163
return;
164
}
165![InBlock.gif](/Images/OutliningIndicators/InBlock.gif)
166
}
167
}
168![ExpandedSubBlockStart.gif](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
class AdapteeXML
{
169
FileStream aFile;
170
Person aPerson;
171
XmlSerializer aXML;
172![ExpandedSubBlockStart.gif](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public AdapteeXML()
{
173
aXML=new XmlSerializer(typeof(Person));
174
aPerson=new Person();
175
}
176![ExpandedSubBlockStart.gif](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public void FillData()
{
177
aPerson.Name="XML";
178
aPerson.Sex="男";
179
aPerson.Brith=DateTime.Now;
180
aPerson.SetID(1);
181
}
182![ExpandedSubBlockStart.gif](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
public void CreateXMLFile()
{
183
try
184![ExpandedSubBlockStart.gif](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
185
aFile=new FileStream("Person.xml",FileMode.Create);
186
}
187
catch(IOException e)
188![ExpandedSubBlockStart.gif](/Images/OutliningIndicators/ExpandedSubBlockStart.gif)
{
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.对象适配器要求读者将希望使用的,被匹配对象的方法提到表面上来。