Dependency Injection in Unity3D Part 1: 什麼是相依性注入?

設想一個例子:我們正在做一個規模很小、很簡單的2D動作遊戲。
假設遊戲會有一個英雄(Hero),攜帶著各種不同的武器(Weapon)。
我們現在要先實踐出可以讓玩家攻擊的功能,程式碼看起來可能像這樣

看起來很合理,接下來就是要想辦法為英雄拿到武器了。

什麼是相依性?
假如B沒有A就不能存在的話,則B相依於A。

用上面的例子來說,Hero沒有Weapon就無法存在,則Hero相依於Weapon。

現在,你要怎麼為Hero拿到Weapon的實體?

方法一:Singleton

你可能會想,Hero在遊戲中應該只有一個,那麼Weapon應該也只會有一個。
(相信我,你不該這樣假設的)
根據這個假設,可以使用singleton pattern…

不要再濫用singleton pattern了,這種寫法用到中期左右來一個變動,像是設計師跟你說要增加單機多人遊玩功能時,就會發現程式已經僵固化,改不動了。

方法二:繼承MonoBehaviour後放在場景上

那麼換個方法,讓Weapon繼承MonoBehaviour,這樣就可以放在場景上,利用SerializeField來獲得實體。Hero沒有繼承MonoBehaviour?那就讓它也繼承吧!

這是很常見的作法,但仍有一些意見:
1. Weapon不用來顯示,也不會用到Awake、Start、Update等函式,只是為了連接至Hero才逼不得已繼承MonoBehaviour,而且連帶Hero也要跟著繼承MonoBehaviour。其實怎麼聽都是很詭異的做法。

2. 想像一份多人開發的遊戲,現在你放Weapon在場景上,同時間其他開發人員放了InventorySystem在場景上,結果導致你上傳檔案(git或svn)至伺服器時衝突了。因為.scene是二進位檔案,無法合併。因此你要重新改場景,要不然就是他重新改場景。在沒有良好經驗與規範的開發環境下,這種事情大概半年就可以發生數十次。

3. 一樣的問題,你可能不會只有一種Weapon。不過即使如此,創建一個WeaponGroup來管理所有Weapon,然後繼承MonoBehaviour並放在場景上,仍然不是一個好選擇。

方法三:new

嗨,忘記這個東西了嗎?

自行產生相依性在遊戲開發常常會出現一個問題:要定義各種不同的產生規則,然後為了這個產生規則將程式碼弄得不太乾淨。
以這個例子來說,開發者知道Hero拿的武器編號不一定是1號,因此之後要想辦法處理這部分的邏輯,一定會寫出額外的程式碼。簡單的話倒是還好,複雜一點的話加到200行的我都看過。

方法四:相依性注入(Dependency Injection,簡稱DI)

DI是透過建構式或公開方法,將相依性當成參數傳遞至物件內的方法。
(通常推薦透過建構式)

所以我們要回過頭去處理當初創造Hero實體的程式碼。

我們不用再創建不必要的singleton,不用再繼承不需要的MonoBehaviour,也不用不斷的處理場景檔案的衝突。

DI與直接透過new來產生實體來比較,DI將創造相依性的責任,與執行邏輯分開了。也就是說,更加的符合「單一職責原則」(Single responsibility principle)
(「職責」指的是「改變的原因」,也就是修改腳本的原因)

使用DI最大的好處是,Unity的程式設計師再也不需要為了獲取特定物件實體,而妥協的改變遊戲邏輯的軟體架構。因為DI將獲得實體的複雜度都集中在物件創建時期了。

但它還是有個大問題:
專案規模一旦變大,像這樣手動撰寫注入邏輯就十分的痛苦
(我曾經聽說過2000行左右的手動注入邏輯)

DI框架

相依性注入其實在一般軟體業是很常見的。
Spring、Ninject、Unity(沒錯,相同名字)都是常見的框架。
免去了大量的手動注入邏輯。

Zenject是專門為Unity遊戲開發打造的DI框架,個人對它評價十分高。
作者仍持續在維護Zenject,在社群(Google Forum)中也會很迅速且清楚的回答使用者的問題。
https://www.assetstore.unity3d.com/en/#!/content/17758

https://assetstore.unity.com/packages/tools/utilities/extenject-dependency-injection-ioc-157735

為什麼Unity社群很少提到DI?

我認為Unity社群很少提到DI是因為,入門者接觸Unity時就被灌輸了盡量依賴MonoBehaviour的概念,尤其官方範例一向都是小型範例,而且只針對Unity的特別功能來進行解說。從那裡看不出專案規模變大後的問題,然後就因此忘記了一般軟體開發的方式。
(我沒有開玩笑,我就是忘記的其中一人,被糟透的軟體架構敲醒後才開始好好思考)

接下來的文章會以Zenject來進行實作上的講解。

發佈留言