设计模式~命令模式

208 阅读3分钟

1. 定义

  • 将一个请求封装成一个对象,从而让用户使用不同的请求把客户端参数化;对请求排队或记录请求日志,以及支持可撤销的操作。

2. UML类图

image.png

  • Receiver:接收者角色,命令的真正执行者,任何类都可以成为一个接收者,而在接收者类中封装具体操作逻辑的方法我们则称为行动方法
  • Command:命令角色,定义所有命令方法的接口
  • ConcreteCommand:具体命令角色,Command的具体实现类,在execute方法中调用接收者角色的相关方法,在接收者和命令执行的具体行为之间加以弱耦合
  • Invoker:请求者角色,主要职责是调用命令对象执行具体的请求,相关的方法被称为行动方法
  • Client:客户端角色

4. 使用场景

  • 当某项操作具备命令语义时,且命令实现存在不确定性,容易变化,那么可以通过命令模式解耦请求与实现,利用抽象命令接口使请求方代码架构稳定,封装接收方实现命令的具体细节
    • 语义中具备 “命令”的操作(如命令菜单,shell 命令⋯);
    • 请求调用者和请求的接收者需要解耦,使得调用者和接收者不直接交互;
    • 需要抽象出等待执行的行为,比如撤销(Undo)操作和恢复(Redo)等操作;
    • 需要支持命令宏(即命令组合操作)。

5. 简单实现

  • 模拟一个儿童遥控车,遥控器上的前进后退相当于命令,具体实现这个命令的为遥控车
  • 首先定义一个遥控车对象(命令实现者),本身拥有前进后退的功能
/**
 * 遥控车
 *
 * @author BTPJ  2023/2/3
 */
public class Telecar {

    public void forward() {
        System.out.println("前进");
    }

    public void retreat() {
        System.out.println("后退");
    }
}
  • 定义命令抽象接口
/**
 * 命令抽象接口
 *
 * @author BTPJ  2023/2/3
 */
public interface ICommand {

    /**
     * 命令执行方法
     */
    void execute();
}
  • 实现前进的命令
/**
 * 前进的命令实现
 *
 * @author BTPJ  2023/2/3
 */
public class ForwardCommand implements ICommand {
    /**
     * 具体实现是车,所以得有一个车的引用对象
     */
    private final Telecar telecar;

    public ForwardCommand(Telecar telecar) {
        this.telecar = telecar;
    }

    @Override
    public void execute() {
        telecar.forward();
    }
}
  • 实现后退的命令
/**
 * 后退的命令实现
 *
 * @author BTPJ  2023/2/3
 */
public class RetreatCommand implements ICommand {
    /**
     * 具体实现是车,所以得有一个车的引用对象
     */
    private final Telecar telecar;

    public RetreatCommand(Telecar telecar) {
        this.telecar = telecar;
    }

    @Override
    public void execute() {
        telecar.retreat();
    }
}
  • 构建一个命令请求者:遥控器
/**
 * 模拟遥控上的前进后退按钮
 *
 * @author BTPJ  2023/2/3
 */
public class Buttons {
    /**
     * 命令只关心前进后退按键,不关心车,实现对Telecar车的解耦
     */
    private ForwardCommand forwardCommand;
    private RetreatCommand retreatCommand;

    public void setForwardCommand(ForwardCommand forwardCommand) {
        this.forwardCommand = forwardCommand;
    }

    public void setRetreatCommand(RetreatCommand retreatCommand) {
        this.retreatCommand = retreatCommand;
    }

    public void forward() {
        forwardCommand.execute();
    }

    public void retreat() {
        retreatCommand.execute();
    }
}
  • 客户端调用
/**
 * 客户端调用
 *
 * @author BTPJ  2023/2/3
 */
public class Client {
    public static void main(String[] args) {
        // 首先要有遥控车
        Telecar telecar = new Telecar();

        // 构造遥控
        Buttons buttons = new Buttons();
        buttons.setForwardCommand(new ForwardCommand(telecar));
        buttons.setRetreatCommand(new RetreatCommand(telecar));

        // 可以开始操作发送命令了
        buttons.forward();
        buttons.retreat();
    }
}

执行结果:
前进
后退

6. 源码中的使用场景

  • Android事件输入系统

7. 优缺点

  • 优点:
    • 降低命令请求与实现的耦合性
    • 扩展性好,可以很灵活的增加新接口
  • 缺点:
    • 大量衍生类的创建导致类的膨胀