Вступление
В этой статье речь пойдет об одном виде организации взаимодействия между скриптами-менеджерами (синглтонами именуемыми), а конкретно — использование отдельного класса-агрегатора, в котором содержаться ссылки на все instance менеджеров. Идея создать класс-агрегатор пришла мне в голову после прочтения этой статьи.
Задачи
Я пришел к выводу, что забивать своими собственными ручками менеджеры в класс-агрегатор каждый раз — это дико неудобно. При создании нового менеджера придется открывать класс-агрегатор и вносить изменения. При достаточном количестве опыта можно такие нудные процессы автоматизировать. Таким образом поставились задачи: автоматическое создание синглтонов и их автоматический сборщик.
Класс-Singleton
using UnityEngine; public class Singleton<T>: MonoBehaviour where T: MonoBehaviour { public static T instance { get; private set; } public void Awake() { if (instance == null) { instance = GetComponent<T>(); ManagersAregator.addManager(instance); } else { Debug.Log($"<color=red>Удаление дубликата синглтона {typeof(T).ToString()}</color>"); Destroy(gameObject); } } public void OnDestroy() { ManagersAregator.removeManager<T>(); } }
В классической реализации синглтона в Unity используется статическая переменная instance. В методе Awake() производится проверка, определен ли instance. Если нет, то получаем ссылку на экземпляр класса с помощью ключевого слова this. Но т.к. в конкретной реализации используется «шаблонная» переменная класса Т, использовать this не получилось. Но мы с легкостью можем получить компонент Т с объекта, на котором висит скрипт. Если же instance определен, то его необходимо уничтожить. Таким образом объект всегда будет на сцене в единственном экземпляре.
В методе Awake() в класс-агрегатор добавляется новый элемент, а в методе OnDestroy() элемент удаляется из класса-агрегатора.
Создание синглтона класса MyClass происходит так:
public class MyClass: Singleton<MyClass>
Если надо использовать метод Awake() в классе MyClass, то надо воспользоваться ключевым словом base (подробнее о base и наследовании):
new void Awake() { base.Awake(); //Вызывается метод Awake() класса-родителя //необходимый код }
Класс-агрегатор
using System.Collections.Generic; using UnityEngine; public static class ManagersAregator { static Dictionary<string, MonoBehaviour> Managers = new Dictionary<string, MonoBehaviour>(); public static void addManager<T>(T newManager) { string keyWord = typeof(T).ToString(); if(Managers.ContainsKey(keyWord)) { Debug.Log($"[ManagersAregator] Менеджер -{newManager}- с ключом -{keyWord}- уже существует"); } else { Managers.Add(keyWord, newManager as MonoBehaviour); Debug.Log($"<color=green>[ManagersAregator] Добавлен новый менеджер -{newManager}- с ключом -{keyWord}-</color>"); } } public static T getManager<T>(string callback) where T: Singleton<T> { string keyWord = typeof(T).ToString(); if(Managers.ContainsKey(keyWord)) { Debug.Log($"<color=yellow>[{callback}] Получение менеджера -{keyWord}-</color>"); MonoBehaviour mbTemp = null; T manager = null; if(Managers.TryGetValue(keyWord, out mbTemp)) { manager = (T)mbTemp; Debug.Log($"<color=green>[{callback}] Менеджер -{manager}- получен</color>"); } else { Debug.Log($"<color=red>[{callback}] Ошибка получения менеджера -{keyWord}-</color>"); } return manager; } Debug.Log($"<color=red>[ManagersAregator] Менеджер с ключом -{keyWord}- отсутствует в словаре.</color>"); return null; } public static void removeManager<T>() { string keyWord = typeof(T).ToString(); if(Managers.ContainsKey(keyWord)) { Managers.Remove(keyWord); Debug.Log($"[ManagersAregator] Менеджер с ключом -{keyWord}- удален из словаря"); } else { Debug.Log($"[ManagersAregator] Менеджер с ключом -{keyWord}- отсутствует в словаре."); } } }
Класс является статическим, т.е. у него не может быть экземпляра на сцене. Таким образом он будет доступен из любой сцены в любое время.
Все скрипты-менеджеры (синглтоны) хранятся в словаре Managers. Ключом для каждого менеджера является имя класса этого менеджера. Возможно, новички в программировании зададут вопрос: «Ба, а что это словарь хранит MonoBehaviour, а все классы наследуются от Singleton?». Это хороший вопрос, ответ на который и является ключом к реализации автоматического агрегатора менеджеров любых классов.
В программировании существует понятие upcasting — преобразование типа к базовому классу. Благодаря тому, что все классы в Unity наследуются от MonoBehaviour, их можно апкастить к MonoBehabiour. Поэтому словарь Managers содержит только объекты класса MonoBehaviour.
Рассмотрим методы класса-агрегатора:
void addManager<T>(T newManager)
Этот метод вызывается в методе Awake() класса Singleton. Аргументом является статическая переменная класса instance. Далее создается ключ по имени класса, которому принадлежит instance, и менеджер добавляется в словарь.
T getManager<T>(string callback) where T: Singleton<T>
Функция принимает аргументом строку с именем класса, откуда вызывается метод. Это сделано исключительно для удобного дебага (в консоли отображается класс, откуда вызывается метод). Пример использования этого метода в класса AnotherMyClass:
public class AnotherMyClass: MonoBehaviour void Start() { string cb = GetType().ToString(); //Получение имени класса в качестве строки MyClass MC = ManagersAregator.getManager<MyClass >(cb); }
В консоли будет висеть сообщение: "Ху**я, переделывай [AnotherMyClass] Менеджер -MyClass- получен".
void removeManager<T>()
Удаляет из словаря менеджер типа Т, если он содержится в словаре.
Итоги
Фактически, из трех функций класса-агрегатора разработчику достаточно использовать только метод getManager. Особым плюсом является хорошая видимость сообщений дебага на все случаи жизни. По желанию, конечно же, их можно отключить. Я же считаю, что будет очень удобно увидеть в какой момент времени, какой класс пытается что-то получить и что он пытается получить.
Надеюсь, эта статья была для вас полезной и вы узнали что-то полезное для себя!
ссылка на оригинал статьи https://habr.com/ru/post/494930/
Добавить комментарий