Singelton c учетом подводных камней

от автора

Введение

Существует огромное количество паттернов и антипаттернов программирования. Зачастую использование паттернов диктует нам опыт и собственно знания их самих. В данной статье я хочу обсудить с вами применение паттерна Singelton, а именно его реализацию в Net применительно к Unity.

Singelton

Отмечу, что занимаю написанием кода в команде, поэтому, как можно больше работы увожу внутрь кода, чтобы разгрузить команду и устранить от нее необходимость задумываться о некоторых сложностях реализации тех или иных паттернов в Unity.

Изучая литературу по Net1, применительно к данному вопросу2 и в результате работы над несколькими проектами, родился следующий класс:

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 Singelton<T> : MonoBehaviour where T : Singelton<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 Singelton! 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 Singelton! Check project.");             DestroyImmediate(this);         }     }      protected void OnDestroy() { alive = false; }      protected void OnApplicationQuit() { alive = false; }      protected virtual void Initialization() { } } 

Сосредоточу свое внимание на нескольких аспектах.

Создание объекта

При расширение проекта, а тем более работы в команде > 3 человек зачастую возникает ситуация, когда последовательность создание объектов становится неясна. Строго говоря3, последовательность вызовов Awake() случайна (конечно это не совсем так, и на этом процесс можно влиять, но документация свята), ввиду чего необходимо устранить этот досадный недостаток посредством реализации свойства Instance{get;}. В результате мы получим полноценный доступ к синглтону из Awake() других классов.

В тоже время этот же факт, не позволяет нам использовать явно Lazy, так как объект будет напрямую вызван через Awake() в любом случае.

Основываясь на описании из банды 4-х4, данный класс предполагает свое единоличное существование, ввиду этого объясняется такая логика свойства Instance{get;}.

Инициализация объекта

Общее правило для Unity — делать это в Awake(). Однако, часто забывают вызвать методы родителя, поэтому предусмотрен виртуальный метод Initialization(). Это упрощает проверку при создании объекта и однозначно разделяет необходимые действия для инициализации объекта (KISS).

Внешняя инициализация

Предусмотрена по ряду причин, в том числе для управления через DI и SD. Подробное описание потребует расширенной статьи и расширения представленного выше класса. Данный задел оставлен на будущее.

Уничтожение объекта

Как правило, при использовании обычных синглтонов, и вызова их методов из методов OnDestroy(), OnApplicationQuit() других классов мы получаем ошибку следующего вида5:

Did you spawn new GameObjects from OnDestroy?

Как правило, я считаю код, написанный в таком стиле ошибочным, поэтому прощу его переписать. Но в случае, если так делать необходимо предусмотрен метод IsAlive(), который можно проверять, перед вызовом методов синглтона. Метод не лучший, но на безрыбье…

Заключение

Все чаще я прихожу к мнению, что пользуясь парадигмой Unity возможно реализовывать свои проекты без Singleton. Зачастую применение этого паттерна делает ваш код сильно-связанным и крайне хрупким.

Спасибо.

Источники

— Рихтер Дж "CLR via C#. Программирование на платформе Microsoft.NET# Framework 4.5 на языке 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/ru/current/ScriptReference/MonoBehaviour.Awake.html

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

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

ссылка на оригинал статьи https://habr.com/ru/post/495432/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *