Паттерн Visitor
Паттерн Visitor описывает операцию над различными объектами, вынося эту операцию за пределы ответственности самих объектов. Классический пример: иерархия типов объектов, для каждого из которых нужно выполнить разные действия в зависимости от типа. Вместо большого switch, можно использовать паттерн Visitor.
Например, система обработки попаданий в игре возвращает абстракцию Hit, скрывающую конкретный тип попадания (по цели или по окружению). Иерархия типов Hit определяет различные действия после выстрела.
Для реализации Visitor, тип Hit модифицируется, добавляя интерфейс для выполнения операции:
// Интерфейс визитера
public interface IHitVisitor
{
void Visit(HitByTarget hit);
void Visit(HitByEnvironment hit);
}
Класс, реализующий этот интерфейс, может, например, по-разному засчитывать попадания: попадание по окружению — как промах, по цели — как чистое попадание.
// Реализация визитера для подсчета попаданий
public class HitCounter : IHitVisitor
{
public void Visit(HitByTarget hit) { /* Логика подсчета попадания по цели */ }
public void Visit(HitByEnvironment hit) { /* Логика подсчета промаха */ }
}
Обработка происходит так: после получения Hit, вызывается метод визитера, соответствующий типу попадания. Перегрузка метода позволяет выбрать правильный метод без дополнительной проверки типа.
Преимущества использования паттерна Visitor:
- При расширении иерархии типов Hit, достаточно добавить новый метод в интерфейс IHitVisitor. Компилятор укажет на места, где необходимо реализовать этот метод.
- Отделение ответственности. Например, обработка попаданий на сервере и клиенте может быть разделена, избегая зависимости кода сервера от Unity. Логика обработки хранится в коде клиента и сервера независимо, без взаимных связей.
Паттерн Observer
Классическая модель взаимодействия компонентов — это pull-модель: компонент запрашивает данные и работает с ними. Например, компонент, отображающий здоровье игрока, каждый кадр запрашивает значение здоровья.
Push-модель, реализуемая паттерном Observer, эффективнее: игрок сообщает об изменении здоровья, и компонент обновляет интерфейс. Это позволяет избежать жестких связей между компонентами.
В C# реализация Observer выполняется с помощью событий:
public class Player
{
public event Action<int> HealthChanged;
public void TakeDamage(int damage)
{
// ...
HealthChanged?.Invoke(health);
}
}
Компонент, подписывающийся на событие HealthChanged, получает уведомление об изменении здоровья. Важно отписываться от события при деактивации компонента с помощью метода -=. Обратите внимание на важность отписки от событий Unity.
Паттерн Decorator
Decorator позволяет изменять поведение объекта, не изменяя его интерфейс. Это композиционное решение.
Например, есть объект, описывающий характеристики игрока. Пассивное заклинание должно изменить эти характеристики. Decorator позволяет «задекорировать» объект характеристиками, модифицируя их поведение.
Decorator имеет тот же интерфейс, что и декорируемый объект, оборачивая его и изменяя значения. В любой момент можно заменить декоратор на оригинальный объект или другой декоратор.
В примере пассивный эффект удваивает все характеристики игрока.
Рассмотренные паттерны проектирования — Visitor, Observer и Decorator — являются мощными инструментами для разработки хорошо структурированного и расширяемого кода в Unity. Их применение упрощает разработку, улучшает читаемость и поддерживаемость кода.