閱讀376 返回首頁    go 阿裏雲 go 技術社區[雲棲]


來源於WCF的設計模式:可擴展對象模式[下篇]

在《來源於WCF的設計模式:可擴展對象模式》我通過一個簡單的例子介紹了基於IExtensibleObject<T>和IExtension<T>這兩個接口為核心的“可擴展對象模式”。在那篇文章中,我是通過編程的方式來應用擴展到擴展對象的。其實,如何能夠通過配置的方式來定義擴展,這個所謂的“可擴展對象模式”將會發揮更大的威力。[源代碼從這裏下載]

目錄:
一、將XxxBuilder定義在配置中
二、ExtensionConfigurationElement<T>和ExtensionNameTypeElementCollection<T>
三、XxxBuilder的配置
四、RoomFactory

為了給大家對基於配置的擴展有一個初步的印象,我們同樣先通過一個具體的例子看看最終實現的效果。同樣采用上篇中關於“創建房間”的例子,不過為了真正的展示配置的作為,我們為代表房間構成元素(牆、窗戶和門)的類型添加相應的屬性。其中Materials屬性代表門的材質,Width和Height代表窗戶的長和寬,而Color則代表強的顏色。

   1: public class Door
   2: {
   3:     public string Materials { get; set; }
   4: }
   5:  
   6: public class Window
   7: {
   8:     public int Width { get; set; }
   9:     public int Height { get; set; }
  10: }
  11:  
  12: public class Wall
  13: {
  14:     public Color Color { get; set; }
  15: }

當然用於構建門、窗和牆的DoorBuilder、WindowBuilder和WallBuilder需要進行相應的修改,因為在構建相應元素的時候需要為相應的屬性賦值。下麵是WindowBuilder的定義,DoorBuilder和WallBuilder的定義與之類似。

   1: [ConfigurationElementType(typeof(WindowBuilderData))]
   2: public class WindowBuilder : IExtension<Room>
   3: {
   4:     public int Width { get; set; }
   5:     public int Height { get; set; } 
   6:     public Window Window { get; private set; }
   7:     public WindowBuilder(int width, int height)
   8:     {
   9:         this.Width = width;
  10:         this.Height = height;
  11:     }
  12:     public void Attach(Room owner)
  13:     {
  14:         owner.Window = this.Window = new Window { Width = this.Width, Height = this.Height };
  15:     }
  16:     public void Detach(Room owner)
  17:     {
  18:         if (this.Window == owner.Window)
  19:         {
  20:             owner.Window = null;
  21:             this.Window = null;
  22:         }
  23:     }
  24: }

現在我們將需要創建的Room,連同它們的DoorBuilder、WindowBuilder和WallBuilder都定義在配置中。在下麵的配置中,我們定義了兩個Room(room1和room2),它們具有不同的Builder的設置(比如DoorBuilder構建門的材質分別為“Iron”和“Wood”,WindowBuilder構建的窗戶尺寸分別為2*2和1*1,而WallBuilder構建的牆的顏色分別是“White”和“Gray”)。

   1: <?xml version="1.0" encoding="utf-8" ?>
   2: <configuration>
   3:   <configSections>
   4:     <section name="roomManager" type="ExtensibleObjectPattern.Configuration.RoomManagerSettings, ExtensibleObjectPattern"/>
   5:   </configSections>
   6:   <roomManager>
   7:     <rooms>
   8:       <add name="room1">
   9:         <builders>
  10:           <add name="doorBuilder" type="ExtensibleObjectPattern.DoorBuilder" materials="Iron"/>
  11:           <add name="windowBuilder" type="ExtensibleObjectPattern.WindowBuilder" width="2" height="2"/>
  12:           <add name="WallBuilder" type="ExtensibleObjectPattern.WallBuilder" color="White"/>
  13:         </builders>
  14:       </add>
  15:       <add name="room2">
  16:         <builders>
  17:           <add name="doorBuilder" type="ExtensibleObjectPattern.DoorBuilder" materials="Wood"/>
  18:           <add name="windowBuilder" type="ExtensibleObjectPattern.WindowBuilder" width="1" height="1"/>
  19:           <add name="WallBuilder" type="ExtensibleObjectPattern.WallBuilder" color="Gray"/>
  20:         </builders>
  21:       </add>
  22:     </rooms>
  23:   </roomManager>
  24: </configuration>

現在我具有一個專門用於創建Room對象的靜態工廠RoomFactory,它可以直接通過指定Room在配置文件中的名稱(room1和room2)直接創建我們需要的Room。如下在Console應用中調用如下代碼來創建配置的兩個Room對象,從輸出的結構我們可以看到創建出來的對象的相應屬性完全和配置是吻合的。

   1: Room room1 = RoomFactory.CreateRoom("room1");
   2: Console.WriteLine("Wall's Color\t: {0}", room1.Walls[0].Color);
   3: Console.WriteLine("Windows's Size\t: {0}*{1}", room1.Window.Width, room1.Window.Height);
   4: Console.WriteLine("Door's Material\t: {0}\n", room1.Door.Materials);
   5:  
   6: Room room2 = RoomFactory.CreateRoom("room2");
   7: Console.WriteLine("Wall's Color\t: {0}", room2.Walls[0].Color);
   8: Console.WriteLine("Windows's Size\t: {0}*{1}", room2.Window.Width, room2.Window.Height);
   9: Console.WriteLine("Door's Material\t: {0}", room2.Door.Materials);

輸出結果:

   1: Wall's Color    : Color [White]
   2: Windows's Size  : 2*2
   3: Door's Material : Iron
   4:  
   5: Wall's Color    : Color [Gray]
   6: Windows's Size  : 1*1
   7: Door's Material : Wood


現在我們來簡單介紹一下具體實現,先來看看表示整個 <roomManager>配置節RoomManagerSettings的定義。從上麵給出的配置結構我們可以看出,這個配置節就是一個包含具體room設置的<rooms>節點,該節點對應著RoomManagerSettings的Rooms屬性。

   1: public class RoomManagerSettings: ConfigurationSection
   2: {
   3:     [ConfigurationProperty("rooms", IsRequired = true)]
   4:     public NameElementCollection<RoomConfigurationElement> Rooms
   5:     {
   6:         get { return (NameElementCollection<RoomConfigurationElement>)this["rooms"]; }
   7:         set { this["rooms"] = value; }
   8:     }
   9:     public RoomConfigurationElement GetRoomConfigurationElement(string name)
  10:     {
  11:         return (RoomConfigurationElement)this.Rooms.GetConfigurationElement(name);
  12:     }
  13:  
  14:     public static RoomManagerSettings GetCurrent()
  15:     {
  16:         return (RoomManagerSettings)ConfigurationManager.GetSection("roomManager");
  17:     }
  18: }

該屬性的類型為NameElementCollection<T>,這是我們自定義的一個泛型配置元素集合(ConfigurationElementCollection)。NameElementCollection<T>的定義如下,泛型參數T為繼承自NamConfigurationElement的配置元素。NamConfigurationElement則是我自定義的一個包含“name”配置屬性的ConfigurationElement。

   1: public class NameElementCollection<T> : ConfigurationElementCollection where T : NameConfigurationElement
   2: {
   3:     public T GetConfigurationElement(string name)
   4:     {
   5:         return (T)this.BaseGet(name);
   6:     }
   7:     protected override ConfigurationElement CreateNewElement()
   8:     {
   9:         return Activator.CreateInstance<T>();
  10:     }
  11:     protected override object GetElementKey(ConfigurationElement element)
  12:     {
  13:         return ((NameConfigurationElement)element).Name;
  14:     }
  15: }

對於RoomManagerSettings的Rooms屬性,其類型的泛型參數定義成RoomConfigurationElement。而RoomConfigurationElement則代表一個具體的Room的配置,除了具有一個名稱(繼承自ConfigurationElement)之外,它餘下的配置就是XxxBuilder的集合了。該集合的類型為ExtensionNameTypeElementCollection<Room>。

   1: public class RoomConfigurationElement : NameConfigurationElement
   2: {
   3:     [ConfigurationProperty("builders", IsRequired = false)]
   4:     public ExtensionNameTypeElementCollection<Room> Builders
   5:     {
   6:         get { return (ExtensionNameTypeElementCollection<Room>)this["builders"]; }
   7:         set { this["builders"] = value; }
   8:     }
   9: }

實際上ExtensionNameTypeElementCollection<T>(類型T為實現了接口IExtensiableObject<T>的可擴展對象類型)是怎個配置係統的核心。在定義它的時候,我使用到了在《通過自定義配置實現插件式設計》中實現的關於“配置元素的動態解析”機製。具體來說,就是讓它繼承自在那篇文章中定義的NameTypeElementCollection<TConfigElement>並將泛型參數TConfigElement指定為ExtensionConfigurationElement<T>定義如下。

   1: public class ExtensionNameTypeElementCollection<T> : NameTypeElementCollection<ExtensionConfigurationElement<T>> where T : IExtensibleObject<T>
   2: {         
   3: }

而ExtensionConfigurationElement<T>實際上代表了具體擴展(也就是我們例子中的XxxBuilder)的配置元素它則繼承自《通過自定義配置實現插件式設計》中定義的NameTypeConfigurationElement,並定義了一個虛方法CreateExtension來創建相應的擴展,即XxxBuilder。

   1: public class ExtensionConfigurationElement<T> : NameTypeConfigurationElement where T: IExtensibleObject<T>
   2: {
   3:     public virtual IExtension<T> CreateExtension()
   4:     {
   5:         throw new NotImplementedException("This method must be overriden in sub class.");
   6:     }
   7: }

現在我們來具體看看DoorBuilder、WindowBuilder和WallBuilder配置元素的定義,它們都具有相同的基類ExtensionConfigurationElement<Room>,並且通過重寫CreateExtension方法根據配置創建相應的DoorBuilder、WindowBuilder和WallBuilder。下麵的代碼片斷代表WindowBuilder的配置元素類型WindowBuilderData。

   1: public class WindowBuilderData : ExtensionConfigurationElement<Room>
   2: {
   3:     [ConfigurationProperty("width", IsRequired =false, DefaultValue = 1)]
   4:     public int Width
   5:     {
   6:         get { return (int)this["width"]; }
   7:         set { this["width"] = value; }
   8:     }
   9:     [ConfigurationProperty("height", IsRequired = false, DefaultValue = 1)]
  10:     public int Height
  11:     {
  12:         get { return (int)this["height"]; }
  13:         set { this["height"] = value; }
  14:     }
  15:     public override IExtension<Room> CreateExtension()
  16:     {
  17:         return new WindowBuilder(this.Width, this.Height);
  18:     }
  19: }

WindowBuilder和WindowBuilderData之間的關聯通過應用在WindowBuilder上的ConfigurationElementTypeAttribute特性實現。配置係統之所以能夠將針對WindowBuilder的配置(XML元素)反序列化為WindowBuilderData,就在於這個特殊的ConfigurationElementTypeAttribute特性。大體的原理是這樣的:首先配置係統知道WindowBuilder所在配置是一個NameTypeConfigurationElement(WindowBuilderData的基類),它可以根據type屬性得到WindowBuilder的類型。然後反射得到應用在它上麵的ConfigurationElementTypeAttribute特性,並進一步得到它對應的配置元素類型為WindowBuilderData。於是它就可以正確地將XML元素反序列化成相應的配置元素對象。具體的原理可以參閱《通過自定義配置實現插件式設計》。

   1: [ConfigurationElementType(typeof(WindowBuilderData))]
   2: public class WindowBuilder : IExtension<Room>
   3: {
   4:     //...
   5: }

最後來看看創建Room的工廠類RoomFactory的定義。用於創建CreateRoom方法的實現其實很簡單:根據名稱得到對應Room的配置,然後獲取所有擴展(即XxxBuilder)的配置來創建具體的擴展(還記得ExtensionConfigurationElement<T>的CreateExtension方法嗎?),最後將所有的擴展添加到創建的Room對象的Extensions列表中即可。RoomFactory的整個配置如下:

   1: public static class RoomFactory
   2: {
   3:     public static Room CreateRoom(string name)
   4:     {
   5:         Room room = new Room();
   6:         var roomSettings = RoomManagerSettings.GetCurrent().GetRoomConfigurationElement(name);
   7:         foreach (ExtensionConfigurationElement<Room> element in roomSettings.Extensions)
   8:         {
   9:             room.Extensions.Add(element.CreateExtension());
  10:         }
  11:         return room;
  12:     }
  13: }

來源於WCF的設計模式:可擴展對象模式[上篇]
來源於WCF的設計模式:可擴展對象模式[下篇]


作者:蔣金楠
微信公眾賬號:大內老A
微博:www.weibo.com/artech
如果你想及時得到個人撰寫文章以及著作的消息推送,或者想看看個人推薦的技術資料,可以掃描左邊二維碼(或者長按識別二維碼)關注個人公眾號(原來公眾帳號蔣金楠的自媒體將會停用)。
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁麵明顯位置給出原文連接,否則保留追究法律責任的權利。
原文鏈接

最後更新:2017-10-26 15:03:39

  上一篇:go  VR/AR 時代就在眼前 (二)
  下一篇:go  《WCF服務編程》關於“隊列服務”一個值得商榷的地方