Как шаблон команды отделяет отправителя от получателя?

Шаблон Command имеет интерфейс IReceiver с несколькими методами, и каждому методу соответствуют конкретные объекты Command (реализующие интерфейс ICommand с методом execute ()).

Я читал, что клиент знает о конкретном получателе и конкретной команде, и обычно это клиент, настраивающий объект-получатель в конкретном командном объекте. Тогда почему говорят, что он разделяет отправителя и получателя?

Когда клиент уже знает конкретного получателя, я чувствую, что это не слабая связь, а также в этом случае клиент может напрямую вызывать API (методы) на объекте-получателе.


person nits.kk    schedule 08.02.2016    source источник


Ответы (3)


Вы можете представить себе рабочий процесс шаблона команды следующим образом.

  1. Command объявляет интерфейс для всех команд, предоставляя простой метод execute (), который просит получателя команды выполнить операцию.

  2. Receiver знает, что делать, чтобы выполнить запрос.

  3. Invoker содержит команду и может заставить Command выполнить запрос, вызвав метод execute.

  4. Client создает ConcreteCommands и устанавливает Receiver для команды.

  5. ConcreteCommand определяет привязку между действием и получателем.

  6. Когда вызовы Invoker выполняются, ConcreteCommand запускает одно или несколько действий на приемнике.

Взгляните на пример кода, чтобы лучше понять ситуацию.

public class CommandDemoEx{
    public static void main(String args[]){

        // On command for TV with same invoker 
        Receiver r = new TV();
        Command onCommand = new OnCommand(r);
        Invoker invoker = new Invoker(onCommand);
        invoker.execute();

        // On command for DVDPlayer with same invoker 
        r = new DVDPlayer();
        onCommand = new OnCommand(r);
        invoker = new Invoker(onCommand);
        invoker.execute();

    }
}
interface Command {
    public void execute();
}

class Receiver {
    public void switchOn(){
        System.out.println("Switch on from:"+this.getClass().getSimpleName());
    }
}

class OnCommand implements Command{

    private Receiver receiver;

    public OnCommand(Receiver receiver){
        this.receiver = receiver;
    }
    public void execute(){
        receiver.switchOn();
    }
}

class Invoker {
    public Command command;

    public Invoker(Command c){
        this.command=c;
    }
    public void execute(){
        this.command.execute();
    }
}

class TV extends Receiver{
    public TV(){

    }
    public String toString(){
        return this.getClass().getSimpleName();
    }
}
class DVDPlayer extends Receiver{
    public DVDPlayer(){

    }
    public String toString(){
        return this.getClass().getSimpleName();
    }
}

выход:

java CommandDemoEx
Switch on from:TV
Switch on from:DVDPlayer

Чтобы ответить на ваш вопрос:

Я читал, что клиент знает о конкретном получателе и конкретной команде, и обычно это клиент, настраивающий объект-получатель в конкретном командном объекте. Тогда почему говорят, что он разделяет отправителя и получателя

Чтобы стандартизировать слова, замените «отправитель» на «вызывающий». Теперь пройдемся по коду.

  1. Invoker simply executes the ConcreteCommand (в данном случае OnCommand) путем передачи ConcreteReceiver.
  2. ConcreteCommand executes Command через ConcreteReceiver, т.е. ConcreteCommand defines binding between Action and Receiver.
  3. Если вы видите рабочий процесс, Invoker не изменяется с помощью дополнительных команд, и вы можете добавить бизнес-логику в execute() метод Invoker, например java.lang.Thread, который был объяснен, как показано ниже.
  4. Таким образом Client (sender) and Receiver are loosely couple through Invoker, which has knowledge of what command to be executed.

Пример темы из этого ссылка

Вы можете создать поток, реализовав объект Runnable.

Thread t = new Thread (new MyRunnable()).start();

=>

 Invoker invoker = new Invoker(new ConcreteCommand());
 invoker.start() 

и у вас есть логика в start () для вызова ConcreteCommand.execute (), который в приведенном выше случае запускается ().

Метод start () вызовет метод run () в потоке. Что произойдет, если вы вызовете метод run () напрямую? Он не будет рассматриваться как цепочка.

Как и метод start () этого потока, вы можете добавить некоторую бизнес-логику в Invoker.

public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();
        group.add(this);
        start0();
        if (stopBeforeStart) {
            stop0(throwableFromStop);
        }
    }

private native void start0(); // Native code is not here but this method will call run() method

public void run() {
    if (target != null) {
        target.run();
    }
}

ИЗМЕНИТЬ:

По вашему последнему запросу

Здесь мы создаем объект команды, объект Receiver и объект Invoker. Затем передаем объект-получатель в объекте команды, а затем передаем объект команды в объект invoker. Это мы делаем для каждого приемника, как здесь для TV и DVDPlayer. Также в методе «main» объект TV и DVDPlayer известны и фактически создаются. Мы можем просто выполнить tvObject.switchOn () и dvdPlayer.switchOn (). Как помогает шаблон Command

Клиенту не нужно беспокоиться об изменениях в классе Receiver. Invoker напрямую работает с ConcreteCommand, у которого есть объект Receiver. Receiver объект может измениться с siwtchOn () на switchOnDevice () в будущем. Но взаимодействие с клиентом не меняется.

Если у вас есть две разные команды, такие как switchOn () и switchOff (), вы все равно можете использовать тот же Invoker.

person Ravindra babu    schedule 09.02.2016
comment
Спасибо, Равиндра, за ответ, но я все еще не понимаю. Я хочу прояснить свое понимание того, насколько полезен шаблон Command. Здесь мы создаем объект команды, объект Receiver и объект Invoker. Затем передаем объект-получатель в объекте команды, а затем передаем объект команды в объект invoker. Это мы делаем для каждого приемника, как здесь для TV и DVDPlayer. Также в методе «main» объект TV и DVDPlayer известны и фактически создаются. Мы можем просто выполнить tvObject.switchOn () и dvdPlayer.switchOn (). Как помогает шаблон Command. - person nits.kk; 09.02.2016
comment
Если switchOn позже был изменен на switchDevice, клиент не требует никаких изменений, если эта команда выполняется из ConcreteCommand. Как и в случае с switchOn, вы можете иметь команду switchOff и использовать один и тот же вызывающий элемент для команд включения и выключения. - person Ravindra babu; 10.02.2016
comment
обратитесь к codereview.stackexchange.com/questions/120029/ Я реализовал шаблон команды в соответствии с моим пониманием после изучения шаблона команды и комментариев здесь. Было бы хорошо, если бы ваши комментарии к обзору были там ... - person nits.kk; 15.02.2016
comment
Обновил свои комментарии в этом вопросе. Связал этот вопрос там :) - person Ravindra babu; 15.02.2016
comment
«Клиенту не нужно беспокоиться об изменениях в классе Receiver. ..... Объект-получатель может измениться с siwtchOn() на switchOnDevice() в будущем. Но взаимодействие с клиентом не меняется. '- если объект-получатель изменяет siwtchOn() на switchOnDevice(), то клиенту не нужно беспокоиться об изменении. Но Concretecommand нужно это знать, верно? Если да, то на какие преимущества вы здесь указываете? - person Istiaque Ahmed; 08.11.2020

Непосредственно из Википедии:

Шаблон команды - это шаблон проектирования поведения, в котором объект используется для инкапсуляции всей информации, необходимой для выполнения действия или запуска события в более позднее время.

Изменить

После повторного прочтения раздела Gang of Four о шаблоне Command , Я придумал лучший сценарий. Допустим, у вас есть библиотека графического интерфейса, которая определяет следующее:

public interface Command {
    public void execute();
}

public class Button {
    private Command command;

    public Button(Command command) {
        this.command = command;
    }

    public void click() {
        command.execute();
    }
}

Button в этом случае является получателем команды, а ваш код, который создает фактические экземпляры Buttons, является клиентом. Конечно, когда вы создаете кнопку, вы должны определить некоторые конкретные реализации интерфейса Command. Но библиотеке графического интерфейса не нужно знать об этих классах; все, что ему нужно, это интерфейс. Вот так код графического интерфейса пользователя отделяется от вашего кода.

person Andrew Williamson    schedule 08.02.2016
comment
спасибо Андрею за ответ. Извините, но все же я не совсем уверен в этом. Не могли бы вы привести небольшой пример того, как, если шаблон команды не используется, становится трудно управлять, или увеличивается сложность, или дублирование кода или любой другой вредный эффект, если шаблон команды не используется ... - person nits.kk; 09.02.2016
comment
@ nits.kk См. правку, надеюсь, в ней больше смысла. Основная идея заключается в том, что реализация скрыта за командным интерфейсом, поэтому вы не получите несколько классов, когда вам нужен только один. Например. RefreshButton и UndoButton могут быть просто Button с разными Command для выполнения. - person Andrew Williamson; 09.02.2016
comment
Впечатляющее объяснение Андрей .. Спасибо за старания. Но мне кажется, что это больше похоже на образец наблюдателя. В приведенном выше объяснении это больше похоже на обратный вызов какой-то миниатюрной формы паттерна наблюдателя. В этом примере Command похожа на наблюдателя, зарегистрированного в классе кнопки. При нажатии вызывается метод обратного вызова execute, и в конкретной реализации метода execute () могут выполняться фактические операции, такие как обновление, отмена. Пожалуйста, поправьте меня, если я неправильно понимаю ... - person nits.kk; 09.02.2016
comment
Да, шаблоны проектирования часто пересекаются. Это также показывает множество возможностей стратегии, где фактическая реализация скрыта за интерфейсом. Особенность Command заключается в том, что каждый объект команды содержит всю информацию, необходимую для выполнения действия. Этот пример на самом деле не так много показывает, вы хотели увидеть, как он отделяет код. Это был самый простой способ показать это. - person Andrew Williamson; 09.02.2016

Слабая связь - не главная цель Command

Вот диаграмма классов для шаблона Command из исходного Design Книга выкройки:

Как вы сказали, Client знает о ConcreteCommand и Receiver, поэтому развязки там нет.

почему говорят, что он разделяет отправителя и получателя

В моем экземпляре книги не говорится, что цель паттерна Command:

Инкапсулируйте запрос как объект, тем самым позволяя параметризировать клиентов с различными запросами, очередями или запросами журнала и поддерживать отменяемые операции.

Ответ Эндрю касается того факта, что логический поток отделен от команд. Возможно, вы лучше увидите слабую связь между Invoker и Command, если обратитесь к диаграмме последовательности шаблона, описанной в шаблонах проектирования:

Многие шаблоны проектирования определяют клиента, который слабо связан с вариантами (например, посетитель, стратегия, наблюдатель, итератор и т. Д.). Слабое соединение - это преимущество для ремонтопригодности, так называемый «дизайн для изменений». Команда особенная, так как Клиент, защищенный от изменений, Invoker - он отделен от ConcreteCommmand классов. Я думаю, это классическая развязка, которую вы ищете. Добавление новых команд потребует изменения Client, но не должно нарушать Invoker, кто знает только Command абстракцию.

Я всегда считал шаблон Command уникальным, потому что его основная цель, по-видимому, заключается в обеспечении функциональных требований: отмена, повтор, ведение журнала, макрокомандные операции, транзакции и т. Д.


Редактировать

Что касается IReceiver абстракции и отделения от Client и конкретных Receiver классов: возможно, это просто шаблон стратегии, используемый с Command. Я процитировал оригинальную книгу. Существует множество вариантов шаблонов (из-за этого Википедия не всегда является отличным справочником по шаблонам).

person Fuhrmanator    schedule 09.02.2016
comment
Спасибо за ответ. Если есть несколько приемников с разными действиями, например TV: switchON (), setFirstChannel (); AC: switchOn (), setMinTemp (); MusicPlayer: switchOn (), setMaxVolume (). Если все эти запросы поставлены в очередь. В случае, если необходимо включить какое-либо новое устройство, такое как Microwave: switchON (), set30SecTimer (), тогда просто объекты Microwave могут быть инкапсулированы в объект TimerCommand, и его можно просто добавить в очередь. Таким образом, шаблон команды может быть хорошим подспорьем. Пожалуйста, поправьте, если я ошибаюсь. - person nits.kk; 09.02.2016