Unity3D - 單例模式和靜態類

版權聲明:本文爲博主原創文章,轉載請註明出處。 https://blog.csdn.net/xmousez/article/details/53897946

Unity3D的API提供了很多的功能,但是很多流程還是會自己去封裝一下去。當然現在網上也有很多的框架可以去下載使用,但是肯定不會比自己寫的用起來順手。

對於是否需要使用框架的問題上,本人是持肯定態度的,把一些常用方法進行封裝,做成一個功能性的框架,可以很大程度上提高代碼的效率,維護也方便。

對於網絡上很多教程上使用的“遊戲通用MVC框架”,現在看來並不符合MVC這種結構性框架的設計思想:要知道,MVC最初是被設計爲Web應用的框架,而遊戲中的很多事件並不是通過用戶點擊UI發生的,View和Controller在遊戲邏輯中的佔比一般都少的可憐,而且很多教程上把Model剝離出很多“Manager”模塊,甚至有人把View和Controller合在一起寫了UIManager——連MVC的結構都沒了,爲啥還要稱之爲MVC框架呢?
MVC: “人紅是非多。。。。”

目前大部分的遊戲框架——特別是小型項目的遊戲框架——都是把一些數據的特定行爲進行了一下封裝:生成一個物件,播放一個特效,進行一次隨機事件等。當然也會有一些結構性的設計或者資源管理設計如:UI的回退棧或者回退鏈,場景的載入記錄和切換,下載隊列的管理等。

在Unity的框架設計中,有一個詞會經常見到:單例模式(singleton)。單例模式就是在整個遊戲中只使用某個類的一個實例,核心的一句話就是public static T Instance;即在類中定義了一個靜態的自身實例供外部使用,調用方法時就是:T.Instance.Function()。在本人最初接觸這種設計方式時經常會與靜態類弄混淆,T.Function()。中間差了一個靜態Instance,很多時候好像區別不大。。。

在接近兩週左右的時間裏,我一直在糾結於自己正在寫的框架到底應該寫成單例模式的還是靜態模式的,今天剛好對這個問題有了一個新的想法:靜態可不可以理解爲一種封閉性很強的單例?

首先回想一下靜態的兩個常識:

  1. 靜態類不能繼承和被繼承!(嚴格點說是隻能繼承System.Object)也就是說你的靜態類不可能去繼承MonoBehaviour,不能實現接口。
  2. 靜態方法不能使用非靜態成員!如果你大量使用靜態方法,而方法裏又需要用到這個類的成員,那麼你的成員得是靜態成員。

第2點需要注意:如果你想在Unity的編輯器下調整某個參數,那麼這個參數就不能是靜態的(哪怕你自定義EditorWindow去修改這個值也沒用),解決的辦法是通過UnityEngine.ScriptableObject去存放配置(生成*.asset文件),然後在運行中通過LoadAsset去加載,然後再改變靜態成員。至於原因,相信不難理解——你看到的所有Unity組件都是一個個實例,你要通過Unity的編輯器去配置,那麼你就得有一個這樣的可配置實例。

從面向對象上想一下:靜態方法或者靜態類,不需要依賴對象,類是唯一的;單例的靜態實例,一般就是唯一的一個對象(當然也可以有多個)。差別嘛。。。好像也不大。。。

如果這樣考慮沒有錯,那再回頭比較一下兩種方式:

  1. 靜態(靜態方法或者靜態類),代碼編寫上絆手絆腳,方法調用很方便,運行效率高一丟丟。邏輯面向過程,不能很好地控制加載和銷燬。
  2. 單例(類的靜態實例),代碼編寫和其他類完全一樣,繼承抽象模版接口都可以,Unity裏也很方便進行參數配置,不過使用麻煩有犯錯的可能性(必須通過實例調用方法),效率不如靜態(但是也不會有很大影響吧)。

如果這些說法太抽象,那我再給出一個常見的問題:如果你的框架有一個SoundManager能夠管理所有的聲音播放,那麼你會怎麼去實現?
(在剛接觸AudioSource這個組件的時候,我想的是每一個聲音都由一個AudioSource去播放。但是後來發現完全沒必要,AudioSource有靜態的PlayClipAtPoint方法去播放臨時3D音效,同時有實例方法PlayOneShot去播放臨時音效(2D和3D取決於當實例的SpatialBlend)。如果沒有特殊的需求,那麼一個AudioSource循環播放背景音樂,上述兩種方法播放遊戲中的特效音頻,這對於大部分遊戲已經足夠了。)

那麼問題來了:你的SoundManager播放聲音的方法如果是靜態的,那麼AudioSource組件必須在代碼中通過各種方式去獲取(新建組件或者獲取特定GameObject下的組件)——因爲保存這個組件的變量必須是靜態的,也就不能通過Unity的編輯器去賦值。如果不去閱讀代碼那麼用戶完全不知道這是一個什麼樣的組件獲取流程,如果我破壞這個流程(同名物體,包含互斥組件等),那麼這個Manager很有可能會出現不可預料的異常。
而繼承MonoBehaviourRequireComponent(typeof(AudioSource)),怎麼看也比“爲了靜態而靜態”的代碼要方便健壯的多。

實際上到這裏已經可以基本總結出何時需要使用單例了:

  1. 只要你的類需要保存其他組件作爲變量,那麼就有必要使用單例;
  2. 只要你有在Unity編輯器上進行參數配置的需求,那麼就有必要使用單例;
  3. 只要你的管理器需要進行加載的順序控制,那麼就有必要使用單例(比如熱更新之後加載ResourcesManager);

當然,這裏都只是“有必要”,並不是“必須”。兩者區別最大的地方,一個是方便寫,一個是方便用。方便寫的代價是每次調用加個instance,方便用的代價則是放棄了面向對象和Unity的“所見即所得”,孰輕孰重,自己抉擇。

另一方面,和“爲了靜態而靜態”一樣,“爲了單例而單例”同樣是一個不合理的設計。這樣的解釋仍然是那麼的模糊,那麼,就給自己定義一個最簡單的規則吧——如果你的單例類裏沒有任何需要保存狀態的變量,那麼這個類裏的方法就可以全都是靜態方法,這個類也可以是個靜態類。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章