接下来我们要介绍结构型模式,这类模式可以将一组对象组合成更大的结构。对于结构型模式(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#):
using System;2
using System.IO;3
using System.Xml.Serialization;4
using System.Xml;5
namespace AdapterClass6
{7
/// <summary>8
///============== Program Description==============9
///Name:AdapterClass.cs10
///Objective:AdapterClass 11
///Date:2006-05-04 12
///Written By coffee.liu13
///================================================14
/// </summary>15
class Class116
{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 Person30
{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 ISerTarget42
{43
void CreateFile();44
}45
class Adapter:AdapteeXML,ISerTarget46
{47
public Adapter():base()48
{49
}50

51
public void CreateFile()52
{53
this.FillData();54
this.CreateXMLFile();55
56
}57
58
}59
class AdapteeXML60
{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
try79
{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):
program AdapterClass;2
//============== Program Description==============3
//Name:AdapterClass.dpr4
//Objective:AdapterClass5
//Date:2006-05-046
//Written By coffee.liu7
//================================================8
{$APPTYPE CONSOLE}9

10
uses11
SysUtils,msxmldom,XMLDoc, oxmldom, xmldom, XMLIntf,windows;12
type ISerTarget=interface13
function ReadFile():string;14
end;15
type AdapteeXML=class16
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
private24
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
begin37
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
var46
i:integer;47
begin48
for i := 1 to Count-1 do begin49
if(node<>nil) then50
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
begin59
Result := InterlockedIncrement(FRefCount);60
end;61

62
function Adapter._Release: Integer;63
begin64
Result := InterlockedDecrement(FRefCount);65
if Result = 0 then66
Destroy;67
end;68

69
function Adapter.QueryInterface(const IID: TGUID; out Obj): HRESULT;70
begin71
if GetInterface(IID, Obj) then72
Result := 073
else74
Result := E_NOINTERFACE;75
end;76
function Adapter.ReadFile():string;77
begin78
self.ReadFromXml(self.node,self.count);79
end;80
constructor Adapter.Create;81
begin82
inherited;83
end;84
var85
aAdapter:Adapter;86
begin87
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#):
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 AdapterObject10
{11
/// <summary>12
///============== Program Description==============13
///Name:AdapterObject.cs14
///Objective:AdapterObject 15
///Date:2006-05-04 16
///Written By coffee.liu17
///================================================18
/// </summary>19
class Class120
{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
else65
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
else76
{77
aXML=new AdapteeXML();78
aXML.FillData();79
}80
}81

82
public void CreateFile(){83
if (S=="XML")84
{85
aXML.CreateXMLFile();86
}87
else88
if (S=="Soap")89
{90
aSoap.CreateSoapFile();91
}92
else if (S=="Bin")93
{94
aBin.CreateBinFile();95
}96
else97
{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
try121
{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 AdapteeBin135
{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
try154
{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
try184
{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.对象适配器要求读者将希望使用的,被匹配对象的方法提到表面上来。


浙公网安备 33010602011771号