Singleton con trampas

Introducción


Hay una gran cantidad de patrones y antipatrones de programación. A menudo, el uso de patrones nos dicta la experiencia y el conocimiento real de sí mismos. En este artículo, quiero discutir con ustedes la aplicación del patrón Singleton , es decir, su implementación en la Red aplicada a Unity.


único


Noto que estoy escribiendo código en un equipo, por lo que tomo la mayor cantidad de trabajo posible dentro del código para descargar el equipo y eliminar la necesidad de pensar en algunas de las dificultades de implementar ciertos patrones en Unity.


Al estudiar la literatura sobre la Red 1 , en relación con esta pregunta 2, y como resultado del trabajo en varios proyectos, nació la siguiente clase:


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() { }
}

Me enfocaré en varios aspectos.


Creación de objetos


Cuando se expande un proyecto, y aún más trabajando en equipo> 3 personas, a menudo surge una situación en la que la secuencia de creación de objetos se vuelve poco clara. Hablando estrictamente 3 , la secuencia de llamadas a Awake () es aleatoria (por supuesto, esto no es del todo cierto, y el proceso puede verse influenciado, pero la documentación es sagrada), por lo que es necesario eliminar este inconveniente molesto mediante la implementación de la propiedad Instance {get;} . Como resultado, tenemos acceso completo al singleton de Awake () de otras clases.


, Lazy, Awake() .

4-4, , Instance{get;}.



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



, DI SD. . .



, , OnDestroy(), OnApplicationQuit() 5:


Did you spawn new GameObjects from OnDestroy?

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



Cada vez más, llego a la conclusión de que usando el paradigma de Unity es posible implementar mis proyectos sin Singleton. A menudo, la aplicación de este patrón hace que su código esté altamente conectado y extremadamente frágil.


Gracias.


Fuentes


- Richter J. "CLR a través de C #. Programación en Microsoft.NET # Framework 4.5 en 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