Toolbox
Chris Oakley (Talk | contribs) (→Implementation) |
Isaiah Kelly (Talk | contribs) (Rewrote script and sample code and improved documentation.) |
||
Line 1: | Line 1: | ||
== Introduction == | == Introduction == | ||
− | + | The "Toolbox" is a concept introduced by J.B. Rainsberger in the article [https://www.ibm.com/developerworks/webservices/library/co-single/index.html Use your singletons wisely]. The basic idea being that the application, not the components, should be the singleton. This basically means creating one singleton (the toolbox) that aggregates and manages all other classes that would normally be singletons themselves. This improves upon the concept by reducing [http://en.wikipedia.org/wiki/Coupling_(computer_programming) coupling] and making it much easier to [http://en.wikipedia.org/wiki/Unit_testing test]. | |
− | + | == Implementation == | |
+ | === Toolbox.cs === | ||
+ | <syntaxhighlight lang="csharp"> | ||
+ | using System; | ||
+ | using System.Collections.Generic; | ||
+ | using UnityEngine; | ||
− | + | public class Toolbox : Singleton<Toolbox> | |
+ | { | ||
+ | // Used to track any global components added at runtime. | ||
+ | private Dictionary<string, Component> m_Components = new Dictionary<string, Component>(); | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | // Prevent constructor use. | |
− | + | protected Toolbox() { } | |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | } | + | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | + | private void Awake() | |
− | + | { | |
− | + | // Put initialization code here. | |
− | + | } | |
− | + | ||
− | |||
− | + | // Define all required global components here. These are hard-codded components | |
+ | // that will always be added. Unlike the optional components added at runtime. | ||
− | |||
− | |||
− | |||
− | + | // The methods below allow us to add global components at runtime. | |
− | + | // TODO: Convert from string IDs to component types. | |
− | + | public Component AddGlobalComponent(string componentID, Type component) | |
− | + | { | |
− | } | + | if (m_Components.ContainsKey(componentID)) |
+ | { | ||
+ | Debug.LogWarning("[Toolbox] Global component ID \"" | ||
+ | +componentID + "\" already exist! Returning that." ); | ||
+ | return GetGlobalComponent(componentID); | ||
+ | } | ||
− | + | var newComponent = gameObject.AddComponent(component); | |
− | + | m_Components.Add(componentID, newComponent); | |
− | + | return newComponent; | |
− | + | } | |
+ | |||
+ | |||
+ | public void RemoveGlobalComponent(string componentID) | ||
+ | { | ||
+ | Component component; | ||
+ | |||
+ | if (m_Components.TryGetValue(componentID, out component)) | ||
+ | { | ||
+ | Destroy(component); | ||
+ | m_Components.Remove(componentID); | ||
+ | } | ||
+ | else | ||
+ | { | ||
+ | Debug.LogWarning("[Toolbox] Trying to remove nonexistent component ID \"" | ||
+ | + componentID + "\"! Typo?"); | ||
+ | } | ||
+ | } | ||
+ | |||
+ | |||
+ | public Component GetGlobalComponent(string componentID) | ||
+ | { | ||
+ | Component component; | ||
+ | |||
+ | if (m_Components.TryGetValue(componentID, out component)) | ||
+ | { | ||
+ | return component; | ||
+ | } | ||
+ | |||
+ | Debug.LogWarning("[Toolbox] Global component ID \"" | ||
+ | + componentID + "\" doesn't exist! Typo?"); | ||
+ | return null; | ||
+ | } | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | == | + | == Requirements == |
+ | * Uses [[Singleton]] base class. | ||
+ | |||
+ | == Usage == | ||
+ | Any required components should be defined in the toolbox class itself. So for example, let's say we have a very important '''PlayerData''' MonoBehaviour. We should create an instance of this in the toolbox and add a public function to allow us to retrieve it. | ||
− | |||
− | |||
<syntaxhighlight lang="csharp"> | <syntaxhighlight lang="csharp"> | ||
− | + | ||
− | + | private PlayerData m_PlayerData = new PlayerData(); | |
− | + | ||
− | + | public PlayerData GetPlayerData() | |
− | + | { | |
− | + | return m_PlayerData; | |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
+ | Now to retrieve this global component we simply use '''Toolbox.Instance''' followed by the function we defined in the toolbox. | ||
− | |||
<syntaxhighlight lang="csharp"> | <syntaxhighlight lang="csharp"> | ||
− | + | var playerData = Toolbox.Instance.GetPlayerData(); | |
+ | Debug.Log(playerData.CurrentLevel); | ||
+ | </syntaxhighlight> | ||
− | + | You can also add, get or remove global components at runtime. However, these methods use strings for IDs which are error-prone, so use with cation! | |
− | + | ||
− | + | <syntaxhighlight lang="csharp"> | |
− | // | + | // Add and get a global component. |
− | + | var playerData = Toolbox.Instance.AddGlobalComponent("PlayerData", PlayerData); | |
− | + | Debug.Log(playerData.CurrentLevel); | |
− | + | ||
− | + | // Getting a global component. | |
− | + | var playerData = Toolbox.Instance.GetGlobalComponent("PlayerData"); | |
− | + | Debug.Log(playerData.CurrentLevel); | |
− | + | ||
− | + | // Delete existing global component. | |
− | + | Toolbox.Instance.RemoveGlobalComponent("PlayerData"); | |
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
− | + | ||
</syntaxhighlight> | </syntaxhighlight> | ||
+ | |||
+ | '''Note:''' It's best not to add or delete components directly on the toolbox yourself, since this will likely lead to problems. | ||
+ | |||
+ | [[Category: C Sharp]] | ||
+ | [[Category: Design Patterns]] |
Latest revision as of 02:40, 14 November 2018
Contents |
[edit] Introduction
The "Toolbox" is a concept introduced by J.B. Rainsberger in the article Use your singletons wisely. The basic idea being that the application, not the components, should be the singleton. This basically means creating one singleton (the toolbox) that aggregates and manages all other classes that would normally be singletons themselves. This improves upon the concept by reducing coupling and making it much easier to test.
[edit] Implementation
[edit] Toolbox.cs
using System; using System.Collections.Generic; using UnityEngine; public class Toolbox : Singleton<Toolbox> { // Used to track any global components added at runtime. private Dictionary<string, Component> m_Components = new Dictionary<string, Component>(); // Prevent constructor use. protected Toolbox() { } private void Awake() { // Put initialization code here. } // Define all required global components here. These are hard-codded components // that will always be added. Unlike the optional components added at runtime. // The methods below allow us to add global components at runtime. // TODO: Convert from string IDs to component types. public Component AddGlobalComponent(string componentID, Type component) { if (m_Components.ContainsKey(componentID)) { Debug.LogWarning("[Toolbox] Global component ID \"" +componentID + "\" already exist! Returning that." ); return GetGlobalComponent(componentID); } var newComponent = gameObject.AddComponent(component); m_Components.Add(componentID, newComponent); return newComponent; } public void RemoveGlobalComponent(string componentID) { Component component; if (m_Components.TryGetValue(componentID, out component)) { Destroy(component); m_Components.Remove(componentID); } else { Debug.LogWarning("[Toolbox] Trying to remove nonexistent component ID \"" + componentID + "\"! Typo?"); } } public Component GetGlobalComponent(string componentID) { Component component; if (m_Components.TryGetValue(componentID, out component)) { return component; } Debug.LogWarning("[Toolbox] Global component ID \"" + componentID + "\" doesn't exist! Typo?"); return null; } }
[edit] Requirements
- Uses Singleton base class.
[edit] Usage
Any required components should be defined in the toolbox class itself. So for example, let's say we have a very important PlayerData MonoBehaviour. We should create an instance of this in the toolbox and add a public function to allow us to retrieve it.
private PlayerData m_PlayerData = new PlayerData(); public PlayerData GetPlayerData() { return m_PlayerData; }
Now to retrieve this global component we simply use Toolbox.Instance followed by the function we defined in the toolbox.
var playerData = Toolbox.Instance.GetPlayerData(); Debug.Log(playerData.CurrentLevel);
You can also add, get or remove global components at runtime. However, these methods use strings for IDs which are error-prone, so use with cation!
// Add and get a global component. var playerData = Toolbox.Instance.AddGlobalComponent("PlayerData", PlayerData); Debug.Log(playerData.CurrentLevel); // Getting a global component. var playerData = Toolbox.Instance.GetGlobalComponent("PlayerData"); Debug.Log(playerData.CurrentLevel); // Delete existing global component. Toolbox.Instance.RemoveGlobalComponent("PlayerData");
Note: It's best not to add or delete components directly on the toolbox yourself, since this will likely lead to problems.