Паттерн Command (Команда) это поведенческий паттерн проектирования, который превращает запрос на действие в отдельный объект с единственным методом execute(). Тот, кто запускает действие (invoker), знает только этот метод и не зависит от того, кто и как действие выполняет (receiver), поэтому команды можно передавать как параметры, хранить, ставить в очередь и отменять. Ниже разбор на классическом примере с лампой и выключателем: четыре роли паттерна, код каждого класса и вывод программы.

Кратко

  • Идея: вызов метода заворачивается в объект-команду. Вызывающий хранит команду и дергает execute(), не зная, что внутри.
  • Четыре роли: команда (command), приемник команд (receiver), вызывающий команды (invoker) и клиент (client), который все связывает.
  • В примере: Light это receiver, TurnOnLightCommand и TurnOffLightCommand это команды, Switch это invoker, а TestCommand с методом main это client.
  • Команда получает приемник в конструкторе и делает ровно одно действие; новое действие добавляют новым классом-командой, не трогая Switch.
  • Зачем: отделить того, кто просит действие, от того, кто его выполняет, чтобы реализацию можно было менять без ущерба для остальной системы.
  • На практике так делают отмену действий, очереди задач и кнопки в интерфейсах; с Java 8 простую команду можно передать ссылкой на метод: new Switch(l::turnOn, l::turnOff).

Привет всем, Форумчане!

Разобрался-таки основательно (как мне кажется) с паттерном Command и хочу попытаться рассказать о нем "своими словами". Корешки книг на полке: заглавная картинка статьи о паттерне CommandИсходя из Википедии, можем узнать, что цель его это создание структуры, в которой класс-отправитель и класс-получатель не зависят друг от друга напрямую. Организация обратного вызова к классу, который включает в себя класс-отправитель. В принципе написано четко и правильно, но это в теории. А как же сделать это? Вот тут начинаются проблемы, т.к. описание уже не так ясно и очевидно. Поэтому я как разобрался, решил рассказать вам как я это понял, может кому-то и пригодится: Исходя из описания цели этого паттерна, буду комбинировать описание сразу с кодом, чтоб было нагляднее, т.к. в той же википедии обобщили для многих языков и поэтому описание отделено от примера. В этом паттерне есть четыре термина, пока примем их как данность: команды (command), приемник команд (receiver), вызывающий команды (invoker) и клиент (client). Пример буду брать с той же Википедии, он вполне сносный.

Приемник команд (receiver): класс Light

Задача есть класс Light, который умеет две вещи: включить свет и выключить. Он в терминах паттерна будет "приемник команд (receiver)"
/*Receiver class*/

public class Light{
     public Light(){  }

     public void turnOn(){
        System.out.println("The light is on");
     }

     public void turnOff(){
        System.out.println("The light is off");
     }
}

Команда (command): интерфейс Command и его реализации

Создадим интерфейс с одним методом execute(), который будет выполнять и который называется в терминах паттерна "команда (command)"
/*the Command interface*/

public interface Command{
    void execute();
}
Необходимо инкапсулировать выполнение умений класса Light. Для этого мы создадим классы TurnOnLightCommand и TurnOffLightCommand, которые реализуют интерфейс Command и которые будут принимать в конструкторе объект класса Light. И каждый из них будет выполнять только одно действие. Один будет вызывать метод turnOn(), а другой turnOff().
/*the Command for turning on the light*/

public class TurnOnLightCommand implements Command{
   private Light theLight;

   public TurnOnLightCommand(Light light){
        this.theLight=light;
       }

   public void execute(){
      theLight.turnOn();
   }
}

/*the Command for turning off the light*/

public class TurnOffLightCommand implements Command{
   private Light theLight;

   public TurnOffLightCommand(Light light){
        this.theLight=light;
       }

   public void execute(){
      theLight.turnOff();
   }
}

Вызывающий команды (invoker): класс Switch

Теперь пришло время создать объект, который бы принимал эти инкапсулированные методы объекта Light. Он в терминах паттерна называется вызывающий команды (invoker). Назовем его Switch и пусть он будет принимать в конструкторе переменные Command, которые будут использоваться в созданных методах flipUp() и flipDown().
/*the Invoker class*/

public class Switch {
    private Command flipUpCommand;
    private Command flipDownCommand;

    public Switch(Command flipUpCommand,Command flipDownCommand){
         this.flipUpCommand=flipUpCommand;
         this.flipDownCommand=flipDownCommand;
    }

    public void flipUp(){
         flipUpCommand.execute();
    }

    public void flipDown(){
         flipDownCommand.execute();
    }
}

Клиент (client): класс TestCommand

Ну и конечно создадим класс, который будет использовать их, чтобы понять, что происходит вообще. Он будет иметь метод main, в котором и будет происходить все действие:
/*The test class*/
public class TestCommand{
   public static void main(String[] args){
       // создаем объект, который будет использоваться
       Light l=new Light();
       // создаем объекты для всех умений объекта Light:
       Command switchUp=new TurnOnLightCommand(l);
       Command switchDown=new TurnOffLightCommand(l);

       // Создается invoker, с которым мы будем непосредственно контактировать:
       Switch s=new Switch(switchUp,switchDown);

       // вот проверка этого, используем методы:
       s.flipUp();
       s.flipDown();
   }
}
На выводе будет следующее:
The light is on
The light is off
С Java 8 интерфейс Command с единственным абстрактным методом это функциональный интерфейс, поэтому для таких простых команд отдельные классы не обязательны: достаточно передать Switch ссылки на методы приемника.
Light l = new Light();
Switch s = new Switch(l::turnOn, l::turnOff);
s.flipUp();   // The light is on
s.flipDown(); // The light is off
Классы TurnOnLightCommand и TurnOffLightCommand нужны, когда команда хранит состояние или умеет больше одного действия, например execute() и undo().

Где это применяется?

По цели ясно, что и для чего это нужно, а именно: в ситуации, когда нужно отделить конкретное исполнение, это очень удобно. Чтоб использование каких-то функций не зависело от конкретной реализации и ее можно было изменять без ущерба для системы. Типичные случаи, где это выручает:
  • отмена и повтор действий: команда помнит, как себя откатить, а история команд это обычный стек;
  • очереди и планировщики задач: задачу кладут в очередь как объект и выполняют позже (так устроен Runnable в JDK);
  • кнопки, пункты меню и горячие клавиши: одно действие привязано сразу к нескольким элементам интерфейса;
  • макросы и скрипты: список команд, которые выполняются подряд;
  • журналирование и транзакции: команды записывают в лог, а при сбое выполняют заново.
как-то так...) Пишите свои комментарии, давайте обсуждать, может что-то можно сделать проще и рассказать лучше, будем все править, если необходимо) Чтоб для тех, кто будет читать первый раз, было как можно понятнее. Ну и кому понравится статья, ставьте "+" на ней :) Для меня это важно) Со временем хочу написать еще про Builder, Singleton и прочие.

Вопросы и ответы

Что такое паттерн Command простыми словами?

Это способ превратить вызов метода в объект. Вместо того чтобы вызывать light.turnOn() напрямую, создают объект-команду с методом execute(), внутри которого и спрятан этот вызов. Тот, кто держит команду, не знает, что она делает и с каким объектом: он просто вызывает execute(). Так запрос можно передавать, хранить и выполнять позже.

Кто такие receiver, invoker и client в паттерне Command?

Receiver (приемник) это объект, который умеет выполнять реальную работу, в примере класс Light. Command (команда) хранит ссылку на приемник и в execute() вызывает нужный его метод: TurnOnLightCommand и TurnOffLightCommand. Invoker (вызывающий) хранит команды и запускает их, не зная деталей: класс Switch. Client (клиент) собирает все вместе: создает приемник и команды и передает их вызывающему, в примере это метод main класса TestCommand.

Зачем оборачивать вызов метода в объект, если можно вызвать его напрямую?

Прямой вызов жестко связывает того, кто просит действие, с тем, кто его выполняет: Switch должен был бы знать про Light и его методы. С командами Switch работает только с интерфейсом Command, поэтому к нему можно подключить любое действие, не меняя его код. Кроме того, объект-команду можно положить в список, очередь или историю, выполнить позже, повторить или отменить, а с обычным вызовом метода так не получится.

Где паттерн Command используется на практике?

Отмена и повтор действий в редакторах (каждая команда помнит, как себя откатить), очереди задач и планировщики (задача это команда, которую выполнят позже), кнопки и пункты меню в графических интерфейсах (одно действие привязано к нескольким элементам), макросы (список команд, выполняемых подряд), а также журналирование и транзакции. В JDK по этому принципу устроен интерфейс Runnable: объект с единственным методом run(), который поток выполняет, не зная, что внутри.

Чем Command отличается от Strategy?

Оба заворачивают поведение в объект с одним методом, но с разной целью. Strategy подставляет один из взаимозаменяемых алгоритмов для одной и той же задачи (например, разные способы сортировки), и выбирает его тот, кто эту задачу решает. Command заворачивает сам запрос: вызывающий не выбирает алгоритм и вообще не знает, что произойдет, а команды можно копить, откладывать и отменять. Подробнее про Strategy и State в статье о разнице между паттернами Состояние и Стратегия.

Можно ли реализовать Command через лямбду в Java 8 и новее?

Да. Интерфейс Command с единственным абстрактным методом execute() это функциональный интерфейс, поэтому вместо класса TurnOnLightCommand можно передать ссылку на метод: new Switch(light::turnOn, light::turnOff). Отдельные классы остаются полезны, когда команде нужно хранить состояние, например данные для отмены, или иметь несколько методов вроде execute() и undo(). См. также мои другие статьи: Читайте также про другие паттерны: