之前我们已经介绍完了创建型模式,它们分别为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#):

 1using System;
 2using System.IO;
 3using System.Xml.Serialization;
 4using System.Xml;
 5namespace 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):
 1program 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
10uses
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
35constructor AdapteeXML.Create;
36begin
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;
42end;
43
44function AdapteeXML.ReadFromXml(Node:IXMLNode;Count:integer):string;
45var
46i:integer;
47begin
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;
54end;
55{ Adapter }
56
57function Adapter._AddRef: Integer;
58begin
59   Result := InterlockedIncrement(FRefCount);
60end;
61
62function Adapter._Release: Integer;
63begin
64   Result := InterlockedDecrement(FRefCount);
65    if Result = 0 then
66   Destroy;
67end;
68
69function Adapter.QueryInterface(const IID: TGUID; out Obj): HRESULT;
70begin
71   if GetInterface(IID, Obj) then
72    Result := 0
73  else
74    Result := E_NOINTERFACE;
75end;
76function Adapter.ReadFile():string;
77begin
78   self.ReadFromXml(self.node,self.count);
79end;
80constructor Adapter.Create;
81begin
82inherited;
83end;
84var
85aAdapter:Adapter;
86begin
87  aAdapter:=Adapter.Create;
88  Writeln(aAdapter.ReadFile);
89end.
90
     
        对象的Adapter模式的结构:
11.gif

 

从图中可以看出:客户端需要调用CreateFile方法,而AdapteeXML,AdapteeSoap,AdapteeBin没有该方法,为了使客户端能够使用AdapteeXXX类,需要提供一个包装(Wrapper)类Adapter。这个包装类包装AdapteeXXX的实例,从而将客户端与AdapteeXXX衔接起来。
由于
AdapterAdapteeXXX是委派关系,这决定了这个适配器模式是对象的。

该适配器模式所涉及的角色包括:

目标(Target)角色:这是客户所期待的接口。目标可以是具体的或抽象的类,也可以是接口,这里为ISerTarget接口。
源(Adaptee)角色:需要适配的类,这里为AdapteeXML,AdapteeSoap,AdapteeBin类
适配器(Adapter)角色:通过在内部包装(WrapAdaptee对象,把源接口转换成目标接口。

以上的例子主要为将Person类串行化为不同的形式的例子,这里分别保存为xml,soap,binary形式。由于我们使用了Adapter进行了统一封装,这样用户可以不必知道具体的封装细节,运用统一的CreateFile方法即可将Person类串行化为不同的形式。
具体代码(C#):

  1using System;
  2using System.IO;
  3using System.Xml.Serialization;
  4using System.Xml;
  5using System.Runtime.Serialization.Formatters.Binary;
  6/////运行时要将System.Runtime.Serialization.Formatters.Soap.dll的引用添加到项目中
  7using System.Runtime.Serialization.Formatters.Soap;
  8
  9namespace 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.对象适配器要求读者将希望使用的,被匹配对象的方法提到表面上来。

posted on 2006-05-04 11:22  coffeeliu  阅读(1304)  评论(1编辑  收藏  举报