Plug-In系统最重要的特点就是实现在运行时动态加载所需Plug-In对象,使程序解耦,黄忠成介绍Plug-In系统时提出三个要求:
一、对Plug-In对象的声明;
二、Plug-In系统与Plug-In对象的沟通契约;
三、不同Plug-In对象契约有级别之分,针对UserControl的Plug-In对象与针对Form的Plug-In对象的契约是不同的。
由于本人所做项目的业务逻辑需求,经常阶段性增加一些模块,但模块是有着层次性与相似度的。
例如:一份体检报告的一级菜单可能由:一般检查、体格检查、实验室检查、器械检查等组成,针对实验室检查这一项又由血常规、尿检、心电图等不同项目组成。以上不管是一级菜单还是二级项目都有些不同的数据格式与表现形式。
根据医院或者市场需求,新增新的菜单或新的项目是不可避免的,由于这些新增的东西与已存在的功能相对还是独立的,所以如果你作为单独模块再次开发一次,也是无可厚非,也不至于形成意大利面条,但我要说的是,你如何保证代码的重用、更好的维护,一些共性的东西还是会牵一发而动全身。
此时Plug-In系统就是我们需要的,经实践,下述的Plug-In系统已在项目中成功运用。
还是针对黄忠成提出的三个要求,对这个Plug-In系统进行介绍一下:
首先、对Plug-In对象的声明是放在配置文件中,.NET配置文件给我们提供一些自定义的sections配置节,可以在那里面对我们所需要的程序集与类的信息进行描述。
其次、Plug-In系统与Plug-In对象的沟通是存在一个Plug-In对象的父类,该父类采用Template Method对其基本业务逻辑进行描述,除了便于沟通,另一个优点是保持了Plug-In对象该有的共性。
关于Plug-In对象契约级别,这里只对UserControl的Plug-In对象进行介绍。需要说明的是,在此Plug-In定义参考了黄忠成的说法,具体实践还是有一些区别的。
实作时的思路:(部份源代码)
一、根据业务逻辑定义一个Plug-In对象的父类Report_JianCe_Panel,保持好应有的共性:包含一些保护字段给子类使用;包含基本业务流程:采用Template Method包含一些虚方法让子类去覆写。
public partial class Report_JianCe_Panel : UserControl
{
public Report_JianCe_Panel()
{
InitializeComponent();
}
///<summary>
///给子类使用
///</summary>
protected string itsReportID;
///<summary>
///报告单号,给外界使用
///</summary>
public string ItsReportID
{
get { return itsReportID; }
set { itsReportID = value; }
}
protected static Hashtable itsDataSets = new Hashtable();
protected string itsItem;
///<summary>
///所检测的项目
///</summary>
public string ItsItem
{
get { return itsItem; }
set { itsItem = value; }
}
///<summary>
///需要加载的数据源
///</summary>
protected DataSet itsDataSource;
///<summary>
///模板方法,主业务逻辑
///</summary>
public void CreatePanel()
{
//一加载数据源
LoadDataSource();
//二针对数据进行格式化
PaintPanel();
}
protected virtual void LoadDataSource()
{
//共性操作
}
protected virtual void PaintPanel()
{
//共性操作
}
二、根据父类对创建各自业务需求的子类,比如JianCe_Panel_1,覆写父类的虚方法时还需要根据情况决定是否先行调用一下base。
public partial class JianCe_Panel_1 :Report_JianCe_Panel
{
public JianCe_Panel_1()
{
InitializeComponent();
}
///<summary>
///具体实作时,最好使用类型化DataSet
///</summary>
protected override void LoadDataSource()
{
if (!itsDataSets.Contains(this.ItsItem))
itsDataSets.Add(this.ItsItem, LoadJianCe_PanelData());
this.itsDataSource = (DataSet) itsDataSets [this.ItsItem];
}
protected override void PaintPanel()
{
//覆写父类的方法
}
//其他自已的操作与逻辑
}
三、将需要Plug-In对象的类写入配置文件,以后会根据配置文件的信息生成Menu或者TreeView。
<framework>
<sections>
<sectionhandler="程序集名1,类名1"/>
<sectionhandler="程序集名2,类名2"/>
</sections>
</framework>
四、建立一个创建Plug-In对象的类,姑且叫它Report_JianCe_PanelFactory,它的作用是根据配置文件中程序集与类的信息,按需delay load使用reflection创建该Type与Instance,因为reflection时速度是受影响的,所在缓存Assembly是必要的。
1、加载并缓存Type
public static class TypeResolverHelper
{
private static Hashtable _assemblyCaches = null;
///<summary>
///根据配置节信息取得相关程序集
///</summary>
///<param name="assemblyString">配置节的内容</param>
///<returns></returns>
public static Type ResolveType(string assemblyString)
{
if (_assemblyCaches == null)
_assemblyCaches = new Hashtable();
if (assemblyString != null && assemblyString.Length > 0)
{
string[] s = assemblyString.Split(',');
Assembly assem = (Assembly)_assemblyCaches[s[1].Trim()];
if (assem == null)
{
assem = Assembly.LoadWithPartialName(s[1].Trim());
if (!_assemblyCaches.Contains(s[1].Trim()))
{
lock (_assemblyCaches.SyncRoot)
{
_assemblyCaches.Add(s[1].Trim(), assem);
}
}
}
return assem.GetType(s[0]);
}
return null;
}
}
2、加载并缓存具体对象
public sealed class Report_JianCe_PanelFactory
{
//用于存放类名与类型信息
private Dictionary<string, object[]> _plugIns = new Dictionary<string, object[]>();
private static Report_JianCe_PanelFactory _instance;
///<summary>
/// Singleton
///</summary>
public static Report_JianCe_PanelFactory Instance
{
get
{
lock (_lock)
{
if (_instance == null)
_instance = new Report_JianCe_PanelFactory();
return _instance;
}
}
}
///<summary>
///取得反射出来的具体类
///</summary>
///<param name="id"></param>
///<returns></returns>
public Report_JianCe_Panel this[string id]
{
get
{
if (!_plugIns.ContainsKey(id)) return null;
object[] o = (object[])_plugIns[id];
if (o[1] == null)
{
Report_JianCe_Panel obj =
(Report_JianCe_Panel)Activator.CreateInstance((Type)o[0], false);
if (obj != null)
o[1] = obj;//将产生出的对象缓存
}
return (Report_JianCe_Panel)((object[])_plugIns[id])[1];
}
}
public bool Contains(string id)
{
return _plugIns.ContainsKey(id);
}
}
五、主业务逻辑中使用Plug-In对象的父类,并将factory创建出来的实例赋给它。
Report_JianCe_Panel panel = Report_JianCe_PanelFactory["mypanel"];
至此,我们就可以针对panel进行相关操作了,既保持了共性又保持松耦合,利于扩展与维护。
希望各位朋友进行交流与指导!
很少写博客,那位朋友知道如何展折代码,请告之,谢谢!
参考资料:
黄忠成《Framework的设计与应用》