[ASP.NET] Autofac IoC Container
情境:
不同客戶具有不同的特休假產生規則,但在產品化的專案前題下,使用者的UI只有一套,因此只有背後邏輯面會因不同客戶而有不同的規則。
需求:
產品化的UI程式,不希望每次增加一個新客戶就要跟著調整,期望只需要新增新的客戶實作邏輯類別,然後可以透過環境設定檔的方式,決定各環境的實作邏輯。
傳統方式:
在一般熟悉的開發模式下,我們可能會像以下程式碼一樣這麼做。
protected void Button1_Click(object sender, EventArgs e) { //模擬外部給與的條件(不同客戶公司) var company = "A"; switch (company) { case "A": ClassLibraryA.Class1 a = new ClassLibraryA.Class1(); a.Cal(); break; case "B": ClassLibraryB.Class1 b = new ClassLibraryB.Class1(); b.Cal(); break; default: break; } }
將不同客戶的特休假產生規則邏輯,寫成不同的類別,然後再利用switch這類的判斷式,直接在程式碼裡依條件產生不同的實體並呼叫其方法。但這樣方式會面臨到維護上的問題。當新增加一個新客戶C時,必須再加入C客戶的ClassLibraryC,然後再這個共用的使用者的UI程式碼裡,把switch判斷式多增加一條判斷分支出來。於是當客戶愈多時,判斷分支就愈加愈多。也就是我們把實作邏輯的實體產生都在程式裡寫死了,非常的沒有彈性。
重構版本:
接下來我們就來重構一下,基於IoC ( 控制反轉 )的設計原則下,首先這個案例應該先把使用者UI與特休假產生規則相依性切開,另外雖然不同客戶有不同的特休假產生規則,但從抽象的面向來看,就是一個特休假天數的產生動作,不同只是內部的實作,因此第一步我們先建立一個 MyLeaveDays 的介面,如下。
interface IMyLeaveDays
{
Decimal Cal();
}
接著將不同公司別的特休假產生規則類別,統一繼承這個介面。
class MyLeaveDaysA : IMyLeaveDays { public decimal Cal() { return decimal.Parse("1"); } }
class MyLeaveDaysB : IMyLeaveDays { public decimal Cal() { return decimal.Parse("2"); } }
再來就是把原本UI直接相依於實作類別的部份,改為相依於介面。建立一個產生特休的服務類別如下,這裡我們採用透過建構子參數注入的方式。
public class MyLeaveDaysCalService { IMyLeaveDays _calLogic; public MyLeaveDaysCalService(IMyLeaveDays callogic) { _calLogic = callogic; } public decimal Cal() { return _calLogic.Cal(); } }
而原本的UI程式則調整如下,改為統一使用MyLeaveDaysCalService,利用建構子注入介面參數,而達到進行不同的實作邏輯。到目前為止,我們已經先把不同客戶的特休假產生邏輯的類別跟產品統一的使用者UI程式給切開,利用介面來隔離,只要維持IMyLeaveDays的穩定性不變動,不管MyLeaveDaysA,MyLeaveDaysB......或MyLeaveDaysN怎麼修改,基本上都不會影響到產品面的這個共用UI程式碼。
protected void Button1_Click(object sender, EventArgs e) { //模擬外部給與的條件(不同客戶公司) var company = "A"; MyLeaveDaysCalService myleavedaysservice; switch (company) { case "A": myleavedaysservice = new MyLeaveDaysCalService(new MyLeaveDaysA()); myleavedaysservice.Cal(); break; case "B": myleavedaysservice = new MyLeaveDaysCalService(new MyLeaveDaysB()); myleavedaysservice.Cal(); break; } }
但是還是有個問題,那就是當我們新增加一個客戶X時,還是必須變動到 switch 程式碼增加一個判斷分支出來,而這並不是我們想要的,我們希望這部份可以利用設定檔的方式讓它可以不用改到程式碼,就可以依照設定讀取到該客戶應該對應到的實作類別。因此接下來就採用 IoC Container - Autofac 來幫我們解決這個問題,關於 IoC Container 其實有很多套,選擇 Autofac 的原因很簡單,它具有的社群資源比較多並持續發展中,採用的使用者眾多,比較不用担心遇上問題找不到解決方法,且簡單易用,而效能根據網路上許多前人的文章資料顯示不需担心。首先我們利用 NuGet 套件管理工具為專案加入 Autofac.Web ( 這裡我們採用的是web form 專案,所以加入的是autofac.web,不同專案類型有不同的autofac 套件,可自行參考 autofac 官網 ) ,這裡我們選擇的是最新穩定版本3.2.0。
接著開啟Global專案,繼承 IContainerProviderAccessor ,並實作介面如下。
static IContainerProvider _containerProvider; public IContainerProvider ContainerProvider { get { return _containerProvider; } }
並且在Application_Start裡加入以下程式碼。
var builder = new ContainerBuilder(); _containerProvider = new ContainerProvider(builder.Build());
由於我們想採用的是透過設定檔的方式來決定客戶端環境應該跑哪一個實作邏輯,所以必須再加入Autofac.Configuration 套件,同樣的使用NuGet套件管理工具。
接下來開啟 Web.config 檔案,在 configSections 區段裡加入 <section name="autofac" type="Autofac.Configuration.SectionHandler, Autofac.Configuration" /> ,name的名稱可以是自訂的。
除此之外,同樣的在Web.config 檔案裡,找到 configuration 區段裡,加入我們的設定檔定義,如下。
<autofac defaultAssembly="BaseLibrary"> <components> <component type="BaseLibrary.MyLeaveDaysA, BaseLibrary" service="BaseLibrary.IMyLeaveDays" /> </components> </autofac>
defaultAssembly:是我們的類別庫
<component type="BaseLibrary.MyLeaveDaysA, BaseLibrary" service="BaseLibrary.IMyLeaveDays" />:Type設定為客戶端要實作的類別,service指向我們的 interface,整句設定用白話來解釋就是【呼叫繼承實作 IMyLeaveDays 介面的MyLeaveDaysA 類別,做為我們要執行的元件】
接下來就是再重構一下我們的UI程式碼,把原本的 switch 做個調整,程式碼如下,autofac 是設定在 web.config 裡的名稱,可以自訂義,調整過後原先的 switch 判斷式全部拿掉了,取而代之是讀取 web.config 的設定,建立 Container,然後取得我們要的服務實作類別。經過這樣的修改,即使日後增加了C 、D、E.. .. .. 等不同邏輯的客戶需求,我們也不需要去變動到產品化的這隻程式,只需要實作繼承IMyLeaveDays的新類別,然後針對不同的客戶環境,在web.config裡調整設定即可。
var builder = new ContainerBuilder(); builder.RegisterType<MyLeaveDaysCalService>(); builder.RegisterModule(new ConfigurationSettingsReader("autofac")); using (var container = builder.Build()) { var obj = container.Resolve<MyLeaveDaysCalService>(); Response.Write(obj.Cal().ToString()); }
由於目前我們在web.config 裡是設定為 BaseLibrary.MyLeaveDaysA ,因此預期具體執行的結果應該是1天,執行結果如下。
接著我們測試修改 web.config ,把 BaseLibrary.MyLeaveDaysA 改為 BaseLibrary.MyLeaveDaysB,因此預期具體執行的結果應該是2天,執行結果如下。
透過這樣的重構修正,採取 IoC 設計加上 autofac IoC Container,可以具體達成我們所要的效果,透過設定檔動態抽換不同的實作類別,對於產品化的專案相當有幫助,當然 autofac 也可以直接讀額外的設定檔像是xml及json檔案,更多的細節可以參考 autofac 官方網站。
Ref :
By No.18