使用IoC與DI(Dependency Injection)有何意義? (一) 它到底是甚麼?
其實我們在好幾年前,就已經談過DI(Dependency Injection)這個主題。當時這類議題被視為進階的開發概念,但如果你最近開始使用 .net core,大概已經發現DI如今已變成.net core中的基本要求。
事實上,從事教育訓練這麼多年的觀察下來,不難發現其實還是有相當多的開發人員不真的很明白,到底DI對於軟體開發有何意義? 它能帶來什麼價值?
這一篇希望能夠用一個較為具體的實例,對初學者解釋到底什麼是DI,以及它能帶來的效益。
結論
先說結論,對開發人員來說,使用DI能夠帶來 提高可測試性(testability) 以及 提高可擴充性的價值,同時降低相依性,讓程式碼便於維護。
在 asp.net core當中DI如今已是基本方案,asp.net core 是以服務(DI Services)的形式把DI這個機制實現在框架當中。讓開發人員不管是用 razor page, MVC, 或是其他開發方式,都可以(幾乎是必須)採用DI服務。
我們很久以前就說過,『框架』本身其實就是一種限制、一種引導,誘導開發人員往某一種方向去開發程式。而如今 asp.net core 把DI納作框架的一部分,就是在引導開發人員在開發時走向某一個路線。
什麼路線呢? 就是高可測試性、低耦合以及高可擴充性,如果兩相比較,我覺得asp.net core中的DI可以為開發人員實現高可測試性這個目的的可能性大概會更高一些,估計是因為最近幾年,unit test已經成為開發領域的某種潮流。
什麼是物件相依性
但不管如何,.net core已經把DI視為基礎,而你要理解這一切,得先搞清楚什麼是物件的相依性。
我常在上架構設計課程時說,職責分離這個設計概念很容易理解,誰都知道不同職責的模塊或物件就該分離,問題是怎麼決定誰的職責是甚麼? 到底誰又該跟誰分離? 等到有一天你真的開始設計,就會發現理解是一回事,真的明白到能夠動手設計又是另一回事…
看個例子,我們先來了解一下背景需求。
假設,我們打算為企業建構一個薪資計算的功能,已知薪資計算會依據三個參數,分別是本月上班時數(WorkHours),時薪(HourlyWage),以及請假時數(PrivateDayOffHours)。
也就是說,倘若Eric本月上班 19天,每天8小時,時薪200,本月請假1天,每請假一天倒扣200元,則Eric的薪資為:
Eric薪資= (19x8) x 200 – (1 x 8) x 200 = 30400 – 1600 = $28800
為了實現這個功能,我們的設計了底下這樣的console程式,它包含一個主程式和兩個類別,我們先看類別的部分:
我們來談談上面這兩個類別的職責,為什麼計算薪資該分成兩個類別? 為何不寫成一個? 如果把上面SalaryFormula中計算薪資的Execute Method直接融入SalaryCalculator裡面作為內建的程式碼(Calculate的一部分),不是讓程式碼簡單的多了嗎? (其實,一般初學者肯定會這麼寫)
的確,但關鍵就在『需求』。
由於考量到這是一個套裝軟體,將來會賣到不同的企業,每一個企業計算薪資的具體公式可能不同,我們希望未來計算薪資的公式可以隨時動態『抽換』,最理想的狀況是,賣給不同的企業,可以抽換成不同的模組。
請記得,最終職責的決定其實往往取決於『需求』。
因此,之所以把計算薪資設計成兩個類別,就是考量到上面這樣的背景需求。我們區分成兩個類別,一個是計算薪資的SalaryCalculator類別,另一個則是薪資計算公式SalaryFormula類別。
SalaryFormula是薪資計算的公式,它具體決定了薪資怎麼計算。而SalaryCalculator這個類別則是負責『計算』這個動作,但不管具體算法為何。
這兩個類別設計好了之後,我們再來看主程式如下:
你會發現主程式裡面new了一個SalaryCalculator類別來計算薪資,並且傳入SalaryFormula()物件實體作為參數。好了,現在問題來了。這整段程式碼有什麼缺點?
沒錯,從原始需求的角度來看,薪資計算的公式若想要可以動態調整,依照先前SalaryCalculator.cs的程式碼,由於類別SalaryCalculator會倚賴SalaryFormula類別的實作(SalaryCalculator.cs 16行相依於36行的SalaryFormula類別的具體實作,因為傳入的參數型別寫死了是SalaryFormula類別),未來這套軟體賣給了另一家公司,倘若想要改變薪資計算的公式,勢必得翻出原始程式碼(48行)來改寫SalaryFormula的實作才行。
砍斷針對具體實作的相依
好,知道問題在哪之後,我們來看怎麼調整? 其實很簡單,請回想起你熟悉的物件導向程式設計中的介面(interface)。請注意最大的改變在11,16,36,55行:
上面這段是修改後的類別,你會發現最大的差異在於55-59行增加了一個介面ISalaryFormula,該介面是類別SalaryFormula的抽象定義,而類別SalaryFormula(36-50)行則是ISalaryFormula的實作。
另外一個重要的修改是16行的SalaryCalculator(…)方法,傳入的物件的型別從SalaryFormula改為ISalaryFormula,如此一來,未來只要是符合ISalaryFormula介面所實作的類別,都可以當作參數傳入。
這一個修改動作,就在改變SalaryCalculator類別對於SalaryFormula類別的相依性,從原先針對類別實作的相依,改為對介面(抽象)相依。這樣做有什麼好處? 砍斷對具體實作的相依性,才能夠為將來系統能夠動態抽換計算公式而鋪路…我們接著往下看。
做了這樣的修改之後,未來倘若我們希望改變薪資的具體計算方式,我們無須修改整套程式碼,只需要再建構一個新的薪資計算類別,然後修改主程式即可(你如果一直往後看,未來只需要透過config檔案『設定』,連主程式都無須修改):
注意上面的程式碼當中,主程式有兩段6-9行與12-16行,本質上這兩段code幾乎完全一樣,唯一的差別是new SalaryCalculator(…)類別時傳入建構子的參數有所不同,一個是計算員工的公式類別實作,一個是計算老闆的公式類別實作。
你會發現由於老闆請假不扣薪(26行),因此即便參數完全一樣,但計算出的結果有所不同:
到這邊,我們已經為了動態抽換鋪好了基礎(對,這部分只是基礎,稱為IoC),接著我們來看,如何在ASP.NET Core中透過DI Services實現動態抽換。
如果你需要完整程式碼,請參考我的Github:
https://github.com/isdaviddong/DotNetCoreConsoleExample_IoC
-----------------------------
本文內容來自於『團隊開發與架構設計實務』課程,我們最近要開課囉,依照過去經驗這門課幾乎都是秒殺,如果你需要預先保留席次,請點選這裡登記唷。
若您要詢問問題,或是需要即時取得更多相關訊息,可按這裡加入FB專頁。
若這篇文章對您有所幫助,請幫我們分享出去,謝謝您的支持。
留言