.NET实现之(自己动手写高内聚插件系统)
在一本书上是这样解释构件的:构件是可以更换的部件,并且这个部件是由一系列很小的部件组成,同样这些小的部件由更小的部件组成;我为什么要区分插件与构件主要原因是这两个名字所表达的思想不同。插件是可插、可卸的过程,没有强调无限极的递归实现子插件的意思,所以本人将其区分开来;当然也可以将这两种架构用同一名词描述,其实是大同小异了。下面我给大家带来怎么用这种设计思路来开发具体的系统。[王清培版权所有,转载请给出署名]
一:问题分析
在进行开发之前我们需要对整个系统有个分析,插件系统所强调的核心思想是能让所开发出来的系统应变日常需求,在功能升级的时候能很方便的进行更新。但这不是插件系统的最大的好处,我们用传统的三层、MVC开发也能实现这种好处,无非是将DLL文件放到目录下然后在重启就行了。
但是由于插件系统将功能点分的很细,大部分的功能在没有必要的情况下是不需要操作更新的。东西分的越小越好控制,但是开发的成本也随着控制粒度而变大。所以这个平衡点需要我们自己把握,不是所有的项目都适应这种架构。
插件系统是采用面向接口开发而不是面向类开发,在我们系统需求出来之后需要抽取功能点以进行插件抽象。这个时候就是考验一个项目的架构师的设计能力了。设计的不好导致后期开发无法进行下去,这类问题有很多种,比如:接口定义不明确、返回类型不明确、接口的公共部分是否抽象完全,也就是基类实现的是否合理等等;这些问题都很复杂,真正开发大型系统时,这些问题不能马虎,搞不好项目失败。从需求中抽出插件然后进行概要文档的编写、详要文档的编写。在一些大的方面设计文档可能很实用,但是我们程序员知道,一个设计文档不能通用,不是任何系统结构都能相同的设计文档,这就牵扯到了公司的文档编写方面了。如果设计文档无法应付这些复杂的系统结构,可以由架构师编写项目的架构设计文档,只有这样才能让开发人员一目了然,程序员才能发挥自主能动力能力,才能使项目完美收工。
我们刚才讲了,插件系统是采用面向接口设计、开发,也就是面向对象领域所提倡的开发思想。既然我们是以面向接口设计的,那么我们的插件是完全依赖于某些接口,就好比COM一样,你的接口不变,我就能找到你。最大的好处就是如果我的项目是需要第三方去实现的,那么我们的程序集文件DLL不需要签名,而不能由其他人跟换的插件使用签名,这样系统显的很有柔韧性。我喜欢易做图们的开发思想,将自己的项目比作大型的机器人,任何部件是可装配、可更换的。不要将自己的项目开发的那么臃肿,那么脆弱。
插件系统对程序员的自身技术要求也是比较高的,这里面纵横交错,都是需要很深厚的技术功底的。都说这个语言好、那个语言好、只要精通什么都好。这个时候就考验你是否真的掌握了这门语言。语言本身是为了满足某些需求而存在的,JS是为了实现HTMLDOM的交互、CSS是为了修饰HTMLDOM、HTML是一种结构表示语言,这样语言的存在和使用都是有方向的,千万不要把语言和语言相比。由于插件自己的耦合几乎为零,这个时候我们都是通过接口进行调用,比如:我在一个接口里面操作了某些功能,同时这些同能要能及时的反馈到另一个插件中去。这样一个小小的功能,就需要我们运用很复杂的调用关系,任何一步处理的不到位,都会给后期的改动带来麻烦,甚至是灾难性的。
二:真实项目解析
我用了这种结构进行了系统开发,前期的构思是很头疼,但是后期的效果很不错的。
我在“.NET简谈构件系统开发模式”一文中已经进行了基本理论的分析,就不在讲了。直接用代码看吧;
1.主程序实现
在主程序要想使用某个插件的时候我们需要用统一的方法或者说是接口吧,能拿到我这个模块所对应的插件;请看代码:
/// <summary>
/// DataSourceOpen插件接口,上下文使用;
/// </summary>
BaseCome ba易做图e;
/// <summary>
/// 打开SqlServer数据源
/// </summary>
private void Tools_Sqlmenu_Click(object sender, EventArgs e)
{
ba易做图e = NewBaseCome();
(ba易做图e as DataSourceOpen).PassDataEvent += new PassDataHandler(FrmDbServer_PassDataEvent);
ba易做图e.StartCome();
}
private void FrmDbServer_PassDataEvent(List<string> param, params string[] par)
{
if (par.Length > 0)
if (!IsOpenSource(par[0]))
BindTreeView(param, par);
}
这是我的一个菜单的单击事件,这个菜单是主程序中的功能菜单,我需要在主程序中调用相对应的插件;上面的BaseCome是插件基类,实现了所有插件共同的一些特征,便于调用和实现;我在事件中使用了一个NewBaseCome()方法,这个方式是当前窗体中的公共方法,请看代码:/// <summary>
/// 统一获取构件基类
/// </summary>
/// <returns>BaseCome对象</returns>
private BaseCome NewBaseCome()
{
return (PlugManager.PlugKernelManager.MainEventProcess("http://www.emed.cc/CodeBuilderStudio/Details/DataSourceOpen") as BaseCome);
}
我通过这个公共方法获取到当前功能需要用的插件,PlugManager.PlugKernelManager.MainEventProcess()是插件管理器中的一个共有方法,这个方易做图根据你传入的XML命名空间获取配置文件中的插件配置节点名称,你可能会问:“为什么要用这种结构的XML配置文件?”。其实我的个人习惯是使用有结构意义的XML文件,这是其一。其二是,我必须确定插件配置文件的唯一性,由于插件系统支持第三方实现,所以我更本不知道插件的名称是什么,所以我用XML命名空间进行规定。当我需要的时候,我直接通过XML命名空间就能获取到当前插件了。我们一起来看插件管理器的实现,请看代码:2.插件管理器实现
/// <summary>
/// 主程序发生事件,需要启动相应构件
/// </summary>
/// <param name="xmlnamespace">构件所属的命名空间</param>
/// <returns>本构件加载是否成功true:成功,false失败</returns>
public static object MainEventProcess(string xmlnamespace)
{
try
{
PlugDom dom = domcollection[xmlnamespace];
if (dom == null)
throw new System.Exception(
"在系统当前上下文构件集合中未能查找出" + xmlnamespace + "命名空间构件,请检查构件配置文件LoadConfig.xml是否进行了相应的设置;");
ComeLoadEvent(dom.Assembly);//构件初始化成功
return ReflectionDomObject(dom);//通过反射DLL文件,启动实现构件
}
catch (Exception err)
&n
补充:Web开发 , ASP.Net ,