C#插件构架实战(Jack H Hansen)

导读:
  1. 问题的引入
  假设你设计的程序已经部署到用户的计算机上,并且能够正常运行了。但是有一天,用户打来了电话——他们要求增加新的功能。确定了用户的需求后,你竟然发现原有的软件架构已经无法胜任新增任务的需求——你需要重新设计这个应用了!但问题是,就算你又用了一个开发周期完成了用户需要的应用,却不能保证用户的需求不会再次变更。也就是说,需求蔓延的可能性依然存在。因此,这种情况下插件构架更能显示出它的优越性。
  2. 几个解决方案的对比
  我总结了一下我所接触到的插件构架,大致上可分为以下几类:


  i> 脚本式
  使用某种语言把插件的程序逻辑写成脚本代码。而这种语言可以是 Python ,或是其他现存的已经经过用户长时间考验的脚本语言。甚至,你可以自行设计一种脚本语言来配合你程序的特殊需要。当然,用当今最流行的 XML 是再合适不过了。
  这种形式的特点在于,稍有点编程知识的用户就可以自行修改你的脚本( ^_^ 假如你不加密它的话)。我们无法论证这是好处还是坏处。因为,这种情况所造成的后果是不可预知的。


  ii> 动态函数库 DLL
  插件功能以动态库函数的形式存在。主程序通过某种渠道(插件编写者或某些工具)获得插件DLL 中的函数签名,然后在合适的地方调用它们。用过 Matlab 的读者都知道, Matlab 中的各项功能几乎都是些动态链入的函数。


  iii> 聚合式
  顾名思义,就是把插件功能直接写成 EXE 。主程序除了完成自己的职责外,还负责调度这些“插件”。我不喜欢这种形式。这使插件插件之间,主程序与插件之间(主要是这一点)的信息交流困难了许多。巴比伦塔的失败[1] 从某种程度上讲就是信息交流无法实现造成的。


  iv> COM 组件
  COM[2] 的产生给这个世界增添了几分活力。只有接口!我们的插件需要做的只是实现程序定义的接口。主程序不需要知道插件怎样实现预定的功能,它只需要通过接口访问插件,并提供主程序相关对象的接口。这样一来,主程序与各插件之间的信息交流就变得异常简单。并且,插件对于主程序来说是完全透明的。


  3. 决策
  C#是面向对象的程序设计语言。它提供了 interface 关键字来直接定义接口。同时, System.Reflection 命名空间也提供了访问外部程序集的一系列相关对象。这就为我们在 C#中实现插件构架打下了坚实的基础。
  下面,我们将以一个具有插件构架的程序编辑器为例,来阐述这种构架在 C#中的实现。

 

 


  二、设计过程
  好了,现在我们准备把所有的核心代码都放在 CSPluginKernel命名空间中。用VSIDE建立一个C#类库工程。在命名空间 CSPluginKernel 中开始我们的代码。


  1. 接口设计
  我们的程序编辑器会向插件开放正在编辑的文档对象。程序启动后,就枚举每一个插件并把它连接到主程序,同时传递主程序对象的接口。插件可以通过这个接口来请求主程序对象或访问主程序功能 。
  根据上面的需求,我们首先需要一个主程序接口:
  public interface IApplicationObject {
  void Alert( string msg ); // 产生一条信息
  void ShowInStatusBar( string msg ); // 将指定的信息显示在状态栏
  IDocumentObject QueryCurrentDocument(); // 获取当前使用的文档对象
  IDocumentObject[] QueryDocuments(); // 获取所有的文档对象
  // 设置事件处理器
  void SetDelegate( Delegates whichOne , EventHandler targer );
  }
  // 目前只需要这一个事件
  public enum Delegates {
  Delegate_ActiveDocumentChanged ,
  }
  然后是 IDocumentObject 接口。插件通过这个接口访问编辑器对象。
  ///
  /// 编辑器对象必须实现这个接口
  ///
  public interface IDocumentObject {
  // 这些属性是 RichTextBox 控件的相应的属性映射
  string SelectionText { get set }
  Color SelectionColor { get set }
  Font SelectionFont { get set }
  int SelectionStart { get set }
  int SelectionLength { get set }
  string SelectionRTF { get set }
  bool HasChanges { get }
  void Select( int start , int length );
  void AppendText( string str );
  void SaveFile( string fileName );
  void SaveFile();
  void OpenFile( string fileName );
  void CloseFile();
  }
  这个接口不需要过多解释。这里我只实现了RichTextBox控件少数的几个方法,其他可能用得到的,读者自行添加即可。
  再然后,根据插件在其生命周期里的行为,设计插件的接口。
  ///
  /// 本程序的插件必须实现这个接口
  ///
  public interface IPlugin {
  ConnectionResult Connect( IApplicationObject app );
  void OnDestory();
  void OnLoad();
  void Run();
  }
  ///
  /// 表示插件与主程序连接的结果
  ///
  public enum ConnectionResult {
  Connection_Success ,
  Connection_Failed
  }
  主程序会首先调用 Connect() 方法,并传递 IApplicationObject 给插件插件在这个过程中做一些初始化工作。然后,插件的 OnLoad() 方法被调用。在这之后,当主程序接收到调用插件的信号时(键盘、鼠标响应)就会调用插件的 Run() 方法来启动这个插件。程序结束时,调用其 OnDestory() 方法。这样,插件的生命才宣告结束。

 


  2. 插件信息的存储与获取
  一个插件需要有它的名称 、版本等信息。作为设计者的你,也一定要留下你的尊姓大名和个人网站等用来宣传自己。 C#的新特性——属性, 就是一个很好的解决方案。因此我们定义一个从 System.Attribute 继承来的类 PluginInfoArrtibute :

本文转自
http://cache.baidu.com/c?word=%B2%E5%BC%FE%2Cc%23&url=http%3A//www%2Ecnblogs%2Ecom/jasononline/archive/2007/06/04/770837%2Easpx&p=882a954384d808ed2abe9b7f0d539f&user=baidu

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值