【实践】基于接口的插件机制

1、前言

插件,意味着可扩展,且宿主程序不依赖于插件,即插即用。这种软件设计方式可使咱们的应用程序最大化地得到可扩展性、适应性和稳定性,并且便于软件的维护和升级。在什么场景下使用插件呢?例如在本篇文章中,我我的有一个小需求就是但愿记事本带行号,因而我本身写了一个极简易的编辑器CodeEditor,以这个编辑器为例,主体程序功能包括常见的新建、复制、查找、保存等已经完成,可是在使用的过程当中发现须要用到 格式化 这个功能,可是我还不想再去改主程序,这种情形下就能够经过插件来实现,这样之后在使用的时候,只要有新的需求就能够经过新增插件来实现,从某种程度上讲这也符合了开放-封闭的设计原则。下面对插件的定义来自百度百科。html

插件(Plug-in)是一种遵循必定规范的应用程序接口编写出来的程序。其只能运行在程序规定的系统平台下(可能同时支持多个平台),而不能脱离指定的平台单独运行。git

2、插件机制实现原理

实现插件机制的两大要素:一个是接口,另外一个是反射。接口实际上是一种“契约”,主程序是经过这种“契约”来约束是否存在符合我指望的对象,若是不符合就不会去加载该对象。在CodeEditor中咱们约定的接口是IExcutable。而这种“契约”的执行就是经过反射来达到目的,主程序中会经过反射加载约定好的Plugin文件夹下全部的DLL文件,而后遍历这些插件并查看是否存在实现了IExcutable的而且能够实例化的类,若是有则建立该类的实例加入集合并返回集合。主程序拿到集合后会在构造函数中加载这些插件,加载过程包括动态添加菜单、指定菜单的点击事件,这样完整的插件加载过程就完成了。下面经过CodeEditor来具体看下插件的实现过程。github

3、插件机制的实践

 下面的图是整个CodeEditor的目录结构小程序

第三个CodeEditorControl能够忽略,这个类库是一个自定义的控件,是实现一个带行号的文本编辑器的核心组件,可是和本文主题关系不大。主要看插件接口CodeEditorInterface和插件实现CodeEditorPlugins以及主程序CodeEditor。这三者的关系能够经过如下图片来展现。app

首先从主程序和插件之间的桥梁入手,就是插件的接口,在CodeEditorInterface中的接口IExcutable中有两个约定方法,一个是GetName负责返回当前的插件名称,用于主程序获取并动态加载到菜单中;另外一个是Excute负责获取主程序中文本并执行相应的操做。代码以下:编辑器

1 public interface IExcutable
2 {
3     //用于主程序动态建立菜单
4     string GetName();
5     //执行具体的文本操做
6     string Excute(string text);
7 }

下面是主程序加载符合“契约”的插件对象的核心代码,主要做用就是过滤符合接口的类并实例化类的对象,加到集合中:函数

 1 public class Common
 2 {
 3     /// <summary>
 4     /// 加载插件
 5     /// </summary>
 6     /// <returns></returns>
 7     public static List<IExcutable> GetPlugins()
 8     {
 9         List<IExcutable> implementObject = new List<IExcutable>();
10         //获取项目根目录下的Plugins文件夹
11         string dir = GetPluginsDir();
12         //遍历目标文件夹中包含dll后缀的文件
13         foreach (var file in Directory.GetFiles(dir + @"\", "*.dll"))
14         {
15             //加载程序集
16             var asm = Assembly.LoadFrom(file);
17             //遍历程序集中的类型
18             foreach (var type in asm.GetTypes())
19             {
20                 //若是是IExcutable接口
21                 if (type.GetInterfaces().Contains(typeof(IExcutable)))
22                 {
23                     //建立接口类型实例
24                     var IExcutable = Activator.CreateInstance(type) as IExcutable;
25                     if (IExcutable != null)
26                     {
27                         implementObject.Add(IExcutable);
28                     }
29                 }
30             }
31         }
32         return implementObject;
33     }
34 
35     /// <summary>
36     /// 获取插件目录
37     /// </summary>
38     /// <returns></returns>
39     static string GetPluginsDir()
40     {
41         string pluginDir = ConfigurationManager.AppSettings["pluginDir"];
42         return pluginDir;
43     }
44 }

下面的代码段主要功能是在主程序中为插件分配菜单,绑定公共事件:this

 1 /// <summary>
 2 /// 建立插件公共事件
 3 /// </summary>
 4 /// <param name="sender"></param>
 5 /// <param name="e"></param>
 6 private void Plugin_Click(object sender, EventArgs e)
 7 {
 8     ToolStripItem item= sender as ToolStripItem;
 9     if (null != item)
10     {
11 
12         if (null != item.Tag)
13         {
14             IExcutable plugin = item.Tag as IExcutable;
15             if (null != plugin)
16             {
17                 CodeContent.RichText=plugin.Excute(CodeContent.RichText);
18             }
19         }
20     }
21 }
22 
23 /// <summary>
24 /// 主程序加载插件
25 /// </summary>
26 private void LoadPlugins()
27 {
28     List<IExcutable> list = Common.Common.GetPlugins();
29     foreach (var Iplugins in list)
30     {
31         ToolStripMenuItem item = new ToolStripMenuItem(Iplugins.GetName());//动态建立以插件菜单 32         item.Name = Iplugins.GetName();
33         item.Click += new EventHandler(Plugin_Click);//绑定公共事件 34         item.Tag = Iplugins;
35         this.Plugins.DropDownItems.Add(item);
36     }
37 }

其中的GetPlugins方法就是遍历指定目录下的DLL文件,并把符合接口约定的对象加入集合返回给主程序。而GetPluginsDir方法是获取插件的存储位置,主要是在配置文件中读取插件目录。spa

 1 <?xml version="1.0" encoding="utf-8"?>
 2 <configuration>
 3   <startup>
 4     <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5"/>
 5   </startup>
 6   <appSettings>
 7     <!--配置加载插件目录-->
 8     <add key="pluginDir"  value="CodeEditorPlugins"/>
 9   </appSettings>
10 </configuration>

 实现效果如图:插件

转换前的文本,Format的做用是把全部的小写字母转为大写。

转换后的文本。

4、总结

这个迷你编辑器是以前的一个小程序,整理代码的时候发现的,忽然想改造一下使其更符合个人使用要求,就顺便加了个插件机制。插件机制是一种良好的软件设计思想,能够在不修改主程序的状况下扩展主程序的功能,有时候一款软件的插件功能要比主程序自带的功能要强大得多。应用插件机制要注意几点:

  • 定义接口,也就是主程序与插件的“契约”
  • 应用反射,经过反射来加载符合接口的类,而后建立该类的对象调用接口方法

 代码整理完毕,已经开源到GitHub上,但愿这篇文章能帮助到对插件机制不是很了解的人。若是文中表述有不得当的地方,还请指正。

 

做者:悠扬的牧笛

博客地址:http://www.cnblogs.com/xhb-bky-blog/p/6287973.html

声明:本博客原创文字只表明本人工做中在某一时间内总结的观点或结论,与本人所在单位没有直接利益关系。非商业,未受权贴子请以现状保留,转载时必须保留此段声明,且在文章页面明显位置给出原文链接。

相关文章
相关标签/搜索