2019年8月31日

開發 | 什麼時候要使用依賴注入

全部依賴都應該要被注入嗎?如果不、怎樣的依賴需要被注入?

有地方怪怪的

當代的程式開發框架,通常可以找到很方便的依賴注入、控制反轉容器工具,像PHP Laravel Framework就內建了很方便的IoC Container,讓開發者可以輕鬆地處理類別的依賴注入。但有時候太方便反而會造成濫用,或是有怪味道的產生。

常見的詭異處是,開發者將一個類別所需要的其他物件通通以依賴注入的方式注入類別,不論是相依於interface或是class,以供類別使用,導致類別建構子的參數變得很多:

class Demo {
    public function __construct(
        IA $a, IB $b, IC $c, ID $d, E $e, F $f, G $g, H $h
    ) {}
}

雖然說Container可以幫忙處理這樣的問題,自動產生物件注入,但是這麼多物件需要被注入,開發者仍然必須要在某個地方,可能是Provider,去定義大量的類別所依賴物件的對應關係,不免讓人想問,這個類別是否相依於太多其他類別了?

為什麼需要依賴注入?

回過頭來思考為什麼我們需要依賴注入?

  1. 希望可以不用變更原類別、模組,透過注入的方式將不同的實作注入類別,讓程式可以不用更改就維持擴充性。
  2. 希望可以不用管相依的套件怎麼實作,在模組內只要依照介面使用就可以了。

如果將所有的類別都使用依賴注入的方式,會將一些只是內部實作需要的類別暴露於外,而失去了類別、模組對外封裝的意義,一來使用者需要知道太多資訊才能使用此類別(沒有人可以保證你永遠可以依賴Container幫你實體化類別),二來是這些依賴真的有必要開放給外部注入嗎?會不會因此導致類別執行了錯誤的行為,失去原本類別的職責呢?

所以開發者必須要思考,哪些依賴是需要被注入,而哪些是不需要的,才不會造成濫用而導致其他副作用。

可能的思考方向

開發者在選擇哪些依賴要開放讓外面注入時,可以先思考以下問題:

  1. 哪些依賴關係是公開的、實際上需要外部提供的服務?
  2. 哪些依賴是允許用來客製化或是用來擴展類別、模組的?
  3. 哪些依賴只是類別、模組的實作細節?

當依賴屬於第一類的時候,最好由外部注入,因為這些類別可能會相依於開發環境,比如說資料庫連線,跟MySQL連線的實作或是跟Oracle連線的實作會是不一樣的,但對於我們的類別而言,與資料庫溝通並不是他所關注的部分,我們的類別只是想要使用別人提供的服務。這類的依賴就可以選擇用注入的方式。

第二類的話通常是用來提供類別的擴充性,或者是讓使用者設定類別,比如說,決定某個類別要使用Email通知,還是簡訊通知,或是開發者可以實做別的通知方式,注入類別使用。但如果是不被允許客製化或擴充的部分,像是模組的核心邏輯,就不應該被注入,因為這樣可能會破壞模組的行為。

第三類的話,則是不建議用注入的方式。因為如果只是模組的實作細節,其實是該被封裝起來不揭露於外部的,這樣可以保護類別的核心行為正確、還有模組的整體性。再者,有些細微末節實作細節,對模組外部來說實在是多餘的,比如模組內部資料格式驗證的類別,由外部注入實在是相當沒有意義。

結論

IoC container是一個很方便的工具,但對開發者來說,沒有任何一項工具或Pattern是萬用的,每一種工具都有其最適合的環境、和限制所在,因此在使用上還是需要被管理的、要視情況使用,避免濫用造成的副作用,反而失去了使用工具的意義。

參考

Dependency Injection Trade-offs