第一章 【C#进阶系列】【MEF框架(一)】
这里对MEF作了基本的介绍,包括使用了一个特定场景(搞自动化运控上位机开发的应该更容易代入场景了),一步一步地介绍了如果从常用的编程过渡到框架性的编程开发。
我们先假设一个场景:在运动控制上位机编程开发中,有固高、雷赛两种运动控制卡,现在我们需要根据实际设备使用哪种卡来实现不同的编程。
常规的实现:
class Program
{
static void Main(string[] args)
{
string ret = "";
string cardName = "实际设备使用哪种运动控制卡";
if (cardName == "固高板卡")
{
CLeadshine cLeadshine = new CLeadshine();
ret = cLeadshine.InitMotionCard();
ret = cLeadshine.ExitMotionCard();
}
else if (cardName == "雷赛板卡")
{
CGoogol cGoogol = new CGoogol();
ret = cGoogol.InitMotionCard();
ret = cGoogol.ExitMotionCard();
}
}
}
public class CLeadshine
{
public string InitMotionCard()
{
return "初始化固高板卡成功";
}
public string ExitMotionCard()
{
return "退出固高板卡成功";
}
}
public class CGoogol
{
public string InitMotionCard()
{
return "初始化雷赛板卡成功";
}
public string ExitMotionCard()
{
return "退出雷赛板卡成功";
}
}
看了上述代码,可以想象一下,里面存在一个主运行程序MEF_P1.exe、一个Leadshine.dll,一个Googol.dll,主程序需要引用两个DLL。
上述代码没有使用接口,违背了依赖倒置原则,接下来我们增加一个接口MotionCard.dll,让Leadshine.dll和Googol.dll都继承该接口,来保证使用的通用性、规范性;
代码实现如下:
class Program
{
static void Main(string[] args)
{
string ret = "";
string cardName = "实际设备使用哪种运动控制卡";
IMotionCard motionCard = null;
if (cardName == "固高板卡")
{
motionCard = new CLeadshine();
}
else if (cardName == "雷赛板卡")
{
motionCard = new CGoogol();
}
ret = motionCard.InitMotionCard();
ret = motionCard.ExitMotionCard();
}
}
public interface IMotionCard
{
string InitMotionCard();
string ExitMotionCard();
}
public class CLeadshine : IMotionCard
{
public string InitMotionCard()
{
return "初始化固高板卡成功";
}
public string ExitMotionCard()
{
return "退出固高板卡成功";
}
}
public class CGoogol : IMotionCard
{
public string InitMotionCard()
{
return "初始化雷赛板卡成功";
}
public string ExitMotionCard()
{
return "退出雷赛板卡成功";
}
}
可见,在使用接口后,只需要根据不同实现类实例化接口,更适用于团队的协助开发;
但仅仅是这样是不满足的,在复杂的系统设计中,不仅要考虑团队协助开发,也要想办法实现松耦合,以便日后扩展等维护;
如要团队中另外一个人新增一个正运动的板卡怎么操作?
按目前的做法是:
1.增加一个ZMotion.dll,继承IMotionCard接口,实现具体代码;
2.在主程序中,引用ZMotion.dll,增加“正运动板卡”的IF分支,并使用CZMotion实现类实例化接口;
接下来我们开始引入MEF的概念,看看MEF是如何操作的,相比较而言是否实现了松耦合,有哪些实际应用的优势。
MEF(Managed Extensibility Framework)使开发人员能够在其.Net应用程序中提供挂钩,以供用户和第三方扩展。可以将MEF看成一个通用应用程序扩展实用工具。
MEF使开发人员能够动态创建扩展,无需扩展应用程序,也不需要任何特定于扩展内容的知识。这可以确保在编译时两者之间不存在耦合,使应用程序能够在运行时扩展,不需要重新编译。
MEF还可以在将扩展加载到应用程序之前,查看扩展程序集的元数据,这是一种速度更快的方法。
一些特定名词的说明:
Contract:契约,即一种约定
Part:部件,即契约的实现(通过Export特性加入到目录中)
Catalog:目录,存放部件的地方,当需要某个部件时,会在目录中寻找
Container:容器,存放目录并进行部件管理,如导出、导入等
Compose:组装,通过容器在目录中寻找到实现相应契约的部件,进行部件的组装
Export:导出,是部件向容器中提供的一个值,可装饰类、字段、属性或方法
Import:导入,从容器中获得可用的导出,可装饰字段、属性或构造函数参数
ImportMany:导入多个
DirectoryCatalog: 指定目录所在的路径。
Lazy
还是在上面的场景中修改。我们增加MEF的应用,让主程序MEF_P1.exe不依赖于Leadshine.dll和Googol.dll,而是根据ContractName(契约名称)在指定的路径中寻找DLL文件,实现相同的功能,从而解决了上层依赖下层的强耦合问题。
代码结构如下:
主程序MEF_P1.exe
class Program
{
static string DllFilePath = "";
static string ContractName = "";
static void Main(string[] args)
{
string ret = "";
string cardName = "实际设备使用哪种运动控制卡";
cardName = "固高板卡";
if (cardName == "固高板卡")
{
DllFilePath = "Googol.dll";
ContractName = "GoogolCard";
}
else if (cardName == "雷赛板卡")
{
DllFilePath = "Leadshine.dll";
ContractName = "LeadshineCard";
}
ret = InitMotionCard();
ret = ExitMotionCard();
}
public static string InitMotionCard()
{
return ExportPart(ContractName, DllFilePath).InitMotionCard();
}
public static string ExitMotionCard()
{
return ExportPart(ContractName, DllFilePath).ExitMotionCard();
}
public static IMotionCard ExportPart(string ContractName, string DllName)
{
// 创建一个程序集目录,用于从一个程序集获取所有的组件定义
//Assembly.GetExecutingAssembly():当前方法所在程序集
//Assembly.GetCallingAssembly():调用当前方法的方法 所在的程序集
//Assembly.LoadFrom(@"DLLName.dll"):DLLName.dll的程序集
AssemblyCatalog Catalog = new AssemblyCatalog(Assembly.LoadFrom(DllName));
//创建容器
CompositionContainer Container = new CompositionContainer(Catalog);
//获得容器中的Export
IMotionCard Export = Container.GetExportedValue<IMotionCard>(ContractName);
return Export;
}
}
接口MotionCard.dll
public interface IMotionCard
{
string InitMotionCard();
string ExitMotionCard();
}
插件Googol.dll
[Export("GoogolCard", typeof(IMotionCard))]
public class CGoogol : IMotionCard
{
public string InitMotionCard()
{
return "初始化固高板卡成功";
}
public string ExitMotionCard()
{
return "退出固高板卡成功";
}
}
插件Leadshine.dll
[Export("LeadshineCard", typeof(IMotionCard))]
public class CLeadshine : IMotionCard
{
public string InitMotionCard()
{
return "初始化雷赛板卡成功";
}
public string ExitMotionCard()
{
return "退出雷赛板卡成功";
}
}
这样,当需要新增一个正运动的板卡怎么操作?
1.增加一个ZMotion.dll,继承IMotionCard接口,实现具体代码;
2.在主程序中,增加“正运动板卡”的锲约名和DLL路径;
完了?还没,这时候还是需要修改主程序,还不是我们想要的,但是到了这一步,效果已经很明显了,接下来
我们把契约名和DLL名称从程序中剥离到配置文件中,并且设计可视化界面提供新增和修改就可以了
这样,当需要新增一个正运动的板卡怎么操作?
1.增加一个ZMotion.dll,继承IMotionCard接口,实现具体代码;
2.更改配置文件或者在可视化界面更改配置
这样就无需更改MEF_P1.exe的代码也无需重新编译,ZMotion.dll相当于一个插件,只通过更改配置文件即可加载到应用程序中。
最终效果如下图,当需要增加ZMotion.dll时,写好ZMotion.dll放到指定路径中,修改config.xml配置文件,增加Item3信息,然后就可以直接打开软件,软件下拉框就出现了正运动板卡了,可选中操作。
解耦
试想下,如果主程序引用了Leadshine.dll和Googol.dll,那么就意味着你可以无限制的开放DLL中类,属性,方法的访问权限,也就意味着主程序中会出现很多耦合的代码,哪天你想移除这个DLL,程序肯定编译失败,而且你要手动删除这些耦合代码,而MEF因为是通过中间接口来完成调用的,所以只向外暴露了接口里面的成员,程序员是无法任意调用DLL中的任何方法,只能通过接口来调用。就算删掉这个DLL,程序也能正常运行!
可扩展性
举个例子,假设你的程序已经移交给客户了,哪天客户说我不想用固高、雷赛的板卡了,我要用正运动的板卡,这时,你只需重新一个ZMotion.dll,使其继承并实现IMotionCard接口,然后,只要将ZMotion.dll和改好的配置文件交给客户,并让其修改选择选项,就能运行使用正运动板卡了。是不是很方便!如果是以前,必须要修改主程序的代码,然后重新编译,发布,再将整个程序移交客户,这样说大家应该都明白了!
更多
MEF不仅可以导出类,还可以导出方法,属性,不管是私有还是公有,从而满足更多的需求!
MEF插件框架学习展示(源代码)(一)
依赖倒置原则:高层模板不应该依赖于底层模板,两者应该依赖于抽象,而抽象不应该依赖于细节。