Рассматривается поведенческий шаблон проектирования Command (Команда), его применение, пример реализации.
Содержание
- Применение
- Реализация
- Итоги
Применение
Зачастую в приложениях у пользователя есть возможность осуществить какую-то операцию несколькими путями: нажать на кнопку, кликнуть пункт меню, нажать горячую клавишу. Согласитесь, повторять один и тот же код команды при этом не слишком разумно. Вот в таких случаях на помощь приходит поведенческий шаблон проектирования Command. Поскольку стоит задача исполнить какую-то команду из разных мест не повторяясь в коде, то вероятно, нам надо убрать жесткую связь между источником команды и ее исполнителем. Вот как раз в этом и заключается назначение шаблона Command. Как же можно это сделать? Очевидно, что сама команда должна понимать, что нужно сделать и где надо произвести требуемое действие. Как это реализуется, нагляднее всего рассмотреть на конкретном примере.
Реализация
Преамбула
Чтобы проще было разбираться, надо договориться о терминологии. Будем придерживаться общепринятых в шаблоне терминов. В таком случае основными компонентами будут:
- Command — интерфейс или абстрактный класс, определяющий интерфейс команд. В простейшем случае в нем декларируется метод execute(), выполняющий команду;
- ConcreteCommand — компонент, владеющий всей необходимой информацией для выполнения одной конкретной команды. Реализует метод execute(), объявленный в интерфейсе Command;
- Receiver — получатель, умеет выполнять набор команд;
- Invoker — объект, запускающий выполнение команд;
- Client — компонент, управляющий взаимодействием перечисленных компонентов для выполнения команд.
Повторюсь, основная цель шаблона Command — отделить запрос на совершение какого то действия(команды) от объектов, выполняющих команды. Другими словами у нас появятся:
- объекты ConcreteCommand, каждый из которых через параметры владеет всей необходимой информацией для выполнения команды: что надо сделать и кто должен сделать;
- объект Receiver, умеющий выполнять некий набор команд. Другими словами, исполнитель команд;
- Invoker — компонент, умеющий запускать команды на выполнение;
- Client — компонент, управляющий созданием и взаимодействием вышеперечисленных составляющих для выполнения нужной команды требуемым исполнителем.
Реализация примера
В нашем примере создадим простейший калькулятор. Определимся, какие операции он будет производить. Для простоты примера ограничимся тремя.
//компонент Receiver шаблона Command - получатель и исполнитель команд
class Calc {
private $curr = 0; //текущее состояние калькулятора
public function add($operand)
{
$this->curr += $operand;
return "Результат операции = " .$this->curr;
}
public function subst($operand)
{
$this->curr -= $operand;
return "Результат операции = " .$this->curr;
}
public function init($operand)
{
$this->curr = $operand;
return "Результат = " .$this->curr;
}
}
Теперь опишем интерфейс наших команд. Нам будет достаточно метода execute() для реализации выполнения команд.
interface Command {
public function execute();
}
Далее опишем классы конкретных команд. Все они должны реализовать объявленный интерфейс. Объект команды должен знать получателя операции, который передается в его конструктор. Также в нашем случае команда получает один из операндов производимой операции.
//компонент concreteCommand шаблона Command
class addCommand implements Command {
public $calculator;
public $operand;
public function __construct($calculator, $operand)
{
$this->calculator = $calculator;
$this->operand = $operand;
}
//реализация представленной команды
public function execute()
{
$this->calculator->add($this->operand);
}
}
Аналогично реализуем другие две команды.
class substCommand implements Command {
public $calculator;
public $operand;
public function __construct($calculator, $operand)
{
$this->calculator = $calculator;
$this->operand = $operand;
}
public function execute()
{
$this->calculator->subst($this->operand);
}
}
class initCommand implements Command {
public $calculator;
public $operand;
public function __construct($calculator, $operand)
{
$this->calculator = $calculator;
$this->operand = $operand;
}
public function execute()
{
$this->calculator->init($this->operand);
}
}
Таким образом, у нас есть калькулятор, умеющий выполнять три операции. И есть объекты этих операций, получающие ссылку на исполнителя своих команд.
Теперь кто-то должен инициировать исполнение операций калькулятора. Этим занимается Invoker. Его роль будет исполнять класс Executor. Для того, чтобы запустить команду, он должен получить ссылку на нее в конструкторе или в set методе.
//компонент Invoker шаблона Command
class Executor {
public $command;
public function __construct($command)
{
$this->command = $command;
}
public function setCommand($command)
{
$this->command = $command;
}
public function run()
{
$this->command->execute();
}
}
Что у нас получилось? У нас есть компонент, умеющий выполнять определенные команды. Есть компоненты этих конкретных команд, которые знают исполнителя команды, и знают, какой метод этого исполнителя вызвать для запуска своей команды. Также есть инициатор команд Invoker, который получает ссылку на конкретную команду, еще он умеет запускать исполнение команды. Этот инициатор знает только о командах, исполнитель ему неизвестен. Наша задача теперь связать все созданные компоненты, чтобы вся эта карусель закрутилась. Эти действия возлагаются на клиента. Логика его работы будет поясняться комментариями.
//Client
class Client {
$calc = new Calc(); //создаем нужного нам исполнителя команд
/*создаем объекты команд, которые может выполнить исполнитель*/
$init = new initCommand($calc, 0);
$adding = new addCommand($calc, 5);
$subst = new substCommand($calc, 2);
/*создаем инициатора команд и заставляем его работать */
$exec = new Executor($init);
$exec->run(); //запускаем команду инициализации калькулятора
$exec->setCommand($adding); //устанавливаем команду сложения
$exec->run(); //выполняем переданную команду
$exec->setCommand($subst); //устанавливаем команду вычитания
$exec->run(); //выполняем переданную команду
}
Итоги
Итак, был рассмотрен поведенческий шаблон проектирования Command или Команда. Мы узнали, что основная цель шаблона убрать жесткие связи между элементами (инициатор команды — команда — исполнитель команды), выполняющими какое-то действие в приложении. В итоге применение шаблона позволяет:
- унифицировать программные операции;
- ослабляет связи между элементами приложения;
- уменьшает избыточность кода;
- создавать более гибкий, легко модифицируемый код;
Перейти к списку шаблонов проектирования.