Reusable Generic Singleton

 

What is it?

A method of ensuring there is only one instance of a class and providing global access to that class.

Ensuring there is only one instance means you won’t be able to freely instantiate the object. It is instantiated once and only once. All classes that access the object will do so through the one instance. Whereas global access provides a method of retrieving the object from anywhere in your codebase.

 

Why should you use it

When you need an object that meets the above criteria i.e. it needs to be globally accessible and have only one instance available at any one time. A debug log hidden behind preprocessor directives is a good example. This provides the ability to output debug information to any class that requires it but it is not shipped with your final game/product.

Additional benefits include:

  • As it can be lazy initialised, an instance of the object is not created if no one requests it. This saves memory and processing time (great for games!).
  • Unlike a static class, a singleton has access to data only known once the program is running (e.g. any variables that need to be loaded/initialised, any data loaded from file etc.) as it is initialised at runtime.

 

Why shouldn’t you use it?

  • Lazy initialisation, while having some possible benefit, can also prove problematic for performance. If you initialise a heavy duty object at an inopportune time a noticeable dip in performance can occur. Definitely not ideal! A Boolean flag is included in this implementation, which can be toggled depending on your performance needs.
  • They violate the single responsibility principle as they control their own creation, destruction and access.
  • Each class accessing the singleton calls the class directly, causing tight coupling. A nightmare for a flexible codebase.
  • Singletons can cause headaches when it comes to unit testing. Unit tests are independent and the tight coupling of a singleton can cause problems. Also as singletons maintain their state each run you have to be careful of the order in which you run your unit tests (which should definitely not be the case!).
  • Maintaining global state not only effects tests but can also cause problems when it comes to concurrency. Locks will need to be placed to stop race conditions, slowing down execution and increasing complexity.

Be careful when using a singleton, as they are generally overused and most often not the right solution to the problem. However, with that said, when/if you do find yourself needing to use one, here is a generic singleton that can be easily maintained separate from your normal code base:

 

The Code

Implementation couldn’t be easier (which is definitely one of the reasons singletons are often popular solutions to certain problems).

using UnityEngine;
/// <summary>
/// A base class for any Singleton. Provides global singular access to a MonoBehaviour.
/// </summary>
public abstract class Singleton<T> : MonoBehaviour where T : MonoBehaviour
{
/// <summary>
/// Gets a value indicating whether this instance is destroyed.
/// </summary>
/// <value><c>true</c> if is destroyed; otherwise, <c>false</c>.</value>
public static bool IsDestroyed { get { return _instance == null; } }
private static bool _applicationIsQuitting = false;
/// <summary>
/// Sets whether this instance will be lazily initialised i.e. only initialised when needed.
/// </summary>
private static readonly bool LAZY_INIT = false;
private static T _instance = null;
/// <summary>
/// Gets the instance. The instance is created if not currently part of the scene.
/// </summary>
/// <value>The instance.</value>
public static T instance
{
get
{
// Don't initialise new _instance if application quiting.
if (_applicationIsQuitting)
{
return _instance;
}
if (!_instance)
{
Initialise();
}
return _instance;
}
}
void Awake()
{
if (!LAZY_INIT)
{
Initialise();
}
_applicationIsQuitting = false;
}
/// <summary>
/// Attempt to find object if present in scene. If not found it is created.
/// </summary>
private static void Initialise()
{
_instance = GameObject.FindObjectOfType<T>();
if (!_instance)
{
_instance = new GameObject().AddComponent<T>();
_instance.gameObject.name = _instance.GetType().Name;
}
}
void OnApplicationQuit()
{
_applicationIsQuitting = true;
}
}

 

How do you use it?

You simply inherit from the Singleton like so:

public class ObjectPool : Singleton<ObjectPool>
{
...
}

And now you can access that class from anywhere using:

...
ObjectPool.instance.Pool(this);
...

Related Posts

Leave a Reply

Your email address will not be published. Required fields are marked *