天天看点

设计模式(14)-命令模式

简介

为什么要使用命令模式

在软件设计中,经常需要向某些对象发送请求,但并不知道具体是哪个对象接收请求,也不知道请求的操作是什么,只有在程序运行时才知道请求的接受者是哪个对象。这时,就可以使用命令模式来设计。命令模式可以对发送者和接受者进行解耦,发送者和接受者之间没有直接调用关系,使对象之间的调用更加灵活。

什么是命令模式

命令模式(Command Pattern):将一个请求封装为一个对象,从而可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。

别名

动作(Action)模式、事务(Transaction)模式。

类型

对象行为型模式

遵守的原则

迪米特法则、开闭原则

角色

角色

  • Command: 抽象命令类
    • 抽象类或者接口
    • 声明了用于执行请求的execute()等方法,在方法中可以调用接受者的方法。
  • ConcreteCommand: 具体命令类
    • 具体类
    • 实现了抽象命令类的方法。
    • 含有请求接受者的引用,可以调用请求接受者的方法。
  • Invoker: 调用者
    • 具体类
    • 请求的发送者
    • 通过命令类对象来执行请求。和抽象命令类之间存在关联关系,在程序运行时可以将一个具体命令对象注入其中,再调用具体命令对象的execute()方法,从而实现间接调用请求接收者的相关操作。
  • Receiver: 接收者
    • 具体类
    • 处理请求,实现具体的业务操作。
  • Client:客户类
    • 客户。创建一个具体命令对象并制定它的接受者。

UML类图

设计模式(14)-命令模式

代码实现

  • 新建接收者Receiver.java
  • 新建抽象命令类Command.java
  • 新建具体命令类ConcreteCommand.java
  • 新建调用者Invoker.java
  • 新建客户类Client.java

接收者Receiver.java

class Receiver {
    public void action() {
        // 处理请求,实现具体的业务操作
        System.out.println("Receiver.action()");
    }
}
           

抽象命令类Command.java

abstract class Command {
    public abstract void execute();
}
           

具体命令类ConcreteCommand.java

class ConcreteCommand extends Command {
    private Receiver receiver; // 维持一个对请求接收者对象的引用

    public ConcreteCommand(Receiver receiver) {
        super();
        this.receiver = receiver;
    }

    public void execute() {
        receiver.action(); // 调用请求接收者的业务处理方法action()
    }
}
           

调用者Invoker.java

class Invoker {
    private Command command;

    // 构造注入
    public Invoker(Command command) {
        this.command = command;
    }

    // 设值注入
    public void setCommand(Command command) {
        this.command = command;
    }

    // 业务方法,用于调用命令类的execute()方法
    public void call() {
        command.execute();
    }
}
           

客户类Client.java

public class Client {
    public static void main(String[] args) {
        Receiver pReceiver = new Receiver();
        ConcreteCommand pCommand = new ConcreteCommand(pReceiver);
        Invoker pInvoker = new Invoker(pCommand);
        pInvoker.call();
    }
}
           

测试

运行Client.java的main()

Receiver.action()
           

实例1:电视机遥控器

UML类图

设计模式(14)-命令模式

代码实现

  • 新建接收者Television.java
  • 新建抽象命令类Command.java
  • 新建具体命令类OpenTVCommand.java、CloseTVCommand.java
  • 新建调用者Invoker.java
  • 新建客户类Client.java

接收者Television.java

public class Television {
    public void trunOn() {
        // 处理请求,实现具体的业务操作
        System.out.println("Television.trunOn(),电视打开了");
    }

    public void trunOff() {
        // 处理请求,实现具体的业务操作
        System.out.println("Television.trunOff(),电视关闭了");
    }
}
           

抽象命令类Command.java

abstract class Command {
    public abstract void execute();
}
           

具体命令类

OpenTVCommand.java

public class OpenTVCommand extends Command {
    private Television television; // 维持一个对请求接收者对象的引用

    public OpenTVCommand(Television television) {
        super();
        this.television = television;
    }

    public void execute() {
        television.trunOn();
    }
}
           

CloseTVCommand.java

public class CloseTVCommand extends Command {
    private Television television; // 维持一个对请求接收者对象的引用

    public CloseTVCommand(Television television) {
        super();
        this.television = television;
    }

    public void execute() {
        television.trunOff();
    }
}
           

调用者Invoker.java

class Invoker {
    private Command command;

    // 构造注入
    public Invoker(Command command) {
        this.command = command;
    }

    // 设值注入
    public void setCommand(Command command) {
        this.command = command;
    }

    // 业务方法,用于调用命令类的execute()方法
    public void call() {
        command.execute();
    }
}
           

客户类Client.java

public class Client {
    public static void main(String[] args) {
        Television television = new Television();
        OpenTVCommand openTVCommand = new OpenTVCommand(television);
        Invoker invoker = new Invoker(openTVCommand);
        invoker.call();

        CloseTVCommand closeTVCommand = new CloseTVCommand(television);
        invoker = new Invoker(closeTVCommand);
        invoker.call();
    }
}
           

测试

运行Client.java的main()

Television.trunOn(),电视打开了
Television.trunOff(),电视关闭了
           

扩展

宏命令

宏命令又称为组合命令,它是命令模式和组合模式联用的产物。

  • 宏命令也是一个具体命令,不过它包含了对其他命令对象的引用,在调用宏命令的execute()方法时,将递归调用它所包含的每个成员命令的execute()方法执行一个宏命令将执行多个具体命令,从而实现对命令的批处理。
  • 一个宏命令的成员对象可以是简单命令,还可以继续是宏命令。

命令队列

当一个请求发送者发送一个请求时,可能不止一个请求接收者产生响应,这些请求接收者逐个执行业务方法,完成对请求的处理。这种情况可以通过命令对象来处理。

实现方法

增加一个CommandQueue类,由该类来负责存储多个命令对象,而不同的命令对象可以对应不同的请求接收者。

撤销操作

实现方法

  • 在命令类中增加一个逆向操作来实现撤销操作。
  • 通过保存对象的历史状态来实现撤销(可使用备忘录模式(Memento Pattern)来实现)。

请求日志

请求日志就是将请求的历史记录保存下来,通常以日志文件(Log File)的形式永久存储在计算机中。

实现方法

  • 将命令对象通过序列化写到日志文件中,此时命令类必须实现Java.io.Serializable接口。

组合模式Composite可用来实现宏命令

待补充

备忘录模式可用来实现撤销功能

待补充

优缺点

优点

  • 遵守“开闭原则”。加入新的具体命令类不会影响原有系统。
  • 遵守“迪米特法则”。请求者和接受者之间不存在直接引用,实现了解耦。
  • 可以比较容易地设计一个命令队列和宏命令(组合命令)。
  • 可以方便地实现对请求的撤销(Undo)和恢复(Redo)。

缺点

  • 系统中可能会有大量的命令类。

适用环境

  • 系统需要在不同的时间指定请求、将请求排队和执行请求。
  • 系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。
  • 系统需要将一组操作组合在一起,即支持宏命令。
  • 支持修改日志。

使用场景

  • 待补充

问题

画出宏命令,命令队列,撤销操作,请求日志的UML结构图、并写出实现代码

在软件开发中,你在哪里用到了命令模式?

待补充。

继续阅读