基于接口的插件机制
一、前言
插件,意味著可擴展,且宿主程序不依賴于插件,即插即用。這種軟件設計方式可以使我們的應用程序最大化地獲得可擴展性、適應性和穩定性,而且便于軟件的維護和升級。在什么場景下使用插件呢?例如在本篇文章中,我個人有一個小需求就是希望記事本帶行號,于是我自己寫了一個極簡易的編輯器(CodeEditor),以這個編輯器為例,主體程序功能包括常見的新建、復制、查找、保存等已經完成,但是在使用的過程中發現需要用到?格式化?這個功能,但是我還不想再去改主程序,這種情形下就可以通過插件來實現,這樣以后在使用的時候,只要有新的需求就可以通過新增插件來實現,從某種程度上講這也符合了開放-封閉的設計原則。下面對插件的定義來自百度百科。
插件(Plug-in)是一種遵循一定規范的應用程序接口編寫出來的程序。其只能運行在程序規定的系統平臺下(可能同時支持多個平臺),而不能脫離指定的平臺單獨運行。
二、插件機制實現原理
實現插件機制的兩大要素:一個是接口,另一個是反射。接口其實是一種“契約”,主程序是通過這種“契約”來約束是否存在符合我期望的對象,如果不符合就不會去加載該對象。在CodeEditor中我們約定的接口是IExcutable。而這種“契約”的執行就是通過反射來達到目的,主程序中會通過反射加載約定好的Plugin文件夾下所有的DLL文件,然后遍歷這些插件并查看是否存在實現了IExcutable的并且可以實例化的類,如果有則創建該類的實例加入集合并返回集合。主程序拿到集合后會在構造函數中加載這些插件,加載過程包括動態添加菜單、指定菜單的點擊事件,這樣完整的插件加載過程就完成了。下面通過CodeEditor來具體看下插件的實現過程。
三、插件機制的實踐
?下面的圖是整個CodeEditor的目錄結構
第三個CodeEditorControl可以忽略,這個類庫是一個自定義的控件,是實現一個帶行號的文本編輯器的核心組件,但是和本文主題關系不大。主要看插件接口CodeEditorInterface和插件實現CodeEditorPlugins以及主程序CodeEditor。這三者的關系可以通過以下圖片來展示。
首先從主程序和插件之間的橋梁入手,就是插件的接口,在CodeEditorInterface中的接口IExcutable中有兩個約定方法,一個是GetName負責返回當前的插件名稱,用于主程序獲取并動態加載到菜單中;另一個是Excute負責獲取主程序中文本并執行相應的操作。代碼如下:
1?public?interface?IExcutable2?{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的作用是把所有的小寫字母轉為大寫。
轉換后的文本。
四、總結
這個迷你編輯器是之前的一個小程序,整理代碼的時候發現的,突然想改造一下使其更符合我的使用要求,就順便加了個插件機制。插件機制是一種良好的軟件設計思想,可以在不修改主程序的情況下擴展主程序的功能,有時候一款軟件的插件功能要比主程序自帶的功能要強大得多。應用插件機制要注意幾點:
定義接口,也就是主程序與插件的“契約”
應用反射,通過反射來加載符合接口的類,然后創建該類的對象調用接口方法
轉載于:https://blog.51cto.com/zhanglida66/1920513
總結
- 上一篇: iOS10.3的新玩意儿
- 下一篇: centos7.3部署kvm虚拟化