Состояние (State) — шаблон проектирования
4 февраля 2020
Шаблон предлагает схему кардинального изменения поведения объекта в зависимости от изменения его внутреннего состояния (значений свойств). Это позволяет сделать архитектуру объекта гибкой, снизить количество условных операторов и сократить объём обработок.
Код реализации паттерна на языке PHP. Скачать исходники (ZIP, 2 Кб)
Шаблон состоит из трёх элементов:
Теперь перейдём непосредственно к коду.
Рассмотрим классы подробнее.
Класс состоит из следующих свойств и методов (большинство методов абстрактные и должны быть определены в дочерних классах):
Код класса CowCharacter (у остальных персонажей код классов практически аналогичен):
Обратите внимание, что методы getSkin(), moveFront(), fly() и fight() делегируют выполнение действий классам состояний. Именно в этом и заключается смысл шаблона, когда поведение игрока (Контекста) зависит от установленного персонажа (Состояния).
Затем превращаем игрока в корову и выполняем тестирование
Потом превращаем игрока в орла
И в завершении превращаем в черепаху
Полный код скрипта test.php
Результат выполнения скрипта test.php
ПРИМЕР ЗАДАЧИ: реализовать превращение игрока в различных живых существ.
Код реализации паттерна на языке PHP. Скачать исходники (ZIP, 2 Кб)
Шаблон состоит из трёх элементов:
- Контекст – класс, поведение объектов которого мы собираемся менять с помощью смены состояния. Контекст содержит свойство-состояние и метод для изменения его значения. В нашей задаче Контекстом является игрок;
- Состояние – класс, определяющий поведение Контекста. Для каждого вида поведения создаётся свой класс-состояние. Иногда в него может передаваться ссылка на Контекст (как правило, это делается, чтобы текущее Состояние могло переводить Контекст в другое Состояние). Роль Состояния у нас будут выполнять персонажи живых существ: корова, орёл и черепаха (далее – игровые персонажи), в которых превращается игрок;
- Интерфейс состояния – интерфейс или абстрактный класс, объявляющие свойства и методы Состояния, которые впоследствии будет использовать Контекст.
Теперь перейдём непосредственно к коду.
character.classes.php
Файл содержит классы игровых персонажей:- AbstractCharacter – абстрактный класс игрового персонажа;
- FarmemCharacter – персонаж «Фермер». Стандартный игровой персонаж;
- CowCharacter – персонаж «Корова»;
- EagleCharacter – персонаж «Орёл»;
- TurtleCharacter – персонаж «Черепаха».
Рассмотрим классы подробнее.
AbstractCharacter
Данный абстрактный класс игрового персонажа служит для упрощения объявления и поддержки единообразия конкретных классов персонажей. По логике шаблона каждый конкретный класс персонажа представляет состояние игрока (Контекста).Класс состоит из следующих свойств и методов (большинство методов абстрактные и должны быть определены в дочерних классах):
- $name – название персонажа;
- getName() – возвращает название персонажа;
- skin() – возвращает скин (графическую оболочку персонажа). Абстрактный метод;
- move() – движение игрока вперёд. Возвращает размер шага или false, если персонаж не может двигаться вперёд. Абстрактный метод;
- fly() – полёт игрока. Возвращает скорость взлёта или false, если персонаж не может летать. Абстрактный метод;
- fight() – удар по противнику. Возвращает силу удара или false, если персонаж не может бить. Абстрактный метод.
/**
* Абстрактный класс "Игоровой персонаж"
*/
abstract class AbstractCharacter
{
/**
* Название персонажа
* @var string
*/
protected $name = '';
/**
* Возвращает название персонажа
* @return string
*/
public function getName()
{
return $this->name;
}
/**
* Возвращает скин (графическую оболочку персонажа)
* @return string
*/
abstract public function skin();
/**
* Движение вперёд
* @return int|bool - возвращает размер шага или 'false', если персонаж не может двигаться вперёд
*/
abstract public function move();
/**
* Полёт
* @return int|bool - возвращает скорость взлёта или 'false', если персонаж не может летать
*/
abstract public function fly();
/**
* Удар по противнику
* @return int|bool - возвращает силу удара или 'false', если персонаж не может бить
*/
abstract public function fight();
}
FarmerCharacter
Класс наследуется от AbstractCharacter и представляет стандартного игрового персонажа – фермера. Новых свойств и методов класс не объявляет, но содержит определения для абстрактных методов skin(), move(), fly() и fight()./**
* Персонаж "Фермер"
*/
class FarmerCharacter extends AbstractCharacter
{
/**
* Название персонажа
* @var string
*/
protected $name = 'Фермер';
/**
* Возвращает скин (графическую оболочку персонажа)
* @return string
*/
public function skin()
{
return 'Скин фермера';
}
/**
* Движение вперёд
* @return int|bool - возвращает размер шага или 'false', если персонаж не может двигаться вперёд
*/
public function move()
{
return 20;
}
/**
* Полёт
* @return int|bool - возвращает скорость взлёта или 'false', если персонаж не может летать
*/
public function fly()
{
return false;
}
/**
* Удар по противнику
* @return int|bool - возвращает силу удара или 'false', если персонаж не может бить
*/
public function fight()
{
return 25;
}
}
CowCharacter, EagleCharacter и TurtleCharacter
Классы тоже наследуется от AbstractCharacter и представляют игровых персонажей – корову, орла и черепаху соответственно. Как и в случае с фермером, новые свойства и методы классы не объявляют, но содержат определения методов skin(), move(), fly() и fight(). Внутри методов каждого персонажа можно прописывать собственную уникальную логику поведения.Код класса CowCharacter (у остальных персонажей код классов практически аналогичен):
/**
* Персонаж "Корова"
*/
class CowCharacter extends AbstractCharacter
{
/**
* Название персонажа
* @var string
*/
protected $name = 'Корова';
/**
* Возвращает скин (графическую оболочку персонажа)
* @return string
*/
public function skin()
{
return 'Скин коровы';
}
/**
* Движение вперёд
* @return int|bool - возвращает размер шага или 'false', если персонаж не может двигаться вперёд
*/
public function move()
{
return 10;
}
/**
* Полёт
* @return int|bool - возвращает скорость взлёта или 'false', если персонаж не может летать
*/
public function fly()
{
return false;
}
/**
* Удар по противнику
* @return int|bool - возвращает силу удара или 'false', если персонаж не может бить
*/
public function fight()
{
return 40;
}
}
player.classes.php
Содержит класс Player, представляющий игрока. Класс состоит из следующих свойств и методов:- $character – игровой персонаж (объект класса AbstractCharacter). В терминологии шаблона данное свойство содержит Состояние;
- Конструктор __construct($character = false) – создаёт объект игрока;
- setCharacter($character) – устанавливает игрового персонажа (изменяет Состояние);
- getCharacterName() – возвращает название персонажа;
- getSkin() – возвращает графическую оболочку игрока;
- moveFront() – выполняет движение игрока вперёд;
- fly() – поднимает игрока вверх и производит полёт;
- fight() – производит удар по противнику.
- test() – тестирование игрока (вывод скина, названия персонажа, движение вперёд и т.п).
Обратите внимание, что методы getSkin(), moveFront(), fly() и fight() делегируют выполнение действий классам состояний. Именно в этом и заключается смысл шаблона, когда поведение игрока (Контекста) зависит от установленного персонажа (Состояния).
<?
//Подключаем классы для работы с игоровыми персонажами
require_once(__DIR__.'/character.classes.php');
/**
* Игрок
*/
class Player
{
/**
* Игровой персонаж
* @var AbstractCharacter
*/
private $character;
/**
* Конструктор. Создаёт объект игрока
* @param AbstractCharacter $character - игровой персонаж. По умлочанию устанавливается персонаж "Фермер"
*/
public function __construct($character = false)
{
//Установка персонажа
if ($character == false) {
$character = new FarmerCharacter();
}
$this->setCharacter($character);
}
/**
* Устанавливает игорового персонажа
* @param AbstractCharacter $character - игровой персонаж
*/
public function setCharacter($character)
{
$this->character = $character;
}
/**
* Возвращает название персонажа
* @return string
*/
public function getCharacterName()
{
return $this->character->getName();
}
/**
* Возвращает графическую оболочку игрока
* @return string
*/
public function getSkin()
{
return $this->character->skin();
}
/**
* Движение игрока вперёд
* @return int|string - возвращает размер шага или строку с сообщением об ошибке
*/
public function moveFront()
{
$moveSize = $this->character->move();
if ($moveSize === false) {
return 'персонаж не умеет ходить';
} else {
return $moveSize;
}
}
/**
* Полёт игрока
* @return int|string - возвращает скорость взлёта/полёта, или строку с сообщением об ошибке
*/
public function fly()
{
$flySize = $this->character->fly();
if ($flySize === false) {
return 'персонаж не умеет летать';
} else {
return $flySize;
}
}
/**
* Удар игрока по противнику
* @return int|string - возвращает силу удара или строку с сообщением об ошибке
*/
public function fight()
{
$fightSize = $this->character->fight();
if ($fightSize === false) {
return 'персонаж не умеет бить';
} else {
return $fightSize;
}
}
/**
* Тестирование персонажа
* @return string - вернёт результат тестирования
*/
public function test()
{
$ar = array();
$ar[] = 'Персонаж: '.htmlspecialchars($this->getCharacterName());
$ar[] = 'Скин: '.$this->getSkin();
$ar[] = 'Движение вперёд: '.$this->moveFront();
$ar[] = 'Полёт: '.$this->fly();
$ar[] = 'Удар: '.$this->fight();
return implode('<br>', $ar).'<br><br>';
}
}
?>
test.php
Содержит тестовый скрипт, в котором создаётся игрок $player (объект класса Player) и выполняется тестирование стандартного персонажа – фермера, установленного для игрока по умолчанию://Создаём игрока и выполняем тестирование действий (выводим название персонажа, его скин, проводим удар, движение и полёт)
$player = new Player();
echo $player->test();
Затем превращаем игрока в корову и выполняем тестирование
//Превращаем игрока в корову, проводим тест
$player->setCharacter(new CowCharacter());
echo $player->test();
Потом превращаем игрока в орла
//Превращаем игрока в орла, проводим тест
$player->setCharacter(new EagleCharacter());
echo $player->test();
И в завершении превращаем в черепаху
//Превращаем игрока в черепаху, проводим тест
$player->setCharacter(new TurtleCharacter());
echo $player->test();
Полный код скрипта test.php
<?
//Подключаем класс для работы с игороком
require_once(__DIR__.'/player.class.php');
//Создаём игрока и выполняем тестирование действий (выводим название персонажа, его скин, проводим удар, движение и полёт)
$player = new Player();
echo $player->test();
//Превращаем игрока в корову, проводим тест
$player->setCharacter(new CowCharacter());
echo $player->test();
//Превращаем игрока в орла, проводим тест
$player->setCharacter(new EagleCharacter());
echo $player->test();
//Превращаем игрока в черепаху, проводим тест
$player->setCharacter(new TurtleCharacter());
echo $player->test();
?>
Результат выполнения скрипта test.php
Персонаж: Фермер
Скин: Скин фермера
Движение вперёд: 20
Полёт: персонаж не умеет летать
Удар: 25
Персонаж: Корова
Скин: Скин коровы
Движение вперёд: 10
Полёт: персонаж не умеет летать
Удар: 40
Персонаж: Орёл
Скин: Скин орла
Движение вперёд: 15
Полёт: 30
Удар: 25
Персонаж: Черепаха
Скин: Скин черепахи
Движение вперёд: 5
Полёт: персонаж не умеет летать
Удар: персонаж не умеет бить