【實踐】基於接口的插件機制

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

聲明:本博客原創文字只表明本人工做中在某一時間內總結的觀點或結論,與本人所在單位沒有直接利益關係。非商業,未受權貼子請以現狀保留,轉載時必須保留此段聲明,且在文章頁面明顯位置給出原文鏈接。

相關文章
相關標籤/搜索