НЕ МОЛЧИ!!!    Сделай что-нибудь, чтобы остановить войну России в Украине.
...бойтесь людей равнодушных - именно с их молчаливого согласия происходят все самые ужасные преступления на свете.   ("Репортаж с петлёй на шее")

Шаблон проектирования Command (Команда)

Рассматривается поведенческий шаблон проектирования 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 или Команда. Мы узнали, что основная цель шаблона убрать жесткие связи между элементами (инициатор команды — команда — исполнитель команды), выполняющими какое-то действие в приложении. В итоге применение шаблона позволяет:

  • унифицировать программные операции;
  • ослабляет связи между элементами приложения;
  • уменьшает избыточность кода;
  • создавать более гибкий, легко модифицируемый код;