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


一、前言

插件,意味着可擴展,且宿主程序不依賴於插件,即插即用。這種軟件設計方式可以使我們的應用程序最大化地獲得可擴展性、適應性和穩定性,而且便於軟件的維護和升級。在什么場景下使用插件呢?例如在本篇文章中,我個人有一個小需求就是希望記事本帶行號,於是我自己寫了一個極簡易的編輯器CodeEditor,以這個編輯器為例,主體程序功能包括常見的新建、復制、查找、保存等已經完成,但是在使用的過程中發現需要用到 格式化 這個功能,但是我還不想再去改主程序,這種情形下就可以通過插件來實現,這樣以后在使用的時候,只要有新的需求就可以通過新增插件來實現,從某種程度上講這也符合了開放-封閉的設計原則。下面對插件的定義來自百度百科。

插件(Plug-in)是一種遵循一定規范的應用程序接口編寫出來的程序。其只能運行在程序規定的系統平台下(可能同時支持多個平台),而不能脫離指定的平台單獨運行。

二、插件機制實現原理

實現插件機制的兩大要素:一個是接口,另一個是反射。接口其實是一種“契約”,主程序是通過這種“契約”來約束是否存在符合我期望的對象,如果不符合就不會去加載該對象。在CodeEditor中我們約定的接口是IExcutable。而這種“契約”的執行就是通過反射來達到目的,主程序中會通過反射加載約定好的Plugin文件夾下所有的DLL文件,然后遍歷這些插件並查看是否存在實現了IExcutable的並且可以實例化的類,如果有則創建該類的實例加入集合並返回集合。主程序拿到集合后會在構造函數中加載這些插件,加載過程包括動態添加菜單、指定菜單的點擊事件,這樣完整的插件加載過程就完成了。下面通過CodeEditor來具體看下插件的實現過程。

三、插件機制的實踐

 下面的圖是整個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

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

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM