[连载]《C#通讯(串口和网络)框架的设计与实现》- 9.插件引擎设计

简介: 目       录 第九章           插件引擎设计... 2 9.1           框架的契约-接口... 2 9.2           插件的雏形-抽象类... 3 9.3           实现接口... 4 9.4           反射机制... 5 9.5           反射工具类... 8 9.6           小结... 9   第九章     插件引擎设计 在介绍《第10章 宿主程序详细设计》之前对接口和插件的相关内容进行一下整体介绍,在设计宿主程序的时候会用到这些知识,也是宿主程序与插件之间交互的核心内容。

目       录

第九章           插件引擎设计... 2

9.1           框架的契约-接口... 2

9.2           插件的雏形-抽象类... 3

9.3           实现接口... 4

9.4           反射机制... 5

9.5           反射工具类... 8

9.6           小结... 9

 

第九章     插件引擎设计

在介绍《第10章 宿主程序详细设计》之前对接口和插件的相关内容进行一下整体介绍,在设计宿主程序的时候会用到这些知识,也是宿主程序与插件之间交互的核心内容。

9.1    框架的契约-接口

     插件式框架的宿主程序启动后,它首先会加载相应的配置文件(例如:设备驱动配置文件等),找到相应的插件程序集,这些程序集以DLL文件格式存在,框架的宿主程序会找到指定的插件类型,由插件引擎依据插件类型(例如:IRunDevice)生成对象实例,由框架的宿主程序的管理器对插件实例进行管理和调度。

    一个插件程序集可能包括多个插件类型,那么框架宿主程序是如何识别这些类型是否为要加载的插件呢?每个插件对象都有一个身份标识-接口,这个标识在框架设计中被称为“通讯契约”。接口可以被看作是一种定义了必要的方法、属性和事件的集合,因此宿主程序就可以通过这种契约来生成具体的实例对象,并对其他组件或接口公开可操作的对象。

    插件式框架作为一个高聚合低耦合的平台,它的功能定义与功能实现之间是分离的。只要符合插件规范的二次开发组件都可以挂载到框架平台中,而它并不并心这些组件的具体功能。当然,框架平台提供了一些必要的信息、机制来保证这些组件能够正常实现二次开发的功能。

    在具有多个逻辑层次的结构设计中,各层之间的通信大多通过接口来实现,接口不会轻易改变,如果一个层的功能发生变化,不会影响其他层;只要正常实现了接口的组件功能,那么程序的运行就没有问题。这种做法使得各层之间的相互影响降低到最低,总之,接口在多业务层级中能够更好的解耦。

    在大部分功能性的编程和设计工作中,很少需要考虑“接口(interface) ”的情况,如果我们仅仅满足通过控件的方式在IDE上编程和使用.NET Framework中一般的类库,可能永远不会在程序中使用到接口,即使在C#等面向对象语言的语法书中读者会无数次看到过这个词,也只是完成一般性的功能,并未掌握面向对象编程的核心思想。

     接口是一般行为的定义和契约。如猫和狗等动物,只需要将一般性的、公共性的属性、动作等定义在接口里,例如:有眼睛、可以吃东西等。尽管不同动物之间存在很大差异,但是接口并不考虑它们各自的特性或功能的差异,例如:什么颜色的眼睛、吃什么东西等。它只关心这些类型都必须实现接口定义的所有功能,而实现了这个接口就可以被看作是一种动物。

    因此,接口的两个主要的作用是:

n  定义多个类型都需要的公共方法、属性。

n  作为一种不可实例化的类型存在。

继承接口实现定义的方法、属性等,实际上是实现了一种策略。

9.2    插件的雏形-抽象类

接口与抽象类非常相似,例如两者都不能new一个实例对象,却都可以作

为一种契约和定义被使用。但是接口和抽象类有本质的不同,这些不同包括:

n  接口没有任何实现部分,但是抽象类可以继承接口后部分实现代码。

n  接口没有字段,但是抽象类可以包含字段。

n  接口可以被结构(Struct)继承,但是抽象类不行。

n  抽象类有构造函数和析构函数。

n  接口仅能继承自接口,而抽象类可以继承自其他类和接口。

n  接口支持多继承,抽象类仅支持单根继承。

在MSDN的相关内容中,给出了如下关于接口与抽象类的建议:

n  如果预计要创建组件的多个版本,则创建抽象类。抽象类提供简单易行的方法来控制组件版本。通过更新基类,所有继承类都随更改自动更新。另一方面,接口一旦创建就不能更改,如果要更新接口的版本,必须创建一个全新的接口。

n  如果创建的功能将在大范围的全异对象间使用,则使用接口。抽象类应主要用于关系密切的对象,而接口最适合为不相关的类提供通用的功能。

n  如果要设计小而简练的功能模块,应该使用接口。如果要设计大的功能单元,则应该使用抽象类。

n  如果要在组件的所有实现间提供通用的已实现功能,应该使用抽象类。抽象类允许部分实现类,而接口不包含任何成员的实现。

9.3    实现接口

接口和抽象类都可以作为“通信契约”,为子类提供规范。下面定义一个接口和抽象类。

//定义一个接口
public interface IMyInterface
{
       void Action(int type);
       string Method(int para);
}

//定义一个抽象类
public abstract class BaseAbstract:IMyInterface

{
       public abstract void Action(int type); //继承此类抽象类时必须实现这个方法。

       public string Method(int para)         //实现这个方法
       {
              return para.ToString();
       }
}

继承接口的话,需要实现全部定义的方法或属性,如下代码:

public class MyClass1:IMyInterface
{
       public void Action(int type)
       {
              Console.WriteLine(type.ToString());
       }
      
       public string Method(int para)        
       {
              return para.ToString();
       }
}

继承抽象类的话,只需要实现抽象类没有实现的方法或属性,一般为抽象方法或属性,如下代码:

public class MyClass2:BaseAbstract
{
       public void Action(int type)   //继承抽象类,只需要实现这个函数。
       {
              Console.WriteLine(type.ToString());
       }
}

9.4    反射机制

    有了设备驱动或插件,还不能挂载到框架平台的宿主程序中。我们考虑的问题是:已经有了任意多个类型插件程序集,框架平台如何从程序集中根据类型定义在内存中生成插件对象?

   回顾普通情况下程序引用其他程序集组件的过程。首先,需要使用“添加引用”对话框加载程序集。然后,通过using关键字引用命名空间。最后,在命令空间下找到相应的类,并new出来一个实例。这是一种静态加载程序集的方式。

   在插件式应用框架中,这种方法并不适合。宿主程序在编译时并不知道它将要处理哪些程序集,更没有办法静态的将插件类型通过using关键字引入,这些都是在运行时才能获得的信息。在这样的情况下,也无法使用静态方法和new关键字来生成一个类型实例。而是需要在运行时获得相关信息动态加载程序集,这个过程被称为反射。

   反射是动态发现类型信息的一种能力,它类似后期绑定,帮助开发人员在程序运行时利用程序集信息动态使用类型,这些信息在编译时是未知的,反射还支持更高级的行为,如能在运行时动态创建新类型,并调用这些类型的方法等。

    JIT编译器在将IL代码编译成本地代码时,会查看IL代码中引用了那些类型。在运行时,JIT编译器利用程序集的TypeRef和AssemblyRef元数据表的记录项来确定哪一个程序集定义了引用的类型。在 AssemblyRef元数据记录项中记录了程序集强名称的各个部分—包括名称,版本,公钥标记和语言文化。这四个部分组成了一个字符串标识。JIT编译 器尝试将与这个标识匹配的程序集加载到当前的AppDomain中。如果程序集是弱命名的,标识中将只包含名称。

   .NET Framework中,为了实现动态加载,需要熟悉Assembly、Type和Activator等工具类的方法。框架平台主要使用了Assembly工具类,这个类中包括Load、LoadFrom和LoadFile。

1.      Assembly的Load方法

   在内部CLR使用Assembly的Load方法来加载这个程序集,这个方法与Win32的LoadLibray等价。在内部,Load导致CLR对程序集应用一个版本重定向策略。并在GAC中查找程序集,如果没有找到,就去应用程序的基目录,私有路径目录和codebase指定的位置查找。如果是一个弱命名程序集,Load不会向程序集应用重定向策略,也不会去GAC中查找程序集。如果找到将返回一个Assembly的引用,如果没有找到则抛出FileNotFoundException异常。注意:Load方法如果已经加载一个相同标识的程序集只会简单的返回这个程序集的引用,而不会去创建一个新的程序集。

大多数动态可扩展应用程序中,Assembly的Load方法是程序集加载到AppDomain的首选方式。这种方式需要指定程序集的标识字符串。对于弱命名程序集只用指定一个名字。

2.Assembly的LoadFrom方法

    当我们知道程序集的路径的场合,可以使用LoadFrom方法,它允许传入一个Path字符串,在内部,LoadFrom首先调用AssemblyName的静态方法GetAssemblyName。这个方法打开指定的文件,通过AssemblyRef元数据表提取程序集的标识,然后关闭文件。随后,LoadFrom在内部调用Assembly的Load方法查找程序集。到这里,他的行为和Load方法是一致的。唯一不同的是,如果按Load的方式没有找到程序集,LoadFrom会加载Path路径指定的程序集。另外,Path可以是URL。

3.Assembly的LoadFile方法

    这个方法初一看和LoadFrom方法很像。但LoadFile方法不会在内部调用Assembly的Load方法。它只会加载指定Path的程序集,并且这个方法可以从任意路径加载程序集,同一程序集如果在不同的路径下,它允许被多次加载,等于多个同名的程序集加载到了AppDomain中,这一点和上面的两个方法完全不一样。但是,LoadFile并不会加载程序集的依赖项,也就是不会加载程序集引用的其他程序集,这会导致运行时找不到其他参照DLL的异常。要解决这个问题,需要向AppDomain的AssemblyResolve事件登记,在回调方法中显示加载引用的程序集。类似于这样:

AppDomain.CurrentDomain.AssemblyResolve += new ResolveEventHandler(CurrentDomain_AssemblyResolve);
static Assembly CurrentDomain_AssemblyResolve(object sender, ResolveEventArgs args)
{
       if (args.Name != null)
       {
              return Assembly.LoadFrom(string.Format("{0}\\plugin\\{1}.dll", Application.StartupPath, new AssemblyName(args.Name).Name));
       }
       return null;
}

      特别注意:要测试LoadFile有没有加载引用的DLL,切不可将DLL拷贝到应用程序的根目录下测试,因为该目录是CLR加载程序集的默认目录,在这个目录中如果存在引用的DLL,它会被加载,造成LoadFile会加载引用DLL的假象。可以在根目录下新建一个子目录如plugin,把引用的dll拷贝到这里面进行测试。

     反射机制也有它的缺点:安全性和性能方面。但是,框架平台在启动的时候、以及增加新设备驱动(插件)的时候需要使用反射,一旦加载到宿主程序中,与静态引用程序集没有本质区别,都是寄存在内存中。

9.5    反射工具类

插件式框架平台使用反射挂载设备驱动,在宿主程序中运行,需要一个专用的工具类来完成相关功能。代码定义如下:

/// <summary>
/// 一个轻便的 IObjectBuilder 实现
/// </summary>
public class TypeCreator : IObjectBuilder
{
       public T BuildUp<T>() where T : new()
       {
              return Activator.CreateInstance<T>();
       }

       public T BuildUp<T>(string typeName)
       {
              return (T)Activator.CreateInstance(Type.GetType(typeName));
       }

       public T BuildUp<T>(object[] args)
       {
              object result = Activator.CreateInstance(typeof(T),args);
              return (T)result;
       }

       /// <summary>
       /// 框架平台主要使用了这个函数。
       /// </summary>
       /// <typeparam name="T"></typeparam>
       /// <param name="assemblyname"></param>
       /// <param name="instancename"></param>
       /// <returns></returns>
       public T BuildUp<T>(string assemblyname, string instancename)
       {
              if (!System.IO.File.Exists(assemblyname))
              {
                     throw new FileNotFoundException(assemblyname + " 不存在");
              }
              System.Reflection.Assembly assmble = System.Reflection.Assembly.LoadFrom (assemblyname);
              object tmpobj = assmble.CreateInstance(instancename);
              return (T)tmpobj;
       }

       public T BuildUp<T>(string typeName, object[] args)
       {
              object result = Activator.CreateInstance(Type.GetType(typeName), args);
              return (T)result;
       }
}

9.6    小结

    下一章节介绍宿主程序详细设计,需要对反射机制有一定的了解,并且会使用到上面的工具类,并在此基础上进行扩展。

    框架平台就要完全了,只需要一小步了。

 

作者:唯笑志在

Email:504547114@qq.com

QQ:504547114

.NET开发技术联盟:54256083

文档下载:http://pan.baidu.com/s/1pJ7lZWf

官方网址:http://www.bmpj.net

相关文章
|
2月前
|
监控 安全
从 Racket 语言出发,创新员工网络监控软件的框架
在数字化企业环境中,员工网络监控软件对于保障信息安全和提升效率至关重要。Racket 语言凭借其独特特性和强大功能,为开发创新的监控软件提供了新可能。通过捕获和分析网络数据包、记录员工网络活动日志,甚至构建复杂的监控框架,Racket 能够满足企业的定制化需求,为企业信息安全和管理提供强有力支持。未来,基于 Racket 的创新解决方案将不断涌现。
46 6
|
1月前
|
数据采集 存储 JSON
Python网络爬虫:Scrapy框架的实战应用与技巧分享
【10月更文挑战第27天】本文介绍了Python网络爬虫Scrapy框架的实战应用与技巧。首先讲解了如何创建Scrapy项目、定义爬虫、处理JSON响应、设置User-Agent和代理,以及存储爬取的数据。通过具体示例,帮助读者掌握Scrapy的核心功能和使用方法,提升数据采集效率。
113 6
|
2月前
|
机器学习/深度学习 人工智能
类人神经网络再进一步!DeepMind最新50页论文提出AligNet框架:用层次化视觉概念对齐人类
【10月更文挑战第18天】这篇论文提出了一种名为AligNet的框架,旨在通过将人类知识注入神经网络来解决其与人类认知的不匹配问题。AligNet通过训练教师模型模仿人类判断,并将人类化的结构和知识转移至预训练的视觉模型中,从而提高模型在多种任务上的泛化能力和稳健性。实验结果表明,人类对齐的模型在相似性任务和出分布情况下表现更佳。
71 3
|
2月前
|
安全 网络安全 区块链
网络安全与信息安全:构建数字世界的防线在当今数字化时代,网络安全已成为维护个人隐私、企业机密和国家安全的重要屏障。随着网络攻击手段的不断升级,从社交工程到先进的持续性威胁(APT),我们必须采取更加严密的防护措施。本文将深入探讨网络安全漏洞的形成原因、加密技术的应用以及提高公众安全意识的重要性,旨在为读者提供一个全面的网络安全知识框架。
在这个数字信息日益膨胀的时代,网络安全问题成为了每一个网民不可忽视的重大议题。从个人信息泄露到企业数据被盗,再到国家安全受到威胁,网络安全漏洞如同隐藏在暗处的“黑洞”,时刻准备吞噬掉我们的信息安全。而加密技术作为守护网络安全的重要工具之一,其重要性不言而喻。同时,提高公众的安全意识,也是防范网络风险的关键所在。本文将从网络安全漏洞的定义及成因出发,解析当前主流的加密技术,并强调提升安全意识的必要性,为读者提供一份详尽的网络安全指南。
|
3月前
|
存储 SQL 安全
网络安全与信息安全:守护数字世界的坚盾在这个高度数字化的时代,网络安全和信息安全已经成为个人、企业乃至国家安全的重要组成部分。本文将深入探讨网络安全漏洞、加密技术以及安全意识的重要性,旨在为读者提供一个全面的网络安全知识框架。
随着互联网技术的飞速发展,网络安全问题日益凸显。从个人信息泄露到企业数据被盗,再到国家安全受到威胁,网络安全事件层出不穷。本文将从网络安全漏洞的定义与分类入手,探讨常见的网络攻击手段;随后深入解析加密技术的原理及其在保护信息安全中的作用;最后强调提升公众与企业的安全意识的重要性,并提出具体的建议。通过综合运用这些知识点,我们可以更好地构建起一道道坚固的防线,守护我们的数字世界。
|
3月前
|
编解码 分布式计算 网络协议
Netty高性能网络框架(一)
Netty高性能网络框架(一)
|
10天前
|
机器学习/深度学习 算法 PyTorch
基于图神经网络的大语言模型检索增强生成框架研究:面向知识图谱推理的优化与扩展
本文探讨了图神经网络(GNN)与大型语言模型(LLM)结合在知识图谱问答中的应用。研究首先基于G-Retriever构建了探索性模型,然后深入分析了GNN-RAG架构,通过敏感性研究和架构改进,显著提升了模型的推理能力和答案质量。实验结果表明,改进后的模型在多个评估指标上取得了显著提升,特别是在精确率和召回率方面。最后,文章提出了反思机制和教师网络的概念,进一步增强了模型的推理能力。
33 4
基于图神经网络的大语言模型检索增强生成框架研究:面向知识图谱推理的优化与扩展
|
9天前
|
运维 监控 安全
公司监控软件:SAS 数据分析引擎驱动网络异常精准检测
在数字化商业环境中,企业网络系统面临复杂威胁。SAS 数据分析引擎凭借高效处理能力,成为网络异常检测的关键技术。通过统计分析、时间序列分析等方法,SAS 帮助企业及时发现并处理异常流量,确保网络安全和业务连续性。
31 11
|
28天前
|
人工智能 自然语言处理
WebDreamer:基于大语言模型模拟网页交互增强网络规划能力的框架
WebDreamer是一个基于大型语言模型(LLMs)的网络智能体框架,通过模拟网页交互来增强网络规划能力。它利用GPT-4o作为世界模型,预测用户行为及其结果,优化决策过程,提高性能和安全性。WebDreamer的核心在于“做梦”概念,即在实际采取行动前,用LLM预测每个可能步骤的结果,并选择最有可能实现目标的行动。
59 1
WebDreamer:基于大语言模型模拟网页交互增强网络规划能力的框架
|
1月前
|
JSON 数据处理 Swift
Swift 中的网络编程,主要介绍了 URLSession 和 Alamofire 两大框架的特点、用法及实际应用
本文深入探讨了 Swift 中的网络编程,主要介绍了 URLSession 和 Alamofire 两大框架的特点、用法及实际应用。URLSession 由苹果提供,支持底层网络控制;Alamofire 则是在 URLSession 基础上增加了更简洁的接口和功能扩展。文章通过具体案例对比了两者的使用方法,帮助开发者根据需求选择合适的网络编程工具。
29 3