一、前言插件,意味着可扩展,且宿主程序不依赖于插件,即插即用。这种软件设计方式可以使我们的应用程序最大化地获得可扩展性、适应性和稳定性,而且便于软件的维护和升级。在什么场景下使用插件呢?例如在本篇文章中,我个人有一个小需求就是希望记事本带行号,于是我自己写了一个极简易的编辑器(CodeEditor),以这个编辑器为例,主体程序功能包括常见的新建、复制、查找、保存等已经完成,但是在使用的过程中发现需要用到 格式化 这个功能,但是我还不想再去改主程序,这种情形下就可以通过插件来实现,这样以后在使用的时候,只要有新的需求就可以通过新增插件来实现,从某种程度上讲这也符合了开放-封闭的设计原则。下面对插件的定义来自百度百科。
二、插件机制实现原理实现插件机制的两大要素:一个是接口,另一个是反射。接口其实是一种“契约”,主程序是通过这种“契约”来约束是否存在符合我期望的对象,如果不符合就不会去加载该对象。在 三、插件机制的实践 下面的图是整个 第三个CodeEditorControl可以忽略,这个类库是一个自定义的控件,是实现一个带行号的文本编辑器的核心组件,但是和本文主题关系不大。主要看插件接口CodeEditorInterface和插件实现CodeEditorPlugins以及主程序CodeEditor。这三者的关系可以通过以下图片来展示。 首先从主程序和插件之间的桥梁入手,就是插件的接口,在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 }
下面的代码段主要功能是在主程序中为插件分配菜单,绑定公共事件: 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方法是获取插件的存储位置,主要是在配置文件中读取插件目录。 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的作用是把所有的小写字母转为大写。 转换后的文本。 四、总结这个迷你编辑器是之前的一个小程序,整理代码的时候发现的,突然想改造一下使其更符合我的使用要求,就顺便加了个插件机制。插件机制是一种良好的软件设计思想,可以在不修改主程序的情况下扩展主程序的功能,有时候一款软件的插件功能要比主程序自带的功能要强大得多。应用插件机制要注意几点:
代码整理完毕,已经开源到GitHub上,希望这篇文章能帮助到对插件机制不是很了解的人。如果文中表述有不得当的地方,还请指正。
作者:悠扬的牧笛 博客地址:http://www.cnblogs.com/xhb-bky-blog/p/6287973.html 声明:本博客原创文字只代表本人工作中在某一时间内总结的观点或结论,与本人所在单位没有直接利益关系。非商业,未授权贴子请以现状保留,转载时必须保留此段声明,且在文章页面明显位置给出原文连接。
|
|