Если вы родились в 1990-х годах, то приведенная выше картинка не нуждается в представлении. Это обязательно заставит вас вернуться к тем ностальгическим воспоминаниям, когда игра Super Mario была единственным, чем можно было заняться на летних каникулах 😆. Удивительный факт игры заключается в том, что исходный размер файла игры составлял всего 32 КБ, что меньше, чем у современного изображения! Мы изучим паттерн государственного проектирования, реализовав небольшую, но важную часть игры Super Mario. Давайте посмотрим, что мы хотим реализовать.
Требования
Мы хотим реализовать поведение игрока в игре Super Mario. Например,
- Дочернее состояние - изначально игрок будет в дочернем состоянии, когда начнется игра. В детском состоянии игрок имеет определенные ограничения, такие как низкая скорость бега, меньшая длина прыжка, не может сломать стену, при встрече с препятствием игрок умрет.
- Взрослое состояние: в этом состоянии игрок будет иметь больше возможностей по сравнению с дочерним состоянием. Он сможет быстрее бегать, прыгать на большую длину, ломать стену. Кроме того, состояние игрока будет изменено на дочернее, если он коснется врага.
- Государство армии - это самое мощное состояние в игре. Помимо основной силы игрока, он может стрелять по врагу на земле с большого расстояния. Однако, если игрок встречает врага, он возвращается во взрослое состояние.
Как мы можем спроектировать и реализовать вышеупомянутое требование, чтобы наш код можно было сопровождать и легко адаптировать к новым требованиям в будущем?
Давайте наивно подумаем. Класс Player может быть реализован следующим образом. Мы можем представить наши состояния как постоянные целые числа. Когда клиент создает экземпляр объекта игрока, он создает экземпляр с дочерним состоянием. После этого, по ходу игры, мы можем вызывать такие методы, как jump (), Shoot (), run () и т. Д. В зависимости от текущего состояния игрока, он будет обновлять выполнение операций. Все идет нормально!
Однако с этим подходом есть проблемы! Вы можете догадаться? Вот некоторые из них,
- Принцип открытости-закрытости ❌ Принцип открытости-закрытости явно нарушен в приведенной выше схеме. Если мы хотим добавить новое поведение или ввести новое состояние игрока, тогда мы будем вынуждены изменить каждый из методов, определенных в классе player. Будет сложно реализовать не только новое состояние, но и все наши модульные тесты нужно будет изменить.
- Дальнейшая модификация может внести ошибки в существующий рабочий функционал игры.
- Взгляните на методы jump (), run () и expectedEnemy (), upgradeState (). Мы полагаемся на кучу if..else .. блоков, что плохо. В настоящее время вариант использования прост, но в реальном мире переход между состояниями может быть намного сложнее.
Теперь давайте посмотрим, как шаблон состояний может решить эту проблему!
У нас есть класс игрока, который является главным героем нашей игры. У него есть другие обязанности, помимо его текущего состояния. Кроме того, мы можем наблюдать, что состояние игрока меняется на протяжении всей игры. В зависимости от состояния поведение игрока будет определяться игровым движком во время выполнения. Так почему бы не отделить эти изменения от того, что осталось прежним!
Шаг 1. Определите интерфейс состояний
Он содержит методы для каждого действия игрока. Все наши конкретные государства будут реализовывать этот интерфейс. Одним из преимуществ наличия интерфейса является то, что теперь каждое состояние должно подчиняться контракту интерфейса - реализовывать все методы, объявленные в интерфейсе! Это обязательно вызовет ошибку времени компиляции, если программист забудет определить один из методов в конкретной реализации, что хорошо 😃.
Шаг 2. Определите конкретные состояния
Давайте определим каждое отдельное состояние, в которое игрок может перейти. В нашем случае у нас будет дочернее состояние, взрослое состояние и состояние армии. Эти состояния будут иметь ссылку на объект игрока. Используя его, мы можем изменить состояние игрока. Здесь можно заметить простоту и удобочитаемость кода по сравнению с наивным решением, которое мы реализовали выше.
Шаг 3. Делегируйте ответственность классу государства
Это довольно просто. Нам просто нужно определить методы setState () и updatePosition () в классе player. Он также будет содержать ссылку на объект состояния.
Используя эту ссылку, игровой движок может изменять состояние игрока на протяжении всей игры. Когда клиент вызывает метод ShouldEnemy (), он делегируется текущему состоянию. Например, если текущее состояние - AdultState, тогда во время выполнения будет выполняться метод встретившейся среды (), определенный в AdultState. И он изменяет текущее состояние на ChildState, чего мы и хотим!
Резюме того, что мы сделали! 😃
- Удалены повторяющиеся условия if-else из каждого метода. Используя паттерн состояний, мы локализовали поведение методов для соответствующих конкретных классов. Это имеет больше смысла, поскольку такое поведение принадлежит определенному состоянию игрока. Поэтому вместо того, чтобы помещать все эти поведения в класс игрока, мы модулировали наш код и поместили его в соответствующие состояния.
- Государственный паттерн вынудил нас следовать принципу «открыто-закрыто» ✅. Мне не нужно вдаваться в подробности! Добавить новое состояние в приложение довольно просто. Просто создайте класс, который реализует интерфейс состояния и реализует поведение нового состояния! В то же время нам не нужно касаться существующих состояний при реализации нового состояния.
- Код легко читается, прост в обслуживании и намного более логичен.
Что такое модель состояния?
Шаблон состояния позволяет объекту изменять свое поведение при изменении его внутреннего состояния. Объект изменит свой класс.
Хорошо, но подождите, разве вы не думаете, что мы используем те же принципы шаблона стратегии? Давайте посмотрим на различия между ними.
Паттерн состояния против паттерна стратегии
Основное различие заключается в цели использования обоих шаблонов.
Мы используем шаблон состояния как альтернативу помещению большого количества if..else..в наш контекст; инкапсулируя поведение в объектах состояния, мы можем просто изменить объект состояния в контексте, чтобы изменить его поведение.
Шаблон стратегии - это гибкая альтернатива подклассу. Если мы используем наследование для определения поведения класса, мы застреваем в этом поведении, даже если оно нам не нужно. Мы не должны предоставлять методы или поведение классам, которым это не нужно. С помощью шаблона стратегии мы можем изменить поведение, составив другой объект.
Уф! Вот и все! Мы изучили паттерн государственного проектирования! Думаю, это был один из классных паттернов. Надеюсь, ты чувствовал то же самое 😃
Увидимся в следующем шаблоне! А пока до свидания.