来源于WCF的设计模式:可扩展对象模式[下篇]
在《来源于WCF的设计模式:可扩展对象模式》我通过一个简单的例子介绍了基于IExtensibleObject<T>和IExtension<T>这两个接口为核心的“可扩展对象模式”。在那篇文章中,我是通过编程的方式来应用扩展到扩展对象的。其实,如何能够通过配置的方式来定义扩展,这个所谓的“可扩展对象模式”将会发挥更大的威力。[源代码从这里下载]
目录:
一、将XxxBuilder定义在配置中
二、ExtensionConfigurationElement<T>和ExtensionNameTypeElementCollection<T>
三、XxxBuilder的配置
四、RoomFactory
一、将XxxBuilder定义在配置中
为了给大家对基于配置的扩展有一个初步的印象,我们同样先通过一个具体的例子看看最终实现的效果。同样采用上篇中关于“创建房间”的例子,不过为了真正的展示配置的作为,我们为代表房间构成元素(墙、窗户和门)的类型添加相应的属性。其中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
二、ExtensionConfigurationElement<T>和ExtensionNameTypeElementCollection<T>
现在我们来简单介绍一下具体实现,先来看看表示整个 <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: }
三、XxxBuilder的配置
现在我们来具体看看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: }
四、RoomFactory
最后来看看创建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: }