在 Unity 中,使用 Singleton Manager 是一種方便的設計模式,但它也有一些潛在的缺點和風險。如下
- 由於 Singleton 通常是全域可訪問的,這使得任何腳本都可以直接與其交互,導致程式碼之間的強耦合,使其難以測試以及維護
- 由於 Singleton 通常是靜態的並且在整個應用程式中持續存在,在單元測試時很難模擬或替換。
- 隨著功能增多, Singleton Manager 的代碼會變得臃腫,難以維護或擴展,可能導致其責任過多(違反單一職責原則,SRP)。
- 通常會使用 DontDestroyOnLoad 保持 Singleton 在場景切換中持久化,但這可能導致重複生成 Singleton (如果新場景中也有同樣的 Manager)導致出現錯誤或是多餘的資源佔用。
- 隱藏的依賴性,由於 Singleton 是全域性的,腳本中直接調用 Singleton 的方法可能隱藏了實際的依賴性,導致在代碼重構或移植時,可能很難理清某些功能到底依賴於哪些 Singleton 。
我們可以使用 ScriptableObject 作為 Dependency Injection 的工具,用來減少 Singleton Manager 的使用。
首先建立一個 Abstract Class
1 2 3 4 5 6
| public abstract class BaseDataService : ScriptableObject { public abstract void SaveLevelData(LevelData levelData);
public abstract LevelData LoadLevelData(int level); }
|
之後根據需要的實作去繼承這個 BaseDataService 類,例如你想要實作將存擋儲存到本機上的檔案中時,可以建立一個 FileGameDataService
,然後在該類實作這些方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| [CreateAssetMenu(fileName = "FileGameDataService", menuName = "ScriptableObject/Data/GameDataService")] public class FileGameDataService : BaseDataService { public override void SaveLevelData(LevelData levelData) { string json = JsonUtility.ToJson(levelData); File.WriteAllText(Application.persistentDataPath + $"/level_{levelData.level}_{levelData.levelType}_{levelData.levelDifficulty}_data.json", json); }
public override LevelData LoadLevelData(int levelIndex, LevelType levelType, LevelDifficulty levelDifficulty) { string path = Application.persistentDataPath + $"/level_{levelIndex}_{levelType}_{levelDifficulty}_data.json"; if (File.Exists(path)) { string json = File.ReadAllText(path); return JsonUtility.FromJson<LevelData>(json); } return null; } }
|
使用 Unity 編輯器建立一個 ScriptableObject。

在需要使用讀檔存檔方法的 MonoBehaviour 類中,加入 [SerializeField] private BaseDataService dataService
參考
1 2 3 4 5 6 7 8 9
| public class GameManager : MonoBehaviour { [SerializeField] private BaseDataService dataService;
public void SaveGame(LevelData levelData) { dataService.SaveLevelData(levelData); } }
|
最後,把這個 FileGameDataService 設定上去即可。

Reference: