插件,意味着可擴展,且宿主程序不依賴於插件,即插即用。這種軟件設計方式可使咱們的應用程序最大化地得到可擴展性、適應性和穩定性,並且便於軟件的維護和升級。在什麼場景下使用插件呢?例如在本篇文章中,我我的有一個小需求就是但願記事本帶行號,因而我本身寫了一個極簡易的編輯器(CodeEditor),以這個編輯器爲例,主體程序功能包括常見的新建、複製、查找、保存等已經完成,可是在使用的過程當中發現須要用到 格式化 這個功能,可是我還不想再去改主程序,這種情形下就能夠經過插件來實現,這樣之後在使用的時候,只要有新的需求就能夠經過新增插件來實現,從某種程度上講這也符合了開放-封閉的設計原則。下面對插件的定義來自百度百科。html
插件(Plug-in)是一種遵循必定規範的應用程序接口編寫出來的程序。其只能運行在程序規定的系統平臺下(可能同時支持多個平臺),而不能脫離指定的平臺單獨運行。git
實現插件機制的兩大要素:一個是接口,另外一個是反射。接口實際上是一種「契約」,主程序是經過這種「契約」來約束是否存在符合我指望的對象,若是不符合就不會去加載該對象。在CodeEditor
中咱們約定的接口是IExcutable
。而這種「契約」的執行就是經過反射來達到目的,主程序中會經過反射加載約定好的Plugin文件夾下全部的DLL文件,而後遍歷這些插件並查看是否存在實現了IExcutable的而且能夠實例化的類,若是有則建立該類的實例加入集合並返回集合。主程序拿到集合後會在構造函數中加載這些插件,加載過程包括動態添加菜單、指定菜單的點擊事件,這樣完整的插件加載過程就完成了。下面經過CodeEditor
來具體看下插件的實現過程。
github
下面的圖是整個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的做用是把全部的小寫字母轉爲大寫。
轉換後的文本。
這個迷你編輯器是以前的一個小程序,整理代碼的時候發現的,忽然想改造一下使其更符合個人使用要求,就順便加了個插件機制。插件機制是一種良好的軟件設計思想,能夠在不修改主程序的狀況下擴展主程序的功能,有時候一款軟件的插件功能要比主程序自帶的功能要強大得多。應用插件機制要注意幾點:
代碼整理完畢,已經開源到GitHub上,但願這篇文章能幫助到對插件機制不是很瞭解的人。若是文中表述有不得當的地方,還請指正。
做者:悠揚的牧笛
博客地址:http://www.cnblogs.com/xhb-bky-blog/p/6287973.html
聲明:本博客原創文字只表明本人工做中在某一時間內總結的觀點或結論,與本人所在單位沒有直接利益關係。非商業,未受權貼子請以現狀保留,轉載時必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接。