GoF23:Command-命令

1、命令模式

命令(Command)

将请求封装成对象,以使用不同请求对其它对象进行参数化。

  1. 场景将请求的调用和实现解耦(请求 = 接收者 + 待执行动作)。
  2. 说明
    1. 执行流程调用者 → 命令对象 → 接收者
    2. 除了命令的执行execute),通常也支持撤销undo)、重做redo)。
    3. 遵循开闭原则。

类图

  1. Receiver(接收者):真正执行请求,通常定义为抽象类型。

  2. Command(命令):封装对接收者的调用

    1. 持有一个 Receiver 的引用。
    2. 提供 execute() 执行请求。
  3. ConcreteCommand(具体命令):

    1. 继承 Command 类。
    2. 实现 execute() 方法,通过接收者完成特定动作。
  4. Invoker(调用者):

    1. 持有若干个 Command 的引用。
    2. 调用 Command 对象完成预期功能。
  5. Client(客户端)

    image

2、case:遥控

2.1、需求

实现遥控器控制不同家电。

  1. Receiver - 家电:真正执行请求。
    • 电灯 Lamp:支持开关。
    • 电视机 Television:支持开关,调节音量。
  2. Invoker - 遥控器:需要发出请求。
    1. 开关:多对按钮,分别对应不同家电的开关操作。
    2. 撤销:单个按钮,取消上一次操作。

2.2、实现命令模式

接收者 - Appliance

接口:定义公共业务方法,即家电的开关。

public interface Appliance {
    void on();
    void off();
}

实现类:实现公共业务方法,允许自定义业务方法。

  1. 电灯 Lamp:支持开关。

    public class Lamp implements Appliance {
        @Override
        public void on() {
            System.out.println("Lamp is on");
        }
        @Override
        public void off() {
            System.out.println("Lamp is off");
        }
    }
    
  2. 电视机 Television:支持开关,调节音量。

    public class Television implements Appliance {
        @Override
        public void on() {
            System.out.println("Television is on");
        }
        @Override
        public void off() {
            System.out.println("Television is off");
        }
    
        // 自定义方法
        public void setVolume(int volume) {
            System.out.println("Television is at " + volume + " db");
        }
    }
    

命令 - Command

持有一个接收者的引用(抽象类型),

定义 execute() 方法用于执行请求。

public abstract class Command {
    protected Appliance appliance;

    public Command(Appliance appliance) {
        this.appliance = appliance;
    }

    public abstract void execute();
}

具体命令

LampXxx

Lamp 只有开关功能(属于接口定义的方法)

可直接通过抽象类型引用。

// 打开电灯
public class LampOnCommand extends Command {
    public LampOnCommand(Appliance appliance) {
        super(appliance);
    }
    @Override
    public void execute() {
        appliance.on();	// 开启
    }
}
// 关闭电灯
public class LampOffCommand extends Command {
    public LampOffCommand(Appliance appliance) {
        super(appliance);
    }
    @Override
    public void execute() {
        appliance.off();	//关闭
    }
}

TelevisionXxx

Television 有自定义功能,

需要判断并向下转型,才能正确执行。

// 打开电视
public class TelevisionOnCommand extends Command {
    public StereoMusicOnCommand(Appliance appliance) {
        super(appliance);
    }
    @Override
    public void execute() {
        if (appliance instanceof Television) {
            Television tv = (Television) this.appliance;
            Television.on();	// 开启
            Television.setVolume(10);	// 设置默认音量
        }
    }
}
// 关闭电视
public class TelevisionOffCommand extends Command {
    public StereoMusicOffCommand(Appliance appliance) {
        super(appliance);
    }
    @Override
    public void execute() {
        appliance.off();	// 关闭
    }
}

Dummy(❗)

无意义的 Command 对象,可避免 NPE

public class DummyCommand extends Command {
    @Override
    public void execute() {
        System.out.println("No Command");
    }
}

调用者 - Remote

假设有 4 对开关按钮。

  1. 成员变量

    1. 整型:开关的数量。
    2. 数组:对 Command 的引用,相同下标的元素代表同一对开关。
  2. 构造方法:初始化数组元素为空命令对象,避免 NPE

  3. setter:允许动态设置 Command 对象。

  4. 业务方法:代表按钮被按下。

    public class Remote {
        private int commandCount;
        private Command[] onCommandList;
        private Command[] offCommandList;
    
        public Remote() {
            // 默认初始化
            commandCount = 4;
            onCommandList = new Command[commandCount];
            offCommandList = new Command[commandCount];
            // 防止NPE
            NoCommand noCommand = new NoCommand();
            for (int i = 0; i < onCommandList.length; i++) {
                onCommandList[i] = noCommand;
                offCommandList[i] = noCommand;
            }
        }
        // 设置命令
        public void setCommand(int index,
                               Command onCommand,
                               Command offCommand) {
            onCommandList[index] = onCommand;
            offCommandList[index] = offCommand;
        }
    	// 业务方法
        public void onButtonPressed(int index) {
            onCommandList[index].execute();
        }
        public void offButtonPressed(int index) {
            offCommandList[index].execute();
        }
    }
    

客户端

基于 Receiver 创建 Command,作为参数完成 Invoker 的初始化。

// 接收者 - 家电
Lamp lamp = new Lamp();
Television tv = new Television();

// 具体命令
LampOnCommand lampOnCommand = new LampOnCommand(lamp);
LampOffCommand lampOffCommand = new LampOffCommand(lamp);
TelevisionOnCommand tvOnCommand = new TelevisionOnCommand(tv);
TelevisionOffCommand tvOffCommand = new TelevisionOffCommand(tv);


// 调用者:遥控器
Remote remote = new Remote();
remote.setCommand(0, lampOnCommand, lampOffCommand);
remote.setCommand(1, tvOnCommand, tvOffCommand);

// 发出请求
remote.onButtonPressed(0);	// Lamp is on
remote.onButtonPressed(1);	// Television is on
remote.onButtonPressed(2);	// No Command(而不是报错NPE)

2.3、宏命令

宏命令(Macro Command)

批量执行命令

  1. 继承 Command,作为命令对象。

  2. 成员变量:CommandList,代表需要批量执行的命令。

    public class MacroCommand extends Command {
        private Command[] commandList;
    
        public MacroCommand(Command[] commandList) {
            this.commandList = commandList;
        }
    
        @Override
        public void execute() {
            // 批量执行
            for (Command command : commandList) {
                command.execute();
            }
        }
    }
    

3、应用

说明

  1. 命令对象一旦创建完成,随时可以调用
  2. 可以在不同线程中调用命令对象,因此衍生一些应用场景。
    • 工作队列
    • 日程安排
    • 线程池

示例:工作队列 + 命令模式

将待执行任务定义成 Command 对象,提供 execute() 方法。

  1. 添加任务:往队尾添加一个 Command 对象。
  2. 执行任务:存在空闲线程时,在队头重复执行操作。
    1. 取出一个 Command 对象,调用 execute() 方法。
    2. 等待调用完成,丢弃该命令对象。
posted @ 2022-01-27 16:51  Jaywee  阅读(50)  评论(0编辑  收藏  举报

👇