ScriptableObject 作為 Dependency Injection 的工具

在 Unity 中,使用 Singleton Manager 是一種方便的設計模式,但它也有一些潛在的缺點和風險。如下

  1. 由於 Singleton 通常是全域可訪問的,這使得任何腳本都可以直接與其交互,導致程式碼之間的強耦合,使其難以測試以及維護
  2. 由於 Singleton 通常是靜態的並且在整個應用程式中持續存在,在單元測試時很難模擬或替換。
  3. 隨著功能增多, Singleton Manager 的代碼會變得臃腫,難以維護或擴展,可能導致其責任過多(違反單一職責原則,SRP)。
  4. 通常會使用 DontDestroyOnLoad 保持 Singleton 在場景切換中持久化,但這可能導致重複生成 Singleton (如果新場景中也有同樣的 Manager)導致出現錯誤或是多餘的資源佔用。
  5. 隱藏的依賴性,由於 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:

評論