Вступление
В этой статье речь пойдет об одном виде организации взаимодействия между скриптами-менеджерами (синглтонами именуемыми), а конкретно — использование отдельного класса-агрегатора, в котором содержаться ссылки на все 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);
}
В консоли будет висеть сообщение: "
void removeManager<T>()
Удаляет из словаря менеджер типа Т, если он содержится в словаре.
Итоги
Фактически, из трех функций класса-агрегатора разработчику достаточно использовать только метод getManager. Особым плюсом является хорошая видимость сообщений дебага на все случаи жизни. По желанию, конечно же, их можно отключить. Я же считаю, что будет очень удобно увидеть в какой момент времени, какой класс пытается что-то получить и что он пытается получить.
Надеюсь, эта статья была для вас полезной и вы узнали что-то полезное для себя!