目錄
- 什么是插件式編程
- OCP:開放封閉原則
- 插件式架構
- C#實現插件式開發的理論基礎
- ServiceHost實現批量寄宿
- 總結
- 參考
兩截門(Dutch Door)——(名詞)一個被水平分割為兩部分的門,這樣每一部分都可以獨立保持開放或者封閉。(《美國傳統英語字典》第4版,2000年)
假設您設計的程序已經部署到用戶的計算機上,並且能夠正常運行了。但是有一天,用戶打來了電話——他們要求增加新的功能。確定了用戶的需求后,你竟然發現原有的軟件架構已經無法勝任新增任務的需求——你需要重新設計這個應用了!但問題是,就算你又用了一個開發周期完成了用戶需要的應用,卻不能保證用戶的需求不會再次變更。也就是說,需求蔓延的可能性依然存在。因此,這種情況下插件構架更能顯示出它的優越性。
同樣的原理,可以應用到我們的widows服務開發中。
什么是插件式編程
OCP:開放封閉原則
軟件實體(類、模塊、函數等)應該是可以擴展的,但是不可修改。
- 對於擴展是開放的(open for extension)。
這意味着模塊的行為是可以擴展的。當應用的需求改變時,我們可以對模塊進行擴展,使其具有滿足那些改變的新行為。還句話說,我們可以改變模塊的功能。
- 對於修改時封閉的(closed for modification)
對於模塊行為進行擴展時,不必改動模塊的源代碼或者二進制代碼。模塊的二進制可執行版本,無論是可鏈接的庫、DLL或者.EXE文件,都無需改動。
怎樣可能在不改動模塊源代碼的情況下去更改它的行為呢?如果不更改一個模塊,又怎么能夠去改變它的功能呢?
答案是抽象。在C#或者其他任何的OOPL(面向對象程序設計語言,如java)中,可以創建出固定卻能夠描述一組任意個可能行為抽象體。這個抽象體就是抽象基類。而這一組任意個可能得行為則表現為可能得派生類。
模塊可能對抽象體進行操作。由於模塊依賴於一個固定的抽象體,所以它對於更改可以是封閉的。同時,通過從這個抽象體派生,可以擴展此模塊的行為。
插件式架構
插件式架構是遵循OCP原則的。插件式架構,一種開放性的、高擴展性的架構體系。基於插件的設計好處很多,把擴展功能從框架中剝離出來,降低了框架的復雜度,讓框架更容易實現。擴展功能與框架以一種很松的方式耦合,兩者在保持接口不變的情況下,可以獨立變化和發布。基於插件設計並不神秘,相反它比起一團泥的設計更簡單,更容易理解。
而筆者實際項目中,通常是按照業務歸屬將不同業務模塊設計成不同插件。
C#實現插件式開發的理論基礎
在dotNET framework中,給開發人員提供了反射機制,通過反射可以動態加載類庫。
Assembly類可以獲得正在運行的裝配件信息,也可以動態的加載裝配件,以及在裝配件中查找類型信息,並創建該類型的實例。
Type類可以獲得對象的類型信息,此信息包含對象的所有要素:方法、構造器、屬性等等,通過Type類可以得到這些要素的信息,並且調用之。
MethodInfo包含方法的信息,通過這個類可以得到方法的名稱、參數、返回值等,並且可以調用之。
諸如此類,還有FieldInfo、EventInfo等等,這些類都包含在System.Reflection命名空間下。
插件方式實現ServiceHost批量寄宿WCF服務
實現基本目標:
- 以插件的方式開發wcf服務端,業務模塊以獨立dll的方式注冊到宿主里
- 業務模塊的添加刪除是動態的,服務端主程序無需重新編譯
- 無須為每個業務模快servicetype單獨做配置,注冊一個業務模塊只需在host里添加一個endpoint和操作契約
(1)創建一個WinowsService服務,創建三個wcf服務,然后分別實現各自wcf的業務邏輯。以上步驟比較簡單。
(2)然后就是編譯wcf.dll,並拷貝到host服務的指定目錄,比如我的在\plugins\xx_wcf.dll。
(3)在Host宿主的配置文件,添加endpoint和操作契約
<system.serviceModel>
<bindings>
<netTcpBinding>
<binding name="tcpBinding">
<security mode="None">
</security>
</binding>
</netTcpBinding>
</bindings>
<services>
<!--添加服務-->
<!--B服務-->
<service name="Plugins.BService.WcfService.BusinessBService" behaviorConfiguration="Plugins.BService.WcfService">
<!--name 必須與代碼中的host實例初始化的服務一樣 behaviorConfiguration 行為配置 -->
<host>
<baseAddresses>
<!--添加調用服務地址-->
<add baseAddress="net.tcp://172.16.0.194:3721/Plugins.BService.WcfService"/>
</baseAddresses>
</host>
<!--添加契約接口 -->
<endpoint address="net.tcp://172.16.0.194:3721/Plugins.BService.WcfService" binding="netTcpBinding" contract="Plugins.BService.WcfService.IBusinessBService" bindingConfiguration="tcpBinding" name="Plugins.BService.WcfService.BusinessBasicInfoService"></endpoint>
<endpoint address="mex" binding="mexTcpBinding" name="MEX" contract="IMetadataExchange"/>
</service>
<!--A服務-->
<service name="Plugins.AService.WcfService.BusinessAService" behaviorConfiguration="Plugins.AService.WcfService">
<!--name 必須與代碼中的host實例初始化的服務一樣 behaviorConfiguration 行為配置 -->
<host>
<baseAddresses>
<!--添加調用服務地址-->
<add baseAddress="net.tcp://172.16.0.194:3722/Plugins.AService.WcfService"/>
</baseAddresses>
</host>
<!--添加契約接口 -->
<endpoint address="net.tcp://172.16.0.194:3722/Plugins.AService.WcfService" binding="netTcpBinding" contract="Plugins.AService.WcfService.IBusinessAService" bindingConfiguration="tcpBinding" name="Plugins.AService.WcfService.BusinessBasicInfoService"></endpoint>
<endpoint address="mex" binding="mexTcpBinding" name="MEX" contract="IMetadataExchange"/>
</service>
<!--Resource.Robot服務-->
<service name="ResourceRobot.White.WcfService.WhiteListService" behaviorConfiguration="ResourceRobot.White.WcfService">
<!--name 必須與代碼中的host實例初始化的服務一樣 behaviorConfiguration 行為配置 -->
<host>
<baseAddresses>
<!--添加調用服務地址-->
<add baseAddress="net.tcp://172.16.0.194:3723/ResourceRobot.White.WcfService"/>
</baseAddresses>
</host>
<!--添加契約接口 -->
<endpoint address="net.tcp://172.16.0.194:3723/ResourceRobot.White.WcfService" binding="netTcpBinding" contract="ResourceRobot.Channels.IWhiteListService" bindingConfiguration="tcpBinding" name="ResourceRobot.White.WcfService.WhiteListService"></endpoint>
<endpoint address="mex" binding="mexTcpBinding" name="MEX" contract="IMetadataExchange"/>
</service>
</services>
<!--定義WcfServiceBehavior的行為-->
<behaviors>
<serviceBehaviors>
<behavior name="Plugins.BService.WcfService">
<serviceMetadata httpGetEnabled="false"/>
<serviceDebug includeExceptionDetailInFaults="false"/>
</behavior>
<behavior name="Plugins.AService.WcfService">
<serviceMetadata httpGetEnabled="false"/>
<serviceDebug includeExceptionDetailInFaults="false"/>
</behavior>
<behavior name="ResourceRobot.White.WcfService">
<serviceMetadata httpGetEnabled="false"/>
<serviceDebug includeExceptionDetailInFaults="false"/>
</behavior>
</serviceBehaviors>
</behaviors>
</system.serviceModel>
(4)接下來就是利用.NET反射機制,去遍歷plugins目錄下的wcf.dll,分別注冊到ServiceHost中。
在service.cs的OnStart方法中,增加以下代碼:
//1.尋找wcf.dll
List<string> pluginpaths = PluginHelper.Find();
//2.遍歷並解析
foreach (string filename in pluginpaths)
{
try
{
//獲取文件名
string asmfile = filename;
string asmname = Path.GetFileNameWithoutExtension(asmfile);
if (asmname != string.Empty)
{
//利用反射,構造DLL文件的實例
Assembly asm = Assembly.LoadFile(asmfile);
//利用反射,從程序集(DLL)中,提取類,並把此類實例化
Type[] t = asm.GetExportedTypes();
foreach (Type type in t)
{
//3.注冊
ServiceHost host = new ServiceHost(type);
if (host != null)
{
host.Open();
}
}
}
}
catch (Exception ex)
{
Console.Write(ex.Message);
}
這樣就實現了一個serivcehost承載多個wcf服務的要求。
3 總結
OCP:開放-封閉原則是敏捷開發中非常重要的原則。
使用插件式架構能夠是整個軟件更加靈活,擴展性更強,同時也便於部署和維護。
最后分享一下趣圖:
如有需要demo的可以留言或私信筆者(576810529@qq.com),並留下您的郵箱~我會盡快send給大家。
參考
- 一個ServiceHost寄宿多個服務?
- 《敏捷軟件開發 原則、模式與實踐》
轉載請注明出處,本文同步更新至
- 微信公眾號:碼農商業參謀
- 網站:http://www.zhikestreet.com/