命令模式——命令发起者和接收者的解耦


Demo 地址: https://github.com/ooblee/HelloDesignPattern

1. 定义

命令模式(Command Pattern):将一个请求封装为一个对象,从而让我们可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。命令模式是一种对象行为型模式,其别名为动作(Action)模式或事务(Transaction)模式。

比如一个品牌下的多功能遥控器。可以控制风扇、空调、电灯、电视等等。

可以单独控制,也可以同时把所有电器打开。

也就是一个命令可能需要调用多个接收者,然后多个接收者又可以单独变化(比如换了台电视)。

这里的遥控器就类似于命令模式中的命令传递者。

我们可以很方便地发出指令,由遥控器来传达,控制相应设备。而不需要直接去和电器的控制进行交互。

命令模式抽象出一个层次,对请求的发送者和接受者进行解耦。

  • 双方独立发展,无需互相依赖。
  • 可以记录执行的命令,进行复原。
  • 易于扩展新的命令。

对请求发起者和接收者进行解耦

2. 设计

主要角色

  • 抽象命令(Command),定义命令执行方法。
  • 具体命令(Concrete Command),调用接收者,执行命令的具体方法。
  • 命令接收者(Receiver),命令的实际执行者,一个命令会存在一个或多个接收者。也可能一个接收者被多个命令使用。
  • 命令传递者(Invoker),调用抽象命令,还可以记录执行的命令。
  • 客户端(Client),发出命令。

类图如下:

命令模式-类图

抽象命令,一般只需要执行命令的方法。这里的例子支持回滚,所以这里加入了回滚方法

public interface ICommand {

    void execute();

    void fallback();
}

命令接收者 A

public class ReceiverA {

    public void sayHello() {
        System.out.println("A hello.");
    }

    public void sayHi() {
        System.out.println("A hi.");
    }
}

命令接收者 B

public class ReceiverB {

    public void hello() {
        System.out.println("B hello.");
    }
}

具体命令 hello,依赖命令接收者 A 和 B 来执行 hello 命令

public class HelloCommand implements ICommand {

    private ReceiverA receiverA = new ReceiverA();
    private ReceiverB receiverB = new ReceiverB();

    public void execute() {
        receiverA.sayHello();
        receiverB.hello();
    }

    public void fallback() {
        System.out.println("hello fallback");
    }
}

具体命令 hi,只有接收者 A 需要执行

public class HiCommand implements ICommand {

    private ReceiverA receiverA = new ReceiverA();

    public void execute() {
        receiverA.sayHi();
    }

    public void fallback() {
        System.out.println("hi fallback");
    }
}

命令传递者,用来触发具体的命令,这里加了一个调用栈,记录执行过的命令。同时增加了回滚和复原所有操作的方法:

public class Invoker {

    private Stack<ICommand> history = new Stack<ICommand>();

    public Invoker() {
    }

    public void invoke(ICommand command) {
        history.add(command);
        command.execute();
    }

    /**
     * 回滚
     */
    public void fallback() {
        if (history.size() > 0) {
            ICommand command = history.pop();

            // 回滚
            command.fallback();
        }
    }

    /**
     * 复原
     */
    public void restore() {
        for (ICommand command : history) {
            command.execute();
        }
    }
}

客户端使用姿势:

public class TestCommand {

    public static void main(String[] args) {

        // 接收者
        Invoker invoker = new Invoker();

        // 命令 hello
        invoker.invoke(new HelloCommand());

        // 命令 hi
        invoker.invoke(new HiCommand());

        // 再调用几次
        System.out.println("\n反复调用:");
        invoker.invoke(new HelloCommand());
        invoker.invoke(new HelloCommand());
        invoker.invoke(new HiCommand());
        invoker.invoke(new HiCommand());
        invoker.invoke(new HiCommand());

        // 复原所有执行过的命令
        System.out.println("\n复原所有命令:");
        invoker.restore();

        // 回滚
        System.out.println("\n回滚:");
        invoker.fallback();

        // 回滚后查询记录
        System.out.println("\n回滚后复原所有命令:");
        invoker.restore();
    }
}

输出如下:

A hello.
B hello.
A hi.

反复调用:
A hello.
B hello.
A hello.
B hello.
A hi.
A hi.
A hi.

复原所有命令:
A hello.
B hello.
A hi.
A hello.
B hello.
A hello.
B hello.
A hi.
A hi.
A hi.

回滚:
hi fallback

回滚后复原所有命令:
A hello.
B hello.
A hi.
A hello.
B hello.
A hello.
B hello.
A hi.
A hi.

2.1. 命令工厂

命令的创建,可以使用工厂模式。

2.2. 命令队列

当命令发起者不关心调用者何时执行,又或者需要中间设立缓冲区,可以创建命令队列。

2.3. 宏命令

命令和命令可以组合成新的命令。

3. 应用

应用场景:

  • 调用者和接收者解耦。大家互不关心,独立变化。各管各的。
  • 请求队列的实现。调用者先发起请求,但不关心接收者何时处理。
  • 系统有撤销和恢复操作。
  • 需要组合命令来实现宏命令(比如批处理)。

4. 特点

4.1. 优点

  • 解耦。命令发起者和接收者不存在直接的引用,二者独立变化。
  • 易扩展。增加新命令,不需要修改客户端代码。
  • 易组合。可以通过命令与命令组合,创建更复杂的命令,比如一个批处理命令。
  • 可以撤销和恢复。通过对命令的记录,可以撤销和恢复之前的命令。

4.2. 缺点

  • 类膨胀。一个具体的命令要对应一个命令类,会导致具体命令类变多。
发布了61 篇原创文章 · 获赞 43 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/firefile/article/details/90314320