Рассматривается поведенческий шаблон проектирования Command (Команда), его применение, пример реализации.
Содержание
- Применение
- Реализация
- Итоги
Применение
Зачастую в приложениях у пользователя есть возможность осуществить какую-то операцию несколькими путями: нажать на кнопку, кликнуть пункт меню, нажать горячую клавишу. Согласитесь, повторять при этом один и тот же код команды при этом не слишком разумно. Вот в таких случаях на помощь приходит поведенческий шаблон проектирования Command. Поскольку стоит задача исполнить какую-то команду из разных мест не повторяясь в коде, то вероятно, нам надо либо обеспечить независимое исполнение команды либо убрать жесткую связь между источником команды и ее исполнителем. Вот как раз в этом и заключается назначение шаблона Command — он отделяет объект, инициирующий команду, от объекта, который эту команду реализует. Производится это с помощью посредника, который понимает, что нужно сделать и где надо произвести требуемое действие. Как же это реализуется, нагляднее всего рассмотреть на конкретном примере.
Реализация
Чтобы проще было разбираться, надо договориться о терминах. Будем придерживаться общепринятых в шаблоне терминов. В таком случае основными компонентами будут:
- Command — интерфейс или абстрактный класс, определяющий интерфейс команд. В простейшем случае в них декларируется метод execute(), выполняющий команду;
- ConcreteCommand — компонент, владеющий всей необходимой информацией для выполнения какой-то конкретной команды. Реализует интерфейс компонента Command;
- Receiver — получатель команд, компонент выполняющий набор команд;
- Invoker — объект, запускающиий выполнение команды;
- Client — компонент, управляющий взаимодействием перечисленных компонентов для выполнения команд.
Повторюсь, основная цель шаблона Command — разорвать жесткую связь между компонентами, которые осуществляют какую-то операцию в приложении. Для этого надо создать ряд независимых объектов. Другими словами у нас появятся:
- объект Receiver, умеющий выполнять некий набор команд;
- объекты ConcreteCommand, каждый из которых через параметры владеет всей необходимой информацией для выполнения команды: что надо сделать, кто и как должен сделать;
- Invoker — компонент, запускающий команду на выполнение;
- Client — компонент, управляющий созданием и взаимодействием вышеперечисленных составляющих для выполнения нужной команды требуемым исполнителем.
Создание компонента Receiver
В нашем примере создадим простейший программный калькулятор, в терминах шаблона это Receiver. Определимся, какие операции он будет производить. Для простоты примера ограничимся тремя.
//компонент 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($value)
{
$this->curr = $value;
return "Состояние = " .$this->curr;
}
}
Создание компонентов Command
Теперь создадим объекты команд, которые будет выполнять калькулятор. Для этого сначала определимся с интерфейсом наших команд.
В простейшем случае интерфейс команд должен исполнять метод 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);
}
}
И последняя наша команда производит инициализацию объекта класса Calc.
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
Таким образом, у нас есть калькулятор, умеющий выполнять операции. И есть объекты команд, каждый из которых знает, как выполнить свою команду. Но эти компоненты не знают, когда и как им взаимодействовать с исполнителем. Значит должен быть посредник, который заставит взаимодействовать команды и исполнителя. Этим занимается 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();
}
}
Все компоненты, необходимые для реализации шаблона Command созданы. Ниже представлена UML диаграмма иллюстрирующая взаимодействие созданных компонентов шаблона. Если вам нужно освежить знания по UML диаграммам, это можно сделать по этой ссылке.
Создание компонента Client
Что у нас получилось? У нас есть исполнитель, умеющий выполнять определенные команды. Есть компоненты каждой команды, умеющие выполнять свою операцию с помощью исполнителя(в нашем случае Calc), и есть Executor, который получает команду и запускает её на исполнение. Но он знает только о команде, исполнитель ему неизвестен. Осталось только управлять созданными компонентами, чтобы вся эта карусель не только закрутилась, но и выполняла требуемую нам последовательность операций. Эти действия возлагаются на клиента(Client в терминах шаблона), которому нужно выпольнить определённую последовательность команд. Создадим его, логика клиента поясняется комментариями.
//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 или Команда. Вы узнали, что основная цель шаблона убрать жесткие связи между элементами приложения, выполняющими какой-то алгоритм. В итоге, c помощью шаблона мы создали своего рода программный Lego, который с помощью своих независимых компонентов позволяет сделать наше приложение более гибким и универсальным. Если детальнее, то он позволил:
- ослабить связи между элементами приложения;
- уменьшить избыточность кода;
- унифицировать программные операции;
- создавать более гибкий, легко модифицируемый код.
Это далеко неполный перечень достоинств шаблона проектирования Command. Его применение, например, позволяет легко реализовать отмену операций, их повтор, собирать более сложные команды из простых. Он также удобен при сетевом взаимодействии компонентов через удаленные вызовы процедур, позволять упростить логирование работы. Это, конечно, не полный перечень, остальное вам подскажет ваша фантазия и опыт.
Перейти к списку шаблонов проектирования.
