🔊События
Автор: Дмитрий Прокопьев
У вас когда-нибудь случалось такое, что один компонент влез в зону ответственности другого? Ситуация, когда на такой компонент сваливалась вся работа и из за этого он становился перегруженным? С этим рано или поздно сталкиваются все, но не все знают, как решить эту проблему.
Пример проблемы
Представим, что мы работаем над игрой, в которой персонаж может зарабатывать игровую валюту и тратить ее в магазине. Для этого мы сделали ему простой кошелек:
public class Wallet : MonoBehaviour
{
public int Money { get; private set; }
public void AddMoney(int amount)
{
if (amount <= 0)
return;
Money += amount;
}
public bool TrySpendMoney(int amount)
{
if (amount <= 0)
return;
bool isEnough = Money >= amount;
if (isEnough)
Money -= amount;
return isEnough;
}
}
У этого кошелька одна простая ответственность: проводить транзакции и обеспечивать их корректность. Но теперь нам прислали новую задачу: отображать количество средств на экране. Не проблема, мы уже реализовали класс WalletView
, который умеет отображать количество средств. Осталось лишь вызвать у него метод:
[SerializeField] private WalletView _walletView;
//...
_walletView.Display(Money);
Нам опять пришла новая задача! Теперь каждый раз, когда происходит транзакция, нужно сделать сохранение в файл, чтобы запомнить сколько денег теперь есть у игрока. Не проблема, мы уже реализовали класс FileSaver
, который умеет сохранять количество денег. Нужно лишь вызвать у него метод:
[SerializeField] private FileSaver _fileSaver;
//...
_fileSaver.SaveMoney(Money);
Удивительно, опять новая задача! Теперь магазин должен предлагать игроку новые товары, когда тот произвел транзакцию. Не проблема, обратимся к классу Shop
:
[SerializeField] private Shop _shop;
//...
_shop.OfferItems(Money);
Видите, куда это идет? Вот так теперь выглядит метод AddMoney
у кошелька:
public void AddMoney(int amount)
{
if (amount <= 0)
return;
Money += amount;
_walletView.Display(Money);
_fileSaver.SaveMoney(Money);
_shop.OfferItems(Money);
}
Бред, так ведь? Теперь кошелек делает совершенно не свою работу. Как нам в будущем догадаться, что именно кошелек делает все это? Как понять, почему магазин внезапно перестал работать, когда мы всего лишь убрали кошелек у игрока? Как понять, почему кошелек отказывается работать, когда мы всего лишь выключили интерфейс? Много вопросов - мало ответов.
Суть проблемы
Где в этой ситуации корень зла? Когда мы допустили роковую ошибку? Мы сделали это тогда, когда повесили на кошелек задачи других компонентов. Но как этого избежать, если только кошелек знает, что происходит с деньгами?
Например, кошелек может уведомить всех, что происходит, но не указывать компонентам, что именно делать. Тогда ответственность за реакцию компонентов переляжет с кошелька на них самих. Мысленно представим такой диалог:
- Wallet (кричит через громкоговоритель): Количество денег изменилось! Теперь у нас 50 монет. Сами решайте что делать с этим, на этом мои полномочия все.
- WalletView: Мне это важно. Выведу на экран число 50
- FileSaver: Мне это важно. Запишу в файл число 50
- PlayerMover: Меня это не беспокоит, направление движения игрока не зависит от финансового положения. Я ничего не слышал
- Shop: Мне это важно. Обновлю ассортимент товаров: добавлю те, которые можно купить за 50 монет
- Coin: Меня не интересует, сколько денег у игрока. Я ничего не слышал
Заметьте, как кошелек больше не отвечает за остальных! Он сообщил информацию и на этом успокоился. Так мы и можем решить эту проблему.
И какое отношение к этому имеют события?
Событие (англ. Event) - это механизм, позволяющий организовать общение между компонентами описанным выше образом. Название "Событие" появилось, потому что сообщение, которое передает компонент, говорит, что "что-то случилось" или "произошло какое-то событие". То есть событием называется само "сообщение".
Тот компонент, который сообщает информацию через событие, называется владельцем события. Помните, как некоторые компоненты услышали сообщение, а некоторые нет? Одни называются "слушателями" (англ. Listeners) или "подписчиками" события, потому что они получают из него информацию. А другие никак не называются, не хотят - не слушают.
Чтобы стать слушателем события, нужно на него подписаться (англ. Subscribe), а чтобы перестать быть слушателем нужно отписаться (англ. Unsubscribe).
Что с этим делать дальше?
Теперь нам известно, зачем нужны события, и мы знаем основные термины. Осталось только почитать про типы событий и их синтаксис: UnityAction
и UnityEvent
Last updated
Was this helpful?