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

简介:

一.  问题的背景

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

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

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

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

       

二 . 具体的案例

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

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

     

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

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

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

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

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

    

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

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

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

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

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

        计算方法A可以输出接口B和C,但计算方法B必须得到两个接口B和C的结果。

复制代码
 1  [SelfBuildClassAttribute(new string[] { }, new string[] { "IB", "IC" })]
2 [XFrmWorkAttribute("计算方法A", "IDataProcess", "可输出接口B和C", "123")]
3 public class Test1 : AbstractProcessMethod, IC, IB
4 {
5 public string outputC
6 {
7 get;
8 set;
9 }
10
11 public string outputB
12 {
13 get;
14 set;
15 }
16 public override bool DataProcess()
17 {
18 outputC = "已经正确赋值C";
19 outputB = "已经正确赋值B";
20 return true;
21 }
22
23 }
24
25
26
27
28 [SelfBuildClassAttribute(new string[] { "IB", "IC" }, new string[] { })]
29 [XFrmWorkAttribute("计算方法B", "IDataProcess", "必须通过外界提供B和C的接口", "123")]
30 public class Test2 : AbstractProcessMethod
31 {
32 [SelfBuildMemberAttribute("IB")]
33 public IB calledIB { get; set; }
34
35 [SelfBuildMemberAttribute("IC")]
36 public IC callIC { get; set; }
37 public override bool DataProcess()
38 {
39
40 XLogSys.Print.Debug(calledIB.outputB);
41 XLogSys.Print.Debug(callIC.outputC);
42 return true;
43 }
44 }
复制代码

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

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

  • [SelfBuildClassAttribute(new string[] { }, new string[] { "IB", "IC" })]
    两个必选形参,即需求的接口字符串列表 和 提供的接口字符串列表。
  • [SelfBuildMemberAttribute("IC")]
    要求被注入的需求者成员变量标识

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

复制代码
 1  /// <summary>
2 /// 实现自组织算法的特性,它一般标记在模块的类名之前
3 /// </summary>
4 public class SelfBuildClassAttribute:Attribute
5 {
6 /// <summary>
7 /// 要求的依赖项接口
8 /// </summary>
9 public ICollection<string> dependInterfaceCollection
10 {
11 get;
12 set;
13 }
14 /// <summary>
15 /// 可以输出的接口
16 /// </summary>
17 public ICollection<string> outputInterfaceCollection
18 {
19 get;
20 set;
21 }
22 public SelfBuildClassAttribute(string[] dependInterfaceName, string[] outputInterfaceName)
23 {
24 dependInterfaceCollection = new List<string>();
25 outputInterfaceCollection = new List<string>();
26 foreach (string rc in dependInterfaceName)
27 {
28 dependInterfaceCollection.Add(rc);
29 }
30 foreach (string rc in outputInterfaceName)
31 {
32 outputInterfaceCollection.Add(rc);
33 }
34 }
35 public SelfBuildClassAttribute(ICollection<string> dependInterface, ICollection<string> outputInterfaceName)
36 {
37 dependInterfaceCollection = dependInterface;
38 outputInterfaceCollection = outputInterfaceName;
39 }
40 }
41
42
43 /// <summary>
44 /// 自组织成员特性,一般放置在类的 要求注入的成员名上
45 /// </summary>
46 public class SelfBuildMemberAttribute:Attribute
47 {
48         public string invokeName{get;set;}  //需要被注入的依赖项接口
49 public SelfBuildMemberAttribute(string myInvokeName)
50 {
51 invokeName = myInvokeName;
52 }
53 }
复制代码

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

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

 

四 .  实现内部组装

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

       

复制代码
 /// <summary>
/// 可实现自组织的算法模块设计
/// </summary>
/// <typeparam name="T"></typeparam>
public class SelfBuildProcessMethodCollection<T>:ProcessMethodCollection<T> where T:IProcess
{


SelfBuildManager mySelfBuildManager = new SelfBuildManager();
/// <summary>
/// 是否设置自动装配算法模块
/// </summary>
public bool isSelftBuild = true;
protected override bool BeginProcess()
{

mySelfBuildManager.BuildModule(this); //实现接口的自动装配
base.BeginProcess();


return true;


}
}
复制代码

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

复制代码
    public class SelfBuildManager
{
/// <summary>
/// 保存所有依赖接口的字典
/// </summary>
Dictionary<string, IProcess> dependDictinary = new Dictionary<string, IProcess>();
/// <summary>
/// 可提供接口的集合字典
/// </summary>
Dictionary<string, IProcess> outputDictinary = new Dictionary<string, IProcess>();
public void BuildModule(ICollection<IProcess> processCollection)
{
myProcessCollection = processCollection;
GetAllDependExportDictionary(); //获取所有依赖和能提供的接口字典
BuildAttribute(); //实现接口自动组装的方法
}

ICollection<IProcess> myProcessCollection;
private void GetAllDependExportDictionary( )
{
foreach (IProcess rc in myProcessCollection)
{
Type type = rc.GetType();
// Iterate through all the Attributes for each method.
foreach (Attribute attr in
type.GetCustomAttributes(typeof(SelfBuildClassAttribute), false))
{
SelfBuildClassAttribute attr2 = attr as SelfBuildClassAttribute;
foreach (string outputString in attr2.outputInterfaceCollection)
{
outputDictinary.Add(outputString, rc);
}
foreach (string dependString in attr2.dependInterfaceCollection)
{
dependDictinary.Add(dependString, rc);
}
}
}
}


private void BuildAttribute()
{
foreach (KeyValuePair<string, IProcess> dependNeeder in dependDictinary)
{
IProcess outputProvider;
outputDictinary.TryGetValue(dependNeeder.Key,out outputProvider); //在输出字典中找到满足该依赖项的接口
if (outputProvider != null)
{
PropertyInfo[] PropertyInfoArray=dependNeeder.Value.GetType().GetProperties(); ///获取该类的所有属性列表
foreach (PropertyInfo fl in PropertyInfoArray)
{
foreach (Attribute attr in fl.GetCustomAttributes(typeof(SelfBuildMemberAttribute), false))
{
SelfBuildMemberAttribute attr2 = attr as SelfBuildMemberAttribute; //找到自装配成员的标记
if (attr2 != null && attr2.invokeName == dependNeeder.Key)
{
try
{

fl.SetValue(dependNeeder.Value, outputProvider, null); //通过反射,将提供者注入到需求者的变量中
}
catch (System.Exception ex)
{
XLogSys.Print.Error(ex.Message+"无法进行组装");
}

break;

}
}

}

}



}

}
}
复制代码

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

 

五. 实现和验证

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

   

     并单击执行按钮:

     

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

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

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

  1. 如果一个需求者发现有不止一个满足该需求的提供者,那么如何选择?目前系统未作此区分,仅仅在找到第一个适配对象后停止搜索。合适的方法是提供用户介入的控制方案,即用户可以用线将不同算法的需求和提供联系起来,当然,该需求暂时有些复杂,如果作者实现了它,一定会公开其方法。
  2. 性能和灵活性: 通过反射实现的方法必须讨论性能,好在系统只执行一次装配过程,并尽可能的通过标记简化搜索条件。  但应该研究更好的搜索方法。
  3. 该功能的易用性: 作者本人认为该系统是足够易用的,你可以简单地将需求和提供接口的字符串列表标记在类前,并将需求的接口标记在需求方的成员变量前,暂时没有想到更好的做法。
  4. 相互依赖问题:一种可能的情况是算法A依赖算法B的结果,算法B依赖A的结果,这种情况一定是不允许的吗?不一定,但若能处理这种需求,就可能实现更强的灵活性,同时带来更复杂的组装逻辑。

   有任何问题,欢迎讨论!

  

 

         

      

  

      

       

      

      

  


作者:热情的沙漠
出处:http://www.cnblogs.com/buptzym/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。


本文转自FerventDesert博客园博客,原文链接:http://www.cnblogs.com/buptzym/archive/2012/03/25/2416854.html,如需转载请自行联系原作者
目录
相关文章
|
8天前
|
前端开发 C# 开发者
.NET使用Umbraco CMS快速构建一个属于自己的内容管理系统
.NET使用Umbraco CMS快速构建一个属于自己的内容管理系统
25 12
|
8天前
|
Web App开发 前端开发 调度
一款基于 .NET + Blazor 开发的智能访客管理系统
一款基于 .NET + Blazor 开发的智能访客管理系统
|
8天前
|
开发框架 JavaScript 前端开发
精选2款.NET开源的博客系统
精选2款.NET开源的博客系统
|
8天前
|
前端开发 JavaScript C#
基于.NET8+Vue3开发的权限管理&个人博客系统
基于.NET8+Vue3开发的权限管理&个人博客系统
|
1月前
|
算法 Java 测试技术
Benchmark.NET:让 C# 测试程序性能变得既酷又简单
Benchmark.NET是一款专为 .NET 平台设计的性能基准测试框架,它可以帮助你测量代码的执行时间、内存使用情况等性能指标。它就像是你代码的 "健身教练",帮助你找到瓶颈,优化性能,让你的应用跑得更快、更稳!希望这个小教程能让你在追求高性能的路上越走越远,享受编程带来的无限乐趣!
94 13
|
2月前
|
开发框架 安全 Java
.NET技术的独特魅力与优势,涵盖高效的开发体验、强大的性能表现、高度的可扩展性及丰富的生态系统等方面,展示了其在软件开发领域的核心竞争力
本文深入探讨了.NET技术的独特魅力与优势,涵盖高效的开发体验、强大的性能表现、高度的可扩展性及丰富的生态系统等方面,展示了其在软件开发领域的核心竞争力。.NET不仅支持跨平台开发,具备出色的安全性和稳定性,还能与多种技术无缝集成,为企业级应用提供全面支持。
37 3
|
3月前
|
关系型数据库 C# 数据库
.NET 8.0 开源在线考试系统(支持移动端)
【10月更文挑战第27天】以下是适用于 .NET 8.0 的开源在线考试系统(支持移动端)的简介: 1. **基于 .NET Core**:跨平台,支持多种数据库,前后端分离,适用于多操作系统。 2. **结合 Blazor**:使用 C# 开发 Web 应用,支持响应式设计,优化移动端体验。 3. **基于 .NET MAUI**:跨平台移动应用开发,一套代码多平台运行,提高开发效率。 开发时需关注界面设计、安全性与稳定性。
|
3月前
|
Windows
.NET 隐藏/自定义windows系统光标
【10月更文挑战第20天】在.NET中,可以使用`Cursor`类来控制光标。要隐藏光标,可将光标设置为`Cursors.None`。此外,还可以通过从文件或资源加载自定义光标来更改光标的样式。例如,在表单加载时设置`this.Cursor = Cursors.None`隐藏光标,或使用`Cursor.FromFile`方法加载自定义光标文件,也可以将光标文件添加到项目资源中并通过资源管理器加载。这些方法适用于整个表单或特定控件。
|
3月前
|
开发框架 JavaScript 前端开发
一个适用于 ASP.NET Core 的轻量级插件框架
一个适用于 ASP.NET Core 的轻量级插件框架
|
3月前
|
前端开发 JavaScript C#
CodeMaid:一款基于.NET开发的Visual Studio代码简化和整理实用插件
CodeMaid:一款基于.NET开发的Visual Studio代码简化和整理实用插件