2019年8月20日 星期二

使用IoC與DI有何意義? (二) asp.net core中的DI服務

看過了前面的介紹,在瞭解了DI的背景需求與前提,以及採用介面來降低對特定物件實作的相依性之後,我們接著來看,怎麼使用asp.net core中的DI服務進行功能抽換。

事實上,早在約莫十年前,坊間就有很多DI框架或套件,例如我之前上課介紹過的Unity Application Block,他是一組DI Container(容器)套件,可以幫助我們更輕易地實現動態注入。而asp.net core則是把DI設計為服務,讓我們在程式碼當中,也可以輕易享有相關的功能。

在軟體開發的領域當中,你慢慢會發現到,真正困擾你的不是寫(新)程式,而是改(舊)程式。在我們開發系統的過程當中,我們會希望盡可能維持原始程式碼的不改變,就能夠加入新功能。原因很簡單,拿先前提過的例子,你只是想要修改薪資計算公式,而不是換掉整套HR系統,然後你剛才接手這個系統不久,這時候你不會想看完這套HR系統的整套原始程式碼,才能增加一個小小的薪資計算公式。

另外,你很有可能碰到運氣更差的狀況,就是你手邊根本沒有原始程式碼。

你不過就只是想改一下薪資計算的公式啊?(因為政府剛調整了最低薪資…)

記得Martin Fowler在重構一書中說過這麼一段話:

你回頭想,果真是如此,如果程式碼寫得夠好,你根本不需要看完所有程式碼,甚至也無須擁有整套程式碼,就能夠依照需求在系統中添加新功能。

所幸,前面我們已經為動態注入奠定好了基礎,請看底下這個Razor Page頁面: (我們之所以從前面的Console App改用Razor Page,是為了要展示asp.net core中的DI之故,請務必看過前面這篇才能理解底下情境)

上面這個Razor Page的執行結果如下:

計算出的薪資是 28800,問題來了,OnGet(…)程式碼當中沒有看到誰去new了那個計算薪資的SalaryFormula類別啊? 莫非是從建構子(上面程式碼第4行)傳入的? 是的,你猜對了。

整個asp.net core框架都支援套DI服務,不管是WebAPI、一般的Controller、或是Razor Page的PageModel,都可以在建構子當中加上特定介面作為參數(例如上面第4行),這時候,asp.net core的DI服務框架,會幫你自動注入你事先指定好的類別實作物件。這就是asp.net core提供的DI『服務』。

如此一來,我們透過asp.net core在開發整套系統時,程式碼就可以砍斷對特定類別實作的依賴,實現動態抽換(注入)的效果了。

那到底,是在哪邊指定要用哪一個類別實作的呢? 就是那鼎鼎大名的Startup.cs:


你會發現,就是在Startup.cs當中,我們指定了HR.ISalaryFormula介面未來的實作採用的是HR.SalaryFormula這個類別(第7行),這使得 PageModel 的建構子被asp.net core系統new起來的時候,會自動帶入(注入)HR.SalaryFormula物件的實體(.net core自動幫我們new的)作為參數,然後就實現了後續薪資計算的功能: (12-14行)

而有朝一日,我們想換掉這個公式的時候怎麼做呢? 容易,只需要在Startup.cs 當中換成另一個同樣是繼承自ISalaryFormula所實作出的類別即可:

嘩啦,如此一來,整套系統所有使用到薪資計算的地方都不需要做任何修改,單單只需要在Startup.cs 當中設定(註冊),就可以決定系統中每一個用到的薪資計算的地方採用的是哪一個計算公式類別。

這就是注入的好處,也是DI所帶來的可抽換性。

剛才你看到的是薪資計算公式,但更多我們常用的情境是處理資料庫的Data Context物件、處理日誌紀錄的Logging物件、處理通知的Notification物件…凡此種種,透過DI的設計就可以輕易的替換了,甚至asp.net core已經內建了不少實作。

未來你要把原先發mail的Notification改成發LINE訊息嗎? 你想把原先寫到檔案系統的Log改成送到雲端嗎? 都沒問題,只需要實作出符合相對應介面的類別,然後在Startup.cs 中以DI服務進行註冊即可,系統中其他的程式碼會自動被注入(.net core採用建構子注入),替換成你註冊的類別實作。

這一整個功能的實現,都源自於一開始我們談到的IoC,砍斷針對類別實作的相依,換成以介面設計的方式來實踐。

很有趣是吧,先消化一下。接著我們要來談談,為什麼DI能夠有助於提高可測試性,以及如何連程式碼都不要改,只需要透過『設定』,就可以為系統注入不同的類別實作…
-----------------------------

本文內容來自於『團隊開發與架構設計實務』課程,我們最近要開課囉,依照過去經驗這門課幾乎都是秒殺,如果你需要預先保留席次,請點選這裡登記唷。

若您要詢問問題,或是需要即時取得更多相關訊息,可按這裡加入FB專頁。
若這篇文章對您有所幫助,請幫我們分享出去,謝謝您的支持。

2019年8月19日 星期一

使用IoC與DI有何意義? (一) 它到底是甚麼?

其實我們在好幾年前,已經談過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

為了實現這個功能,我們的設計了底下這樣的程式,它包含一個主程式和兩個類別,我們先看類別的部分:

我們來談談上面這兩個類別的職責,為什麼計算薪資該分成兩個類別? 為何不能寫成一個? 如果把上面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類別的相依性,從原先針對類別實作的相依,改為對介面(抽象)相依。這樣做有什麼好處? 砍斷對具體實作的相依性,才能夠為將來系統能夠動態抽換計算公式而鋪路…我們接著往下看。

做了這樣的修改之後,未來倘若我們希望改變薪資的具體計算方式,我們無須修改整套程式碼,只需要再建構一個新的薪資計算類別,然後修改主程式即可(你如果一直往後看,未來只需要透過『設定』,連主程式都無須修改):

注意上面的程式碼當中,主程式有兩段6-9行與12-16行,本質上這兩段code幾乎完全一樣,唯一的差別是new SalaryCalculator(…)類別時傳入建構子的參數有所不同,一個是計算員工的公式類別實作,一個是計算老闆的公式類別實作。

你會發現由於老闆請假不扣薪(26行),因此即便參數完全一樣,但計算出的結果有所不同:

到這邊,我們已經為了動態抽換鋪好了基礎,接著我們來看,如何在ASP.NET Core中透過DI Services實現動態抽換。

-----------------------------

本文內容來自於『團隊開發與架構設計實務』課程,我們最近要開課囉,依照過去經驗這門課幾乎都是秒殺,如果你需要預先保留席次,請點選這裡登記唷。

若您要詢問問題,或是需要即時取得更多相關訊息,可按這裡加入FB專頁。
若這篇文章對您有所幫助,請幫我們分享出去,謝謝您的支持。

Related Posts Plugin for WordPress, Blogger...

熱門文章