Singleton mit Fallstricken

Einführung


Es gibt eine Vielzahl von Mustern und Antipattern der Programmierung. Oft diktiert uns die Verwendung von Mustern die Erfahrung und das tatsächliche Wissen über sich selbst. In diesem Artikel möchte ich mit Ihnen die Anwendung des Singleton- Musters diskutieren , nämlich seine Implementierung in Net, wie sie auf Unity angewendet wird.


Singleton


Ich stelle fest, dass ich Code in einem Team schreibe, daher nehme ich so viel Arbeit wie möglich in den Code, um das Team zu entladen und die Notwendigkeit zu beseitigen, über einige der Schwierigkeiten bei der Implementierung bestimmter Muster in Unity nachzudenken.


Durch das Studium der Literatur zu Net 1 in Bezug auf diese Frage 2 und als Ergebnis der Arbeit an mehreren Projekten wurde die folgende Klasse geboren:


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

Ich werde mich auf verschiedene Aspekte konzentrieren.


Objekterstellung


Wenn Sie ein Projekt erweitern und vor allem als Team> 3 Personen arbeiten, tritt häufig eine Situation auf, in der die Reihenfolge der Objekterstellung unklar wird. Genau genommen 3 ist die Reihenfolge der Aufrufe von Awake () zufällig (dies ist natürlich nicht ganz richtig und der Prozess kann beeinflusst werden, aber die Dokumentation ist heilig), weshalb es notwendig ist, diesen lästigen Nachteil durch Implementierung der Instance {get;} -Eigenschaft zu beseitigen . Infolgedessen erhalten wir von Awake () anderer Klassen vollen Zugriff auf den Singleton .


, Lazy, Awake() .

4-4, , Instance{get;}.



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



, DI SD. . .



, , OnDestroy(), OnApplicationQuit() 5:


Did you spawn new GameObjects from OnDestroy?

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



Zunehmend komme ich zu dem Schluss, dass es mit dem Unity-Paradigma möglich ist, meine Projekte ohne Singleton umzusetzen. Wenn Sie dieses Muster anwenden, ist Ihr Code häufig stark verbunden und äußerst zerbrechlich.


Danke.


Quellen


- Richter J. "CLR über C #. Programmierung unter 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, Zweite Ausgabe", 2017


All Articles