Singleton with pitfalls

Introduction


There are a huge number of patterns and antipatterns of programming. Often the use of patterns dictates to us the experience and the actual knowledge of themselves. In this article I want to discuss with you the application of the Singleton pattern , namely its implementation in Net as applied to Unity.


Singleton


I note that I am writing code in a team, so I take as much work as possible inside the code in order to unload the team and eliminate the need to think about some of the difficulties of implementing certain patterns in Unity.


Studying the literature on Net 1 , in relation to this question 2, and as a result of work on several projects, the following class was born:


using UnityEngine;

/// <summary>
///    .
/// </summary>
/// <typeparam name="T">,    </typeparam>
/// <remarks>
///        OnDestroy  OnApplicationQuit
///      IsAlive.     
/// ,        .
/// 
/// 
///      Awake, OnDestroy, 
/// OnApplicationQuit    
/// base.Awake()  .
/// 
///    Initialization -      
///  .
/// 
///     unity,    
///   .    ,    
///       .
/// 
///  :
///     -  "CLR via C#"
///     - Chris Dickinson "Unity 2017 Game optimization"
///</remarks>

public class Singleton<T> : MonoBehaviour where T : Singleton<T>
{

    private static T instance = null;

    private bool alive = true;

    public static T Instance
    {
        get
        {
            if (instance != null)
            {
                return instance;
            }
            else
            {
                //Find T
                T[] managers = GameObject.FindObjectsOfType<T>();
                if (managers != null)
                {
                    if (managers.Length == 1)
                    {
                        instance = managers[0];
                        DontDestroyOnLoad(instance);
                        return instance;
                    }
                    else
                    {
                        if (managers.Length > 1)
                        {
                            Debug.LogError($"Have more that one {typeof(T).Name} in scene. " +
                                            "But this is Singleton! Check project.");
                            for (int i = 0; i < managers.Length; ++i)
                            {
                                T manager = managers[i];
                                Destroy(manager.gameObject);
                            }
                        }
                    }
                }
                //create 
                GameObject go = new GameObject(typeof(T).Name, typeof(T));
                instance = go.GetComponent<T>();
        instance.Initialization();
                DontDestroyOnLoad(instance.gameObject);
                return instance;
            }
        }

        //Can be initialized externally
        set
        {
            instance = value as T;
        }
    }

    /// <summary>
    /// Check flag if need work from OnDestroy or OnApplicationExit
    /// </summary>
    public static bool IsAlive
    {
        get
        {
            if (instance == null)
                return false;
            return instance.alive;
        }
    }

    protected void Awake()
    {
        if (instance == null)
        {
            DontDestroyOnLoad(gameObject);
            instance = this as T;
            Initialization();
        }
        else
        {
            Debug.LogError($"Have more that one {typeof(T).Name} in scene. " +
                            "But this is Singleton! Check project.");
            DestroyImmediate(this);
        }
    }

    protected void OnDestroy() { alive = false; }

    protected void OnApplicationQuit() { alive = false; }

    protected virtual void Initialization() { }
}

I will focus on several aspects.


Object Creation


When expanding a project, and even more so working as a team> 3 people, a situation often arises when the sequence of creating objects becomes unclear. Strictly speaking 3 , the sequence of calls to Awake () is random (of course, this is not entirely true, and the process can be influenced, but the documentation is holy), which is why it is necessary to eliminate this annoying drawback by implementing the Instance {get;} property . As a result, we get full access to the singleton from Awake () of other classes.


, Lazy, Awake() .

4-4, , Instance{get;}.



Unity — Awake(). , , Initialization(). (KISS).



, DI SD. . .



, , OnDestroy(), OnApplicationQuit() 5:


Did you spawn new GameObjects from OnDestroy?

, , , . , IsAlive(), , . , ...



Increasingly, I come to the conclusion that using the Unity paradigm it is possible to implement my projects without Singleton. Often, applying this pattern makes your code highly-connected and extremely fragile.


Thank.


Sources


- Richter J. "CLR via C #. Programming on Microsoft.NET # Framework 4.5 in C #", 2013


- https://www.codingame.com/playgrounds/1979/different-ways-to-implement-singleton-in--net-and-make-people-hate-you-along-the-way


- https://docs.unity3d.com/en/current/ScriptReference/MonoBehaviour.Awake.html


- https://ru.wikipedia.org/wiki/Design_Patterns


- Dickinson Chris "Unity 2017 Game Optimization, Second Edition", 2017


All Articles