天天看点

.NET插件系统(三) 插件间通信问题——设计可自组织和注入的组装程序

一.  问题的背景

       动态系统的要求之一,是不同模块可以根据自身需求自动组装,这往往通过配置文件或用户选择进行。  这个基本问题在前面的文章中已经讲述过了。

       但新的问题来了,我们定义了不同的插件a,b,c,那么,不同插件之间的通信如何进行?

   如果系统本身的框架非常明晰而且不易更改,那么面向固定接口的方法是最简单方便的。 这也是大部分插件系统在“主结构”上使用的做法。

   但是,如果系统框架本身非常易变,连他们之间交互的接口都会随着问题的不同而不同。这就好像,系统中包含不同种类的插座和插头,我们需要自动将符合要求的插座和插头安装好,实现自动组网。如何实现这种自组织的组装程序呢?

二 . 具体的案例

      为了便于更好的说明问题,以一个我实际面对的设计问题进行分析,实在抱歉由于时间所限不能提供demo.

      我需要开发一个数据挖掘和处理平台,不同的功能通过插件的形式接入系统,并形成如左边的可执行列表:

.NET插件系统(三) 插件间通信问题——设计可自组织和注入的组装程序

      用户可以很简单的通过拖拽,将左边的处理拖到后边的算法执行列表中,当点击"运行"按钮时,列表中的算法模块会被顺序或并行执行。

      这些算法是多样的,比如数据挖掘中常用的  数据筛选,分词,聚类,数据分类显示等功能。你可以不必了解这些算法本身是什么,但本文中,您可能需要了解他们之间的结果可能相互依赖。这些算法的共同特征,是必须依赖于前一个或多个算法的结果,同时本身还可以输出给其他算法。 例如,数据分类显示必须依赖于聚类的结果,聚类则必须依赖于前期分词和数据筛选的功能。

      这种结构很像顺序流动的数据流,或者像电网或自来水网的结构。那么问题来了,系统执行前不知道这些算法到底是什么,那怎么能提供插件间交互的需求?  一种做法是,读写数据库,只要上一个算法告诉下一个算法数据的位置在哪里就可以了,但这种做法很不“环保”,试想,好好的存在内存中的数据,干嘛要写到硬盘中再读出来呢?这会造成无谓的开销。

      另外,算法执行列表(右边)的顺序应该与组装顺序无关,意思是处在数据流上游的模块不一定就在执行列表的上游。

    我们必须设计一套方法,能实现这些算法的相互通信。

 三 . 声明可提供接口和注入接口需求

       首先,为了保证重用,算法模块之间的通信方式只能是接口或抽象类。不论如何,算法应该告诉管理器,它必须依赖什么,它可以提供什么。

       如果一个算法模块可以提供某接口的结果,那么它必须实现该接口。

   如果算法必须依赖某接口,那么它应该最少包含一个该接口的内部成员,或者,也实现之(本文没有考虑这种情况)。

       下面我们简单实现两个类:

        计算方法a可以输出接口b和c,但计算方法b必须得到两个接口b和c的结果。

    两个方法方法非常简单,继承于abstractprocessmethod类,你不需要关心这个类的具体内容,只需注意 test1实现了两个接口ib和ic,这两个接口都能提供两个字符串。test2类则必须获得ib和ic两个接口的字符串成员。

       我们可以通过自定义attribute实现可提供和依赖的接口的标识。 本系统中使用了两个自定义的attribute:

[selfbuildclassattribute(new string[] { }, new string[] { "ib", "ic" })]

        (xfrmworkattribute是插件的标记,详情可见我上一篇关于插件的文章)     

 这两个类的作用已经在注释上写清楚了,您可以结合test1和test2两个类的具体实现来理解:  test1不需要依赖任何接口,但可以输出两个接口ib,ic。  test2方法需要依赖ib和ic两个接口,因此它有两个成员变量,并加上了标记,标记的内容是该接口的名称。

  下面,我们要做的工作,就是在运行时,自动将test1的方法注入到test2的内部接口上。

四 .  实现内部组装

        当用户点击运行时,系统会自动实现接口装配,并按照执行策略执行列表当中的算法模块。

   在我的系统中,所有算法的抽象接口都是iprocess,但在这篇文章中,自组织并不一定需要该接口。系统保存的算法保存在了icollection<iprocess>接口中。而具体装配的方法,则定义在selfbuildmanager中。

     具体的方法请参考代码的注释部分。由于代码注释已经很详细了,因此不做更多解释。

五. 实现和验证

     我们将计算方法a和计算方法b都拖入算法执行列表中:

.NET插件系统(三) 插件间通信问题——设计可自组织和注入的组装程序

     并单击执行按钮:

.NET插件系统(三) 插件间通信问题——设计可自组织和注入的组装程序

      可以看到,接口确实被正确赋值了。设计成功。

六. 必须考虑的问题和扩展点

     虽然设计成功,但系统有一些不可避免的问题:

如果一个需求者发现有不止一个满足该需求的提供者,那么如何选择?目前系统未作此区分,仅仅在找到第一个适配对象后停止搜索。合适的方法是提供用户介入的控制方案,即用户可以用线将不同算法的需求和提供联系起来,当然,该需求暂时有些复杂,如果作者实现了它,一定会公开其方法。

性能和灵活性: 通过反射实现的方法必须讨论性能,好在系统只执行一次装配过程,并尽可能的通过标记简化搜索条件。  但应该研究更好的搜索方法。

该功能的易用性: 作者本人认为该系统是足够易用的,你可以简单地将需求和提供接口的字符串列表标记在类前,并将需求的接口标记在需求方的成员变量前,暂时没有想到更好的做法。

相互依赖问题:一种可能的情况是算法a依赖算法b的结果,算法b依赖a的结果,这种情况一定是不允许的吗?不一定,但若能处理这种需求,就可能实现更强的灵活性,同时带来更复杂的组装逻辑。

   有任何问题,欢迎讨论!

继续阅读