JavaRush/Java блог/Архив info.javarush/Паттерн Command своими словами
Roman_kh
33 уровень

Паттерн Command своими словами

Статья из группы Архив info.javarush
участников

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

Разобрался-таки основательно(как мне кажется) с паттером Command и хочу попытаться рассказать о нем "своими словами". Паттерн Command своими словами - 1Исходя из Википедии, можем узнать, что цель его — это создание структуры, в которой класс-отправитель и класс-получатель не зависят друг от друга напрямую. Организация обратного вызова к классу, который включает в себя класс-отправитель. В принципе написано чётко и правильно, но это в теории. А как же сделать это? Вот этим начинаются проблемы, т.к. описание уже не так ясно и очевидно. Поэтому я как разобрался, решил рассказать вам как я это понял, может кому-то и пригодится: Исходя из описания цели этого паттерна, буду комбинировать описание сразу с кодом, чтоб было нагляднее, т.к. в той же википедии обобщили для многих языков и поэтому описание отделено от примера. В этом паттерне есть четыре термина, пока примем их как данность: команды(command), приемник команд(receiver), вызывающий команды(invoker) и клиент(client). Пример буду брать с той же Википедии, он вполне сносный. Задача есть класс 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");
     }
}
Создадим интерфейс с одним методом 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();
   }
}
Теперь пришло время создать объект, который бы принимал эти инкапсулированные методы объекта 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();
    }
}
Ну и конечно создадим класс который будет использовать их, чтобы понять что происходит вообще. Он будет именть метод 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"

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

По цели ясно, что и для чего это нужно, а именно: в ситуации, когда нужно отделить конкретное исполнение, это очень удобно. Чтоб использование каких-то функций не зависело от конкретной реализации и ее можно было изменять без ущерба для системы. как-то так...) Пишите свои комментарии, давайте обсуждать, может что-то можно сделать проще и рассказать лучше, будем всё править, если необходимо) Чтоб для тех, кто будет читать первый раз, было как можно понятнее. Ну и кому понравится статья — ставьет "+" на ней :) Для меня это важно) Со временем хочу написать еще про Builder, Singleton и прочие. См. также мои другие статьи:
Комментарии (9)
  • популярные
  • новые
  • старые
Для того, чтобы оставить комментарий Вы должны авторизоваться
N1k0
Уровень 22
3 июня 2021, 11:39
Насколько я понял, патерн команда позволяет реализовать стек операций, который в свою очередь помогает организовать историю хранения операций, а также такие возможности как отмена операции (undo) и возврат к состоянию на момент выполненной операции. Хорошо изложено тут: рефакторинг
Jean Valjean
Уровень 41
11 декабря 2020, 10:44
А ведь всего то просили свет выключить, уходя с работы
11 марта 2021, 16:04
Чтобы выключить свет нужно думать как свет
1 ноября 2018, 19:46
суть реализации понял, спасибо, но вот зачем этот паттерн, к сожалению пока так и не могу догнать.((( На первый неопытный взгляд, этот паттерн плодит кучу классов с одним методом. Если например команд не две а 102, то на каждую по своему отдельному классу надо.
NodeOne
Уровень 41
Expert
3 марта 2019, 05:54
при том, что можно вызывать те же самые методы у объекта класса light. Вот если командный класс реализует много интерфейсов и соответственно собирает(инкапсулирует) методы разных классов... вот где кайф чувак!!!
Nullptr
Уровень 35
18 января 2017, 01:11
Вот тут довольно хороший и понятный пример разбора паттерна Команда
imp
Уровень 31
17 января 2017, 19:29
2Роман
насколько я понял этот паттерн у тебя еще должен быть CommandExecutor(единственная точка входа)
то есть к самим командам у тебя не должен быть доступа.

соот-но команды ты должен вызывать CommandExecutor.execute(Команда)
Torin
Уровень 27
17 января 2017, 18:05
Я все понял, только не понял зачем мне это нужно. Давай еще примеров! примеры наше все! :) А лучше описать проблему, которую мог бы решить этот паттерн
Roman_kh
Уровень 33
17 января 2017, 19:29
Вот нашел пример, понравился мне:
public class Steps {
    public void goSouth() {
        System.out.println("step to south");
    }

    public void goNorth() {
        System.out.println("step to north");
    }

    public void goEast() {
        System.out.println("step to east");
    }

    public void goWest() {
        System.out.println("step to west");
    }
}

public abstract class StepsCommand implements Command {
    protected Steps steps = new Steps();
}

public interface Command {
    void execute();
}

public class GoEastCommand extends StepsCommand {
    @Override
    public void execute() {
        steps.goEast();
    }
}

public class GoNorthCommand extends StepsCommand {
    @Override
    public void execute() {
        steps.goNorth();
    }
}

public class GoSouthCommand extends StepsCommand {
    @Override
    public void execute() {
        steps.goSouth();
    }
}

public class GoWestCommand extends StepsCommand {
    @Override
    public void execute() {
        steps.goWest();
    }
}

public class Navigator {
    private final List<StepsCommand> steps = new LinkedList<>();
    private final List<StepsCommand> path = new LinkedList<>();

    public Navigator registerStep(StepsCommand step) {
        steps.add(step);
        return this;
    }

    public void go() {
        for(StepsCommand step : steps) {
            step.execute();
            ((LinkedList)path).addFirst(step);
        }
        steps.clear();
    }

    public void goBack() {
        for(StepsCommand step : path) {
            step.execute();
        }
        path.clear();
    }
}

public class Client {
    public static void main(String[] args) {
        Navigator navigator =
            new Navigator().registerStep(new GoEastCommand())