笔者近来发觉一件很有趣的事情,当我对工厂模式中Factory生成工厂类下的switch或者if---else进行解耦的时候,结果就会出现插件式机制的影子。当对解耦方法进一步深入探索的时候,发觉工厂模式跟插件式应用框架很相似。到底这种相似程度如何呢?我们就来一起探讨一下吧。在开始前,我们先得理清思路。在整个工厂模式实现的时候,我们需要有个基接口。在这里,最重要的就是基接口的类型选择,我们到底是选择接口还是选择抽象类呢?
以下是比较定义接口与抽象类适应环境及优缺点(摘自MSDN):
1、接口定义实施者必须提供的一组成员的签名。接口不能提供成员的实现细节。例如,ICollection 接口定义与使用集合相关的成员。实现该接口的每个类都必须提供这些成员的实现细节。类可以实现多个接口。
2、类定义每个成员的成员签名和实现细节。Abstract(在 Visual Basic 中为 MustInherit)类的行为在某方面与接口或普通类相同,即可以定义成员,可以提供实现细节,但并不要求一定这样做。如果抽象类不提供实现细节,从该抽象类继承的具体类就需要提供实现。
3、虽然抽象类和接口都支持将协定与实现分离开来,但接口不能指定以后版本中的新成员,而抽象类可以根据需要添加成员以支持更多功能。
4、优先考虑定义类,而不是接口。在库的以后版本中,可以安全地向类添加新成员;而对于接口,则只有修改现有代码才能添加成员。
5、如果需要提供多态层次结构的值类型,则应定义接口。值类型必须从 ValueType 继承,并且只能从 ValueType 继承,因此值类型不能使用类来分离协定和实现。这种情况下,如果值类型要求多态行为,则必须使用接口。
6、请考虑定义接口来达到类似于多重继承的效果。如果一个类型必须实现多个协定,或者协定适用于多种类型,请使用接口。例如,IDisposable 是由许多不同情况下使用的类型实现的。如果要求从基类继承的类可处置,会使类层次结构很不灵活。MemoryStream 等应从其父类继承基于流的协定的类,不可能还是可处置的。
7、有关设计指南的更多信息,请参见 Krzysztof Cwalina 和 Brad Abrams 编著、Addison-Wesley 于 2005 年出版的“Framework Design Guidelines: Conventions, Idioms, and Patterns for Reusable .NET Libraries”(《框架设计指南:可重用 .NET 库的约定、术语和模式》即《.NET设计规范》)。
此文中,笔者采用接口作为基接口进行实现。即IPublisher接口。
工厂模式中的工厂,其实就是生产实例的一个类。比如此文的例子中为PublisherFactory类。工厂模式,实际上就是定义一个基接口,然后定义其他的类继承于该接口,并实现该接口。然后用一个工厂类封装实例化过程。本文通过定义IPublisher作为基接口,通过TsinghuaUniversityPress和ChinaMachinePress继承该接口并实现该接口。然后再用一个PublisherFactory类工厂进行实例化过程的封装。
代码如下:
///
/// 所有出版社的基接口
///
public interface IPublisher
{
///
/// Publish方法
///
void Publish();
}
///
/// 清华大学出版社
///
public class TsinghuaUniversityPress : IPublisher
{
#region IPublisher 成员
///
/// 实现Publish方法
///
public void Publish()
{
Console.WriteLine("清华大学出版社出版!");
}
#endregion
}
///
/// 机械工业出版社
///
public class ChinaMachinePress : IPublisher
{
#region IPublisher 成员
///
/// 实现Publish方法
///
public void Publish()
{
Console.WriteLine("机械工业出版社出版!");
}
#endregion
}
接下去就是对出版社的实例化进行封装:
///
/// 出版社生成工厂
///
public class PublisherFactory
{
#region 初始化出版社生成工厂
public PublisherFactory()
{ }
public PublisherFactory(enumPublisher enumpublisher)
{
this._enumpublisher = enumpublisher;
}
#endregion
#region 创建出版社实例对象
///
/// 创建出版社实例对象
///
public void CreatePublisherObject()
{
switch (_enumpublisher)
{
case enumPublisher.TsinghuaUniversityPress:
_publisher = new TsinghuaUniversityPress();
break;
case enumPublisher.ChinaMachinePress:
_publisher = new ChinaMachinePress();
break;
default:
break;
}
/* 以下这种方法也可以 */
//if (_enumpublisher == enumPublisher.TsinghuaUniversityPress)
// _publisher = new TsinghuaUniversityPress();
//else if (_enumpublisher == enumPublisher.ChinaMachinePress)
// _publisher = new ChinaMachinePress();
_publisher.Publish();
}
#endregion
private enumPublisher _enumpublisher;
///
/// 出版社枚举
///
public enumPublisher Enumpublisher
{
get { return _enumpublisher; }
set { _enumpublisher = value; }
}
private IPublisher _publisher;
}
然后我们在客户端进行调用实例化的话只要实例化PublisherFactory并对其传入一个枚举参数enumPublisher,之后直接调用PublisherFactory中的CreatePublisherObject方法就可以实现生产实例化的出版社了。代码如下(MainTest.TestProgram):
PublisherFactory publisherFactory1 = new PublisherFactory();
publisherFactory1.Enumpublisher = enumPublisher.TsinghuaUniversityPress;
publisherFactory1.CreatePublisherObject();
PublisherFactory publisherFactory2 = new PublisherFactory(enumPublisher.TsinghuaUniversityPress);
publisherFactory2.CreatePublisherObject();
以上便是一个简单工厂模式实现的过程。
好了,到这里我们要对工厂模型中的switch或者if---else进行解耦了。首先,想象这样一个情景,如果在这里我们增加一个出版社,电子工业出版社(ElectronicsIndustryPress),那么我们就要修改enumPublisher枚举,新增一个ElectronicsIndustryPress枚举并且在工厂类PublisherFactory中增加一个case判断:
case enumPublisher.ElectronicsIndustryPress:
_publisher = new ElectronicsIndustryPress();
break;
这里,很明显的便违反了开放--封闭原则。何为开放--封闭原则?开放--封闭原则就是说软件实体(类、模块、函数等等)应该是可以扩展的,但是不能修改(《大话设计模式》)。对于PublisherFactory这个工厂类,我们要增加一个出版社就必须要修改其CreatePublisherObject方法,这是违反开放--封闭原则的。这样的工厂,我们在对其功能进行扩展的时候,需要改动的地方太多太多了。因此我们需要寻求一种新的扩展机制。
对于软件系统开发来说,需求是最重要的,因为整个系统是围绕着需求进行分析、设计和开发而成的。但是需求又是最最不确定的东西,我们不能指望需求在一开始就确定,并且再也不会发生变化。那么,在开发过程中,我们面对系统需求的改变,系统必须相对灵活、容易修改,或者容易进行功能扩展。这样不至于新需求一来,我们就需要把原先的东西全部推倒重来。 这样不仅降低了系统开发成本,还提高了系统开发效率,可以使我们在新系统推出不久后,很快的再推出新版本的系统。这里就好比我们要增加一个出版社(电子工业出版社),有什么方法可以不改动Factory类工厂的情况下增加一个出版社呢(即对其进行扩展)?.NET下有个非常棒的东东,那就是反射,.NET Framework中我们引用System.Reflection命名空间就可以使用反射这一技术。
反射描述了在运行过程中检查和处理程序元素的功能。他允许我们完成:
1、枚举类型成员
2、实例化新对象
3、执行对象的成员
4、查找类型的信息
5、检查应用于类型的定制特性
6、创建和编译新程序集
当然他的功能不仅仅局限于此,更多关于反射的内容请参看MSDN。
现在我们来实现对Factory类工厂中的switch或者if---else解耦过程实现。运用反射技术实现SimplePluginFactory类的CreatePluginObject方法,代码如下:
///
/// 创建实例插件对象
///
public void CreatePluginObject()
{
//获取路径下所有dll文件名
string[] files = Directory.GetFiles(this._assemblyPath, "*.dll");
foreach (var f in files)
{
//载入dll文件
Assembly _assembly = Assembly.LoadFile(f);
Type[] types = _assembly.GetTypes(); //获取类型
foreach (Type t in types)
{
//进行实例化(如果遍历的类实现IPublisher接口则进行实例化)
_publisher = Activator.CreateInstance(t) as IPublisher;
if (_publisher != null)
{
//调用Publish方法
_publisher.Publish();
}
}
}
}
至此我们如果需要增加出版社(电子工业出版社和人民邮电出版社),只需要出版社实现基接口的Publish方法就行了,在MainTestTestProgram下我们调用方法为:
//新增加的dll的路径
string strPath = Application.StartupPath + "//Plugins//";
SimplePluginFactory simplePlugin = new SimplePluginFactory(strPath);
simplePlugin.CreatePluginObject();
如此简单,不需要管SimplePluginFactory中的CreatePluginObject,而且不用修改enumPublisher枚举,事实上在扩展上用不到这个枚举了。只需要把生成的dll即插件放入应用程序的Plugins文件夹下就行,但是要注意一点,我们增加的插件必须实现IPublisher接口。因为他相当于整个代码中的一个公共契约。读者可以自行试验,比较一下以下两种情况的结果。把示例代码的“MainTest/MainTest/bin/Debug/Plugins”目录下的SimplePlugins.dll文件剪切出来,运行程序显示的结果与该目录下存在SimplePlugins.dll文件的结果进行比较,会发现很有趣的事情。
此时,增加的SimplePlugins.dll就相当于对原先的程序进行了功能扩展,而这个扩展的公共契约就是基接口IPublisher,因此,SimplePlugins.dll对于原先的程序来说就相当于一个功能扩展的插件。而SimplePluginFactory 就是一个生成插件对象的工厂。
到这里,我想肯定会有疑问,为何从原先的类生成工厂到了现在的插件生成工厂?其实,在我看来,插件机制就是工厂模式的升华。插件是一种通过某种规定的契约或接口写出来,并执行相应的操作的程序。即软件功能的扩展模块。对于插件式应用框架来说,对外部提供一些契约使外部可以通过契约生成插件。因此,插件式应用框架很好的体现了开放--封闭原则。
这里先来解释一下框架的含义:框架即支持或围住其他物体的一种基本结构,用作建筑物基础的支撑骨架(《应用框架的设计与实现——.NET平台》)。那么何为应用框架?其实,说简单点,应用框架就是给你提供一个统一的上下文环境的结构和一些可以直接使用的模版提高软件系统的开发效率。
而插件式机制就是对这个应用框架进行功能扩展开发的插件。对于大型应用软件的开发来说,使用插件式应用框架开发方法是中相当不错的选择,现在越来越多的大中型软件都使用上了插件机制:Microsoft Office System、AutoCAD、Visual Studio 2005/2008、Eclipse、QQ等等。其中以Eclipse的平台设计得最为经典。
那么插件式框架的设计有什么好处呢?笔者列出下列6条:
1、不需要重新修改和编译原来的程序代码直接进行功能扩展。
2、只需要公共契约就能扩展程序为程序增加新功能。
3、在需求不断变更的情况下可以方便的增删程序功能。
4、减少程序开发过程中的返工量。
5、降低开发成本。
6、方便团队合作开发。
以下为本文源代码下载地址:http://download.csdn.net/source/1439907