Command模式如何将发送方与接收方分离?

时间:2016-02-08 18:42:58

标签: java design-patterns command-pattern

Command模式有一个IReceiver接口,只有很少的方法,并且对应每个方法都有具体的Command对象(用execute()方法实现接口ICommand)。

我已经读过客户端知道具体的接收器和具体的命令,通常客户端在具体的命令对象中设置接收器对象。那为什么说它解耦发送者和接收者?

当客户端已经知道具体的接收器时,我觉得这不是松耦合,在这种情况下客户端也可以直接调用接收器对象上的API(方法)。

3 个答案:

答案 0 :(得分:2)

直接来自Wikipedia

  

命令模式是一种行为设计模式,其中一个对象用于封装执行操作或稍后触发事件所需的所有信息。

修改

重新阅读命令模式的Gang of Four部分后,我想到了一个更好的方案。假设您有一个GUI库,它定义了以下内容:

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接口的一些具体实现。但GUI库不需要知道这些类;它所需要的只是界面。这就是GUI代码与代码分离的方式。

答案 1 :(得分:2)

您可以将Command模式工作流视为如下。

  1. Command为所有命令声明一个接口,提供一个简单的execute()方法,该方法要求Receiver执行该操作。

  2. Receiver知道如何执行请求。

  3. Invoker拥有一个命令,可以通过调用execute方法让Command执行请求。

  4. Client创建ConcreteCommands并为该命令设置Receiver

  5. ConcreteCommand定义了动作和接收者之间的绑定。

  6. 执行Invoker次来电时,ConcreteCommand会在接收方上执行一项或多项操作。

  7. 查看示例代码,以更好的方式理解事物。

    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
    

    回答你的问题:

      

    我已阅读客户端了解具体接收器和具体命令,通常是客户端在具体命令对象中设置接收器对象。那么为什么说它解耦发送者和接收者

    要标准化单词,请替换" sender"与"调用者"。现在浏览代码。

      通过传递ConcreteReceiver
    1. Invoker simply executes the ConcreteCommand (在本例中为OnCommand)。
    2. 通过ConcreteReceiver
    3. ConcreteCommand executes Command ,即ConcreteCommand defines binding between Action and Receiver.
    4. 如果您看到工作流程,Invoker不会更改其他命令,您可以在Invoker的execute()方法中添加业务逻辑,如java.lang.Thread,已解释如下。
    5. 这样 Client (sender) and Receiver are loosely couple through Invoker, which has knowledge of what command to be executed
    6. 来自此link

      主题示例

      您可以通过实现Runnable对象来创建Thread。

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

      =>

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

      你在start()中有逻辑来调用在上面的例子中运行()的ConcreteCommand.execute()。

      start()方法将在Thread中调用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 Object。然后在命令对象中传递receiver对象,然后在invoker对象中传递命令对象。我们为每个接收器做的就像我们在这里为电视和DVDPlayer做的那样。同样在方法&#39; main&#39; TV和DVDPlayer的对象是已知的并且实际上是已创建的。我们可以简单地做tvObject.switchOn()和dvdPlayer.switchOn()。 Command模式如何帮助

      客户无需担心Receiver课程的变化。 Invoker直接在ConcreteCommand上运行,Receiver具有Receiver个对象。 siwtchOn对象将来可能会将switchOnDevice()更改为switchOn()。但客户互动不会改变。

      如果您有两个不同的命令,例如switchOff()和Invoker(),您仍然可以使用相同的<div ng-app="myApp" ng-controller="myCtrl"> <select class="form-control input-sm" id="accountSelect" ng-model="option.account" ng-options="account.id as account.name for account in accounts"> </select> </div>

答案 2 :(得分:1)

松散耦合不是Command

的主要目标

这里是原始Design Patterns book的命令模式的类图:

Command class diagram

正如您所说,Client知道ConcreteCommandReceiver,所以那里没有脱钩。

  

为什么说它将发送者和接收者分离

我的书副本并没有说这是Command模式的目标:

  

将请求封装为对象,从而允许您使用不同的请求,队列或日志请求参数化客户端,并支持可撤销的操作。

安德鲁的答案触及逻辑线程与命令分离的事实。当您参考设计模式中描述的模式的序列图时,您可以更好地看到InvokerCommand之间的松散耦合:

Command sequence diagram

许多设计模式定义了与变体松散耦合的客户端(例如,访问者,策略,观察者,迭代器等)。松耦合是可维护性的一个好处,即所谓的变更设计。命令很特殊,因为受到更改保护的客户端是Invoker - 它与ConcreteCommmand类分离。我认为这是你正在寻找的经典脱钩。添加新命令需要更改Client,但不应该中断Invoker,只知道Command抽象。

我一直认为Command模式是唯一的,因为它的主要目标似乎是提供功能需求:撤消,重做,日志记录,宏命令操作,事务等。

修改

关于IReceiver抽象和与Client和具体Receiver类的解耦:这可能只是与Command一起使用的策略模式。我引用了原书。存在许多模式的变体(因此,维基百科并不总是模式的很好的参考)。