工厂函数、动态加载、效率,一个都不能少!

熟悉设计模式的朋友们都知道,有一种叫“工厂函数”(Factory function)的模式:通过Invoke一个函数,来生成一个“某种类”的实例,而不是将实例化硬编码到代码里。这样的模式带来了优秀的扩展性,也避免了大量的重复劳动——直接用一个基类指针来“接住”工厂生产的新对象,vtable一跳,多态的意味就出来了 :-)

工厂函数的能力不止于此。有时需要对用户隐藏派生类的类型(一个完全不过分的条件,不是吗?),此时,将上图中Generate函数的参数除去即可。这时新实例的类型将取决于生成函数内部的逻辑,也就是说生成函数可以根据调用时的上下文环境不同而返回不同的派生类。

 

很强大,不是吗?但是笔者最近在使用这种设计模式的时候却又遇到了新的挑战——当派生类的类型对于工厂函数本身来说也是不透明的的时候,该怎么办?

这样讲可能还是有些抽象,举一个具体的例子吧,当系统中规定了基类的接口,而派生类以dll的形式存在的时候,工厂函数就完全不知道该怎么去产生新对象了。

幸运的是,C#提供了很强大的动态加载能力——使用System.Reflection命名空间中的Assembly类加载dll,再使用CreateInstance来生成对象,例如下面这段代码:

//I hate writing code with Bo Ke Yuan code editor! :-(
System.Reflection.Assembly assembly = System.Reflection.Assembly.LoadFile(@"Blablabla.dll");
// Sorry I'm not sure whether namespace is needed
// in assembly.GetType(..). Probably not?
System.Type typeOfTheClass = assembly.GetType("Namespace.ClassType");
BaseClass holder
= assembly.CreateInstance(typeOfTheClass.FullName);
//Done.

于是,可以在初始化时让工厂加载dll,让这个assembly一直驻留在工厂中。每次用户调用生成函数的时候,就用反射机制得到一个实例。

将这段代码直接塞进工厂中,看起来问题就这样解决了。但是大量测试与Profiling表明,Reflection很慢,不是一般的慢,是真的真的很慢……经验性的描述是,做一次CreateInstance的代价和在一个有1M个元素的Dictionary中做一次查找不相上下。如果要生成大量的实例的话,代价不是一般的昂贵。

Ok,一旦开始追求效率,程序员的野性,爆发力,想象力,甚至诗意,什么东西都出来了——大概这就是“本性”吧。。。经过一些思考以及可乐与香烟的启发,笔者终于想出了一个办法——使用双重工厂函数

第一重工厂函数和以往的任何函数一样,该怎么写还是怎么写。第二重工厂函数直接内置在基类的接口里,并规定派生类必须实现“New一个自己的实例”这样的接口。这样只要工厂中有一个派生类的实例(不妨叫它Master Instance),后续的制造过程就变成了工厂要求师傅实例源源不断地生产徒弟的过程。

在这里需要注意的一点是,由于静态函数是无法继承的,所以在工厂中保存一个派生实例时一个必须的过程。这样的话,工厂必须使用上面那段代码在初始化的时候先用反射生成一个师傅实例(这么昂贵,我必须叫它师傅 :-) ),用基类指针抓住它以供日后之需。

一个简单的实例如下:(抱歉,机器上没有Visual Studio,直接用博客园代码编辑器写的代码不保证完全正确……)

abstract class BaseClass
{
abstract SoMeMeThOdS(...);
abstract BaseClass Generate();
};

class Factory
{
// Must be invoked before calling Generate()
static void LoadClass(string fileName)
{
assembly
= System.Reflection.Assembly.LoadFile(fileName);
System.Type typeOfTheClass
= assembly.GetType("Namespace.ClassType");
master
= assembly.CreateInstance(typeOfTheClass.FullName)
}
static BaseClass Generate()
{
return master.Generate();
}
static BaseClass master = null;
System.Reflection.Assembly assembly
= null;
};

如此一来,反射操作只用进行一次,剩下的生成工作就交给了派生类。经测试,效率大大提升——工厂函数、动态加载、执行效率,一个都不能少!

 

posted @ 2011-09-17 02:19  SouthSeven  阅读(4196)  评论(7编辑  收藏  举报