2011年11月23日星期三

Dependency Injection 筆記 (6)

現在我們已經知道寬鬆耦合是很重要的設計原則,也看過一些簡單的範例,對相依性注入的寫法有一些初步的印象。這裡要討論的是:如何判斷哪些東西要注入?哪些不要?(決定的關鍵在於相依類別的穩定度)

聲明:這是我閱讀 Dependency Injection in .NET 一書的筆記,寫作的內容架構與靈感多來自本書。若覺得這些筆記對您有幫助,主要應歸功於該書的作者 Mark Seemann。筆記內容可能會持續修訂更新,請勿以複製全文的方式轉載。

哪些東西要注入?

一旦你開始感受到寬鬆耦合的好處,在設計類別的時候,可能會傾向於讓所有類別之間的耦合都保持寬鬆。換言之,碰到任何需求都想要先寫成介面,然後透過相依性注入的方式(例如先前範例中使用的 Constructor Injection)來建立物件之間的關聯。

不過,天下沒有白吃的午餐,寬鬆耦合也是。每當你將類別之間的相依性抽離出來,再透過某種方式,於特定時機注入相依性,這樣的動作都會產生一些額外成本。不管三七二十一,將所有物件都設計成可任意替換、隨時插拔,通常不會是個好主意。

以 .NET 基礎類別庫(Base Class Library;BCL)為例,此類別庫包含許多組件,各組件又包含許多現成的類別,方便我們直接取用。每當你在程式中使用 BCL 的類別,例如 String、DateTime、Hashtable 等等,就等於在程式中加入了對這些類別的依賴。此時,你會擔心有一天自己的程式可能把這些類別替換成別的類別嗎?如果會/不會,是什麼原因讓你擔心/放心?

答案取決於當時你對特定類別/元件是否會經常變動的信心程度。而所謂的「經常變動」,也會依應用程式的類型、大小而有不同的定義。

相對於其他在網路上找到或購買的元件,我們可能會覺得 .NET BCL 裡面的類別應該會相對穩定得多,亦即不會隨便改來改去,導致既有程式無法編譯或正常執行。這樣的認知可能有一部分來自於我們對該類別的提供者--Microsoft--有相當程度的信心,另一部分則可能是根據經驗來判斷。

然而,並非所有相依性都生來平等--即使是微軟 .NET Framework 提供的元件,在使用時也有可能需要考慮採用寬鬆耦合的寫法(例如 SqlConnection、Linq To SQL),以免將來碰到某些需求變動時,得花很多工夫修改既有的 code。

接縫

由於近日將出遠門,到一個比較冷的地方,所以買了件 G 牌的防風大衣,這件大衣有個好處:如果覺得不夠暖,還可以買搭配的內裡,透過拉鍊直接將兩件衣服連接起來。兩件衣服能夠緊密結合,靠的是事先設計好的接縫(相同規格的拉鍊)。

每當我們決定針對介面來寫程式,而不是直接使用特定的具象類別時,就像在程式中加入一條接縫(seam)。接縫可以連接兩個獨立的物件,也方便我們分別對這些物件進行測試與除錯。

在先前的範例中,ILogger 介面就是這裡所說的接縫。透過這條接縫,你可以在程式中銜接各種符合 ILogger 規格的記錄器,例如 ConsoleLogger、WindowsEventLogger 等等。當然,就如稍早所說的,這條接縫也會帶來額外的成本,並不是所有的東西都應該設計成可任意抽換。那麼,什麼情況才適合加入接縫呢?

如前面所說的,答案取決於當時你對特定類別/元件是否會經常變動的信心程度。換個方式來說,就是類別的穩定度啦。在剛開始練習使用 DI 的時候,可以先從這個角度來思考。

我們可以將類別與類別之間的相依性概略分成兩種:穩定相依(stable dependencies)與不穩定相依。

穩定相依

一般而言,你可以將大部分(不是全部)的 .NET BCL 視為穩定的元件,亦即可安心地直接在自己的程式中使用這些類別。之所以覺得它們「穩定」,是因為:(1) 這些類別都已經寫好了,可直接在程式中重複使用,而且 (2) 就算有新版本推出,也能繼續跟舊版本保持相容,而不會破壞既有的應用程式(我們相信 Microsoft 或特定廠商、開發社群會盡量做到這點)。

除此之外,還要再加上一點:(3) 從現在到你可預見的將來,在此期間裡,你絕對不會想要把某個類別換掉。

符合上述幾個條件的類別,便可將它歸類為具有足夠穩定度的相依性。如果要更簡單一點,就反過來看:只要是可能變動的相依性,就是不穩定的。
NOTE  如果你有用過 DI Container,或者大概知道它的用途,可以先看一下這裡的補充:基本上,DI Container 大致都符合上述三個條件,但第 (3) 點可能有待斟酌。因為目前有許多 DI Container 的實作品可供選擇,很可能我們在初次評估時不是那麼詳細周全,以致於第一次用了某個 DI Container 之後,過兩三個月就把它給換掉了。因此,在使用 DI Container 時有個注意事項:只能在應用程式的組合根(Composition Root)中使用 DI Container。註:Composition Root 就是組合元件的地方,此系列的第 4 集曾提過。
不穩定相依

就是那種會翻臉如翻書經常修改的類別或介面啦(yes, 介面沒設計好,也可能會常換規格,這影響也很嚴重)。

如果你發現欲使用的類別或元件符合下列任何一種情形,很可能該類別是不夠穩定的:
  1. 需要額外設定許多執行環境的組態。
    一個明顯的例子就是對關聯式資料庫的存取:如果我們的程式大量依賴特定的資料存取技術(例如 LINQ to SQL),那麼將來如果需要更換資料存取技術,就會變得非常困難。但這裡其實也有個比較難處理的問題:LINQ to SQL 剛推出時,很多人都覺得很好用啊,哪知道它這麼快就被 LINQ to Entity 取代了?採用新技術的一個風險,就是很難確定該技術的壽命是否夠長。這也是為什麼常聽到有人建議,至少等到 2.0 版出來之後,再來決定是否要正式採用的原因。當然其中可能還牽涉到個人對新技術的 sense、遠見、以及經驗法則等因素,這就不細說了。
  2. 目前還在開發中,尚未完成,或需求仍有不確定的地方。
    還沒完成的東西,當然很難稱得上穩定。
  3. 不是每台開發機器上面都有。
    有些元件或工具可能很昂貴,或部署的組態比較複雜,以至於只存在於特定機器上。這樣的相依性對單元測試而言是很不利的。
小結

說了那麼多,到底我該選擇 Entity Framework 還是 NHibernate? Enterprise Library 還是 Spring.NET 或二者都用?好像還是很難決定啊!

如果我說「看情況」或「看個人/團隊/專案而定」,可能有人會覺得,那就跟沒回答一樣吧?

但事實就是這樣....Orz

有時候,似乎就只能選擇被某套元件或框架綁住(所以問題只剩下:你願意被誰綁住?)。否則,我們可能得自己寫一套類別庫,在應用程式和資料存取元件當中插入一層接縫,以便將來可隨時替換 Entity Framework、NHibernate、或其他資料存取技術。倒不是說技術上做不到,而是得看應用程式是否需要這麼彈性的架構。如果將來有可能需要把資料存取層抽換掉,例如從關聯式資料存取技術換成雲端資料存取技術,那麼在設計架構的時候就得多費一些心思,讓整個 business layer 和 data access layer 各自獨立、寬鬆耦合才行了。

ps. 若有後續,我打算按照書中的步調,以 ASP.NET MVC + Entity Framework 三層式架構的範例來說明為什麼我們經常看到的所謂 n-tier 架構在寬鬆耦合方面還是不及格。但如文中所說,我將遠行(赴美)幾個月的時間,此系列的筆記可能暫時中斷。目前只能說看情況、看時間,量力而為了。

0 回應: