命令模式如何将发送者与接收者解耦?

How does the Command pattern decouple the sender from the receiver?

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

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

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

直接来自Wikipedia

The command pattern is a behavioral design pattern in which an object is used to encapsulate all information needed to perform an action or trigger an event at a later time.

编辑

重新阅读 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 代码与您的代码分离的方式。

松耦合不是 Command 的主要目标

这是来自原始 Design Patterns book 的命令模式的 class 图:

正如你所说,Client 知道 ConcreteCommandReceiver,所以那里没有解耦。

why it is said it decouples the sender and the receiver

我的书没有说这是命令模式的目标:

Encapsulate a request as an object, thereby letting you parameterize clients with different requests, queue or log requests, and support undoable operations.

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

许多设计模式定义了一个与变体松散耦合的客户端(例如,访问者、策略、观察者、迭代器等)。松散耦合有利于可维护性,so-called 设计变更。命令是特殊的,因为受保护免受更改的客户端是 Invoker —— 它与 ConcreteCommmand classes 分离。我认为这就是您正在寻找的 classic 解耦。添加新命令将需要更改 Client,但不应破坏 Invoker,后者只知道 Command 抽象。

我一直认为命令模式是独一无二的,因为它的主要目标似乎是提供功能需求:撤消、重做、日志记录、macro-command 操作、事务等


编辑

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

你可以把命令模式的工作流程想象成下面这样。

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

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

  3. Invoker持有命令,可以通过调用execute方法获取Command执行请求

  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

回答你的问题:

I have read client knows about the concrete receiver and concrete command and it is usually client setting up the receiver object in the concrete command object. Then why it is said it decouples the sender and the receiver

为了标准化单词,将"sender"替换为"invoker"。现在通过代码。

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

线程示例 来自此link

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

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

=>

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

并且您在 start() 中有调用 ConcreteCommand.execute() 的逻辑,在上述情况下为 运行()。

start()方法会调用Thread中的运行()方法。如果直接直接调用运行()方法会怎么样?它不会被视为线程

像这个线程的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();
    }
}

编辑:

关于您的上次查询

Here we are creating the command object, Receiver object and Invoker Object.Then passing the receiver object in the command object and then passing the command object in invoker object. This we do for each Receiver like we do here for TV and DVDPlayer. Also in the method 'main' Object of TV and DVDPlayer are known and in fact are created. We can simply do tvObject.switchOn() and dvdPlayer.switchOn(). How does Command pattern help

客户无需担心 Receiver class 的变化。 Invoker 直接作用于 ConcreteCommand,它有 Receiver 个对象。 Receiver 对象将来可能会将 siwtchOn() 更改为 switchOnDevice()。但客户端交互不会改变。

如果您有两个不同的命令,例如 switchOn() 和 switchOff(),您仍然可以使用相同的 Invoker.