插件,意味着可扩展,且宿主程序不依赖于插件,即插即用。这种软件设计方式可以使我们的应用程序最大化地获得可扩展性、适应性和稳定性,而且便于软件的维护和升级。在什么场景下使用插件呢?例如在本篇文章中,我个人有一个小需求就是希望记事本带行号,于是我自己写了一个极简易的编辑器(CodeEditor),以这个编辑器为例,主体程序功能包括常见的新建、复制、查找、保存等已经完成,但是在使用的过程中发现需要用到 格式化 这个功能,但是我还不想再去改主程序,这种情形下就可以通过插件来实现,这样以后在使用的时候,只要有新的需求就可以通过新增插件来实现,从某种程度上讲这也符合了开放-封闭的设计原则。下面对插件的定义来自百度百科。
插件(Plug-in)是一种遵循一定规范的应用程序接口编写出来的程序。其只能运行在程序规定的系统平台下(可能同时支持多个平台),而不能脱离指定的平台单独运行。
二、插件机制实现原理实现插件机制的两大要素:一个是接口,另一个是反射。接口其实是一种“契约”,主程序是通过这种“契约”来约束是否存在符合我期望的对象,如果不符合就不会去加载该对象。在CodeEditor中我们约定的接口是IExcutable。而这种“契约”的执行就是通过反射来达到目的,主程序中会通过反射加载约定好的Plugin文件夹下所有的DLL文件,然后遍历这些插件并查看是否存在实现了IExcutable的并且可以实例化的类,如果有则创建该类的实例加入集合并返回集合。主程序拿到集合后会在构造函数中加载这些插件,加载过程包括动态添加菜单、指定菜单的点击事件,这样完整的插件加载过程就完成了。下面通过CodeEditor来具体看下插件的实现过程。
三、插件机制的实践下面的图是整个CodeEditor的目录结构
第三个CodeEditorControl可以忽略,这个类库是一个自定义的控件,是实现一个带行号的文本编辑器的核心组件,但是和本文主题关系不大。主要看插件接口CodeEditorInterface和插件实现CodeEditorPlugins以及主程序CodeEditor。这三者的关系可以通过以下图片来展示。
首先从主程序和插件之间的桥梁入手,就是插件的接口,在CodeEditorInterface中的接口IExcutable中有两个约定方法,一个是GetName负责返回当前的插件名称,用于主程序获取并动态加载到菜单中;另一个是Excute负责获取主程序中文本并执行相应的操作。代码如下:
IExcutable 2 { GetName(); Excute(string text); 7 }
下面是主程序加载符合“契约”的插件对象的核心代码,主要作用就是过滤符合接口的类并实例化类的对象,加到集合中:
Common 2 { 加载插件 List<IExcutable> GetPlugins() 8 { 9 List<IExcutable> implementObject = new List<IExcutable>(); dir = GetPluginsDir(); (, )) 14 { asm = Assembly.LoadFrom(file); (var type in asm.GetTypes()) 19 { (type.GetInterfaces().Contains(typeof(IExcutable))) 22 { IExcutable = Activator.CreateInstance(type) as IExcutable; 25 if (IExcutable != null) 26 { 27 implementObject.Add(IExcutable); 28 } 29 } 30 } 31 } 32 return implementObject; 33 } 获取插件目录 GetPluginsDir() 40 { ]; 42 return pluginDir; 43 } 44 }
下面的代码段主要功能是在主程序中为插件分配菜单,绑定公共事件:
创建插件公共事件 Plugin_Click(object sender, EventArgs e) 7 { 8 ToolStripItem item= sender as ToolStripItem; 9 if (null != item) 10 { (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 } 主程序加载插件 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方法是获取插件的存储位置,主要是在配置文件中读取插件目录。
实现效果如图:
转换前的文本,Format的作用是把所有的小写字母转为大写。
转换后的文本。
四、总结