利用.NET的Reflection增强对象工厂的扩展性
对象工厂(Object Factory)模式通常被用来从一个派生系统中产生某个对象,并将其作为基类的实例返回,从而获得基类的接口,并尽量掩盖派生类的细节,以便充分利用面向对象的多态性来获得强大的功能。通常,对象工厂的实现方法是,在一个工厂方法中,先利用一个基于类型标记(type tag)的switch语句找出适当的类型,然后创建该类型的实例并返回之。
举例来说,设想一个图形系统,它包括了线、圆、矩形等元素,这些元素具有一些公共的操作,比如Draw、Resize等。那么我们可能具有下面这样一个继承体系:
为了能够在对象工厂中区分这些类,我们还需要为它们分别指定一个类型标记。这些类型标记可以是Enumeration、整数、字符串等能够唯一地标记这些类的值。用它们的类名字符串作为标记看起来不错。我们可以使用下面这个对象工厂来创建Shape对象:
public sealed class ShapeFactory
{
private ShapeFactory()
{
}
public static BaseShape CreateShape(string shapeID)
{
switch(shapeID)
{
case "Rectangle":
return new Rectangle();
case "Circle":
return new Circle();
case "Line":
return new Line();
default:
return null;
}
}
}
ShapeFacory的唯一用途就是用来创建Shape实例,我们不希望它本身那能够被继承,或者能够被实例化,因此,它被声明为sealed,并具有一个私有构造函数。当我们需要得到某个Shape的实例时,只要调用ShapeFactory的CreateShape()方法,并传入一个适当的shapeID字符串,CreateShape()就会为我们返回正确的Shape实例了。
增强扩展性
现在我们拥有了一个Shape工厂,它工作的不错。但是这个工厂具有一个明显的不足:难以扩充。每当系统中新增加一个Shape类时,我们都不得不修改CreateShape()方法,向其中加入新的case语句。这在我们的产品没发布之前还好,我们可以完全控制我们的代码。但当我们的产品发布之后,用户可以很容易地从BaseShape派生自己的Shape类,但他们却很难利用CreateShape()方法将他们的Shape类加入到系统中,因为他们无法修改CreateShape()方法的实现。因此这个Shape工厂还需要一些扩展性。但是解决这个问题的一个主要的障碍是,如果不使用switch语句,那么CreateShape()将无法预先知道到底存在哪些Shape类,以及类型标记与具体的类之间的关系。
Alexandrescu在他的《Modern C++ Design》中针对这个问题给出了一个C++解法。他在对象工厂类中利用一个std::map来维护类型标记与类型的创建方法之间的关系,并在对象工厂类中增加了两个接口Register()和Unregister(),用来在此map中注册或注销类型标记和类型创建方法。每增加一个Shape类时,需要同时为这个类写一个匿名名字空间,在此空间中调用Register()方法,将自己的类型标记和创建方法注册到对象工厂中。
在C#中,我们可以借鉴Alexandrescu的方法,在对象工厂中利用一个Hashtable来维护类型标记与类型之间的关系,利用Register()方法来注册。每个Shape类必须负责自己的注册工作,因此我们为每个Shape类增加一个RegisterShape()方法,它调用ShapeFactory.Register()来注册自己。但是有两个问题:
1、 对RegisterShape()方法的调用应当何时进行呢?
2、 C#并不支持匿名名字空间,那么如何来调用每个Shape类的RegisterShape()方法呢?
对于第一个问题,我们有一个时机,那就是在CreateShape()方法被调用之前,各个Shape类必须已经完成了注册。在静态构造函数中完成对RegisterShape()的调用到是个不错的选择。
对于第二个问题,我们可以使用Reflection机制,首先遍历所有的类型,找出由BaseShape派生的类型,然后分别调用它们的RegisterShape()方法。
按照以上思路,ShapeFactory的实现代码如下:
public sealed class ShapeFactory
{
private static Hashtable _creationMap = null;
static ShapeFactory()
{
_creationMap = new Hashtable();
Assembly a = typeof(ShapeFactory).Module.Assembly;
Module[] modules = a.GetModules();
for(int i = 0; i < modules.Length; i++)
{
Type[] types = modules[i].GetTypes();
for(int j = 0; j < types.Length; j++)
{
if(!types[j].Equals(typeof(BaseShape))
&& types[j].BaseType != null
&& types[j].BaseType.Equals(typeof(BaseShape)))
{
Object obj = types[j].InvokeMember(null,
BindingFlags.DeclaredOnly |
BindingFlags.Public |
BindingFlags.Instance |
BindingFlags.CreateInstance,
null, null, null);
types[j].InvokeMember("RegisterShape",
BindingFlags.DeclaredOnly |
BindingFlags.Public | BindingFlags.NonPublic |
BindingFlags.Instance | BindingFlags.InvokeMethod,
null, obj, null);
}
}
}
}
public static void Register(string shapeID, Type shape)
{
if(!_creationMap.ContainsKey(shapeID))
_creationMap.Add(shapeID, shape);
}
public static BaseShape CreateShape(string shapeID)
{
Type shape = (Type)_creationMap[shapeID];
if(shape == null)
return null;
return (BaseShape)shape.InvokeMember(null,
BindingFlags.DeclaredOnly |
BindingFlags.Public |
BindingFlags.Instance | BindingFlags.CreateInstance,
null, null, null);
}
}
在CreateShape()方法第一次被调用之前,静态构造函数会先执行。它利用Reflection机制遍历所有的类型,对于由BaseShape派生的类型,先创建一个实例,然后调用其RegiserShape()方法,将其在Hashtable中注册。当CreateShape()方法被调用时,根据传入的shapeID从Hashtable中取出相应的类型,并返回其实例。这样,我们就有了一个具有一定扩展性的Shape工厂。
总结
在对象工厂中增加Reflection机制,可以在一定程度上增强对象工厂的扩展性。改进后的ShapeFactory不用再在每次增加了Shape类之后进行修改了,只要新加入的Shape类实现了RegiserShape()方法,它就能够被注册到对象工厂中,并被正确地创建。这样,我们甚至可以方便地为我们的系统实现插件功能。比如,我们可以指定一个插件目录,遍历这个目录,将其中的Shape类注册到工厂中。用户只需将他们的插件拷贝到这个目录下即可。
当然,Reflection也许并不是此问题的最佳解决方案。它需要遍历系统中所有的类型,执行效率不够高。还好静态构造函数只会被执行一次。希望本文能够起到抛砖引玉的作用。如果您有更好的方案,欢迎和我交流。我的联系方式:sam1111@citiz.net
下一篇:.NET框架中基于角色的安全性(3) >>
相关文章:
- · 《.NET软件技术学习与实践》之序言
- · .net remoting范例
- · .net缓存应用与分析
- · 一种改进的轻量级.NET应用程序性能测试框架
- · 用.NET创建Windows服务
- · .net中清除EXCEL进程最有效的方法
- · 初探.NET中的delegate类型与.NET事件
- · .NET中自己构建一个ArrDictionary
- · 使用.NET生成Excel文件
- · 根据函数名称定位函数
- · .Net项目制作安装程序
- · 使用.net下的系统事件增强应用程序
- · Dotnet总结(4)--xml读写
- · Dotnet总结(3)--打印
- · Dotnet总结(2)--访问ms sql server 数据库基类--2
- · 如何使用.NET配置文件(一)
- · 将.aspx文件和图片编译进dll
- · .net如何实现页面间的参数传递
- · Microsoft .NET 中的简化加密
- · .Net远程方法调用研究
- · .net 里面 protected private 的变量也可以访问
- · 构建基本的.NET Remoting应用程序
- · 让你的.NET程序兼容不同版本的Dll文件
- · 谈Microsoft .NET战略
- · .net中xmlhttp下载文件的方法参考
- · .net Compact Flamework中MD5CryptoServiceProvider的实现
- · 把.NET程序部署到没有安装.NET Framwork的机器上
- · 在.NET下编写中文代码程序
- · ADO 与ADO.NET
- · C#异步数据接收串口操作类
- · 用 .NET 开发的轻量级 UI 测试自动化
- · .net下开发COM+组件
- · 深入理解.NET 的JIT编译方式
- · .Net配置文件常用配置说明
- · 完整的在.net后台执行javascript脚本集合
- · .net手机软件开发(六)OBEX应用——文件传输部分
- · .net手机软件开发(5)——OBEX介绍
- · .NET Framework General Reference - Capitalization Styles
