对象工厂
对象工厂(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的Reflection增强对象工厂的扩展性-.NET教程,.NET Framework
版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com 特别注意:本站所有转载文章言论不代表本站观点! 本站所提供的图片等素材,版权归原作者所有,如需使用,请与原作者联系。未经允许不得转载:IDC资讯中心 » 利用.NET的Reflection增强对象工厂的扩展性-.NET教程,.NET Framework
相关推荐
-      VS2010的aspx文件中的html代码的格式化方法
-      .net 反序题目的详细解答第1/2页
-      asp.net创建html文本文件实例
-      比较完整的 asp.net 学习流程
-      官网 Ext direct包中.NET版的问题
-      C# XML操作 代码大全(读XML,写XML,更新,删除节点,与dataset结合等)第1/2页
-      c# 连接字符串数据库服务器端口号 .net状态服务器端口号
-      asp.net教程:简单的C#图片上传代码或C#文件上传代码