2020年10月23日 星期五

使用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之故,請務必看過前面這篇才能理解底下情境)

上面這個頁面是 .net core 的 Razor Page Model,如果你不熟這個架構,只會MVC,那你就暫時把它當作某個Controller來看好了,後面我們會介紹Console和MVC模式下的DI。

它的執行結果如下:

頁面被載入的時候,會運行到OnGet()方法,其中的程式碼計算出的薪資是 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,砍斷針對類別實作的相依,換成以介面設計的方式來實踐。

到這邊你應該理解了為何前面要先實現IoC,因為IoC是我們後面能夠DI的基礎。前面說過,這邊再次提醒,DI除了能夠幫我們容易抽換(例如上面這個例子是抽換薪資計算的公式),但除此之外,同時可以實現提高可測試性與程式碼高可維護性的效果(一時想不通為什麼嗎? 我們後面會再慢慢介紹)。很有趣是吧,先消化一下。未來我們會接續著來談談,為什麼DI能夠有助於提高可測試性,以及如何連程式碼都不要改,只需要透過『設定』,就可以為系統注入不同的類別實作…

有個問題...

在結束這一篇前先問你一個問題,為何 asp.net 的頁面被載入的時候,建構子會被注入我們所需要的物件執行個體? 我知道,我知道,是因為我們在 Startup.cs 中這麼指定的。但...你有沒有想過,為何我們這麼指定,系統就會這麼運行? 具體誰做的呢? 就好比上例中的 PageModel,一定得有人把這個類別給 new 起來,然後呼叫它的建構子,並且幫我們把new好的_SalaryFormula物件實體傳入,否則程式碼無法運作啊,做這件事情的人到底是誰呢? 這個問題隱含著另一個問題...如果我們用的不是 asp.net ,是Console App,那也可以這樣注入嗎?

如果想不出答案,請接著往下看下一篇,我們要回到console中繼續探索 .net core中的DI...

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

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

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

2 則留言:

null 提到...

老師您好, 請教一下, 我在實務上經常遇到SalaryFormula跟BossSalaryFormula並存的情況. 請問這個要如何用DI處理? 謝謝

isDavid 提到...

先談需求,如果就上面這個薪資計算的例子來說。實務上,你的薪資計算公式可以全寫在"一組"類別裡面。可以規劃成一家公司一組類別,而非一個類別一種公式。
如果是一家公司一組類別,軟體賣到不同公司就不用只需要抽換即可。就好比,你在一套系統中,大概不會用兩種以上的LOG機制,大多只會用一種,在不同的情境下抽換即可。

然後再說萬一,你真的需要在系統中有多種以上的實作方式,其實你可以直接自己建立執行容器(host),就可以帶入多種實作,這部分,我們會在下一篇介紹...

Related Posts Plugin for WordPress, Blogger...

熱門文章