设计模式06—命令模式

本文详细介绍了命令模式的原理与应用,通过遥控器控制家电设备的案例展示了如何解耦请求者与执行者。文章还探讨了如何在命令模式中添加撤销功能,以及如何实现宏命令以组合多个命令。最后,讨论了多层次撤销操作的实现以及命令模式在工作队列和日程调度中的应用。
摘要由CSDN通过智能技术生成

上一篇:《单件模式》

命令模式用于将方法调用封装起来
通过封装方法调用,我们可以把运算块包装成形。所以调用此运算的对象不需要关心事情是如何进行的,只要知道如何使用包装成形的方法来完成它就可以。通过封装方法调用,也可以做一些很聪明的事情,例如记录日志,或者重复使用这些封装来实现撒销(undo)。

实际应用场景

有一个遥控器可以控制家电自动化,但是这些家电不是来自于同一个厂商,可能是多个厂商,但是遥控器只有一个,可以允许遥控器可以有多个按键来控制不同的设备。
在这里插入图片描述

但是我们又不想通过一堆if else来识别和控制这些设备,于是我们引入命令模式,命令模式就是将动作的请求者从动作的执行者中进行解耦,具体做法如下:
把请求(比如开灯)封装成一个命令对象,所以,如果对每个按钮都存储一个命令对象,那么当按钮被按下的时候,就可以让命令对象做相关的工作。遥控器并不需要知道工作内容是什么,只要有个命令对象能和正确的对象沟通,把事情好计可以了。所以,遥控器和电灯对象解耦了。

第一个命令对象

实现命令接口

首先,让所有的命令对象实现相同的包含一个方法的接口。

public interface Command {
    public void execute();
}

接下来实现一个开灯的命令

Light 只有两个动作即开灯和关灯,所以

public class Light {
    public void on() {
        System.out.println("Light is on");
    }

    public void off() {
        System.out.println("Light is off");
    }
}

LightOnCommand:

public class LightOnCommand implements Command {
    Light light;

    /*构造器被传入了某个电灯(比方说客厅的电灯),
     * 以便让这个命令控制,然后记录在实例变量中,
     * 一旦调用execute()就由这个电灯对象成为接收者,负责接收请求*/
    public LightOnCommand(Light light) {
        this.light = light;
    }

    @Override
    public void execute() {
        light.on();
    }
}

使用命令对象

假设我们有一个遥控器,他只有一个按钮和对应的插槽,可以控制一个装置,那么对应的控制器如下:

public class SimpleRemoteControl {
    /*有一个插槽持有命令,而这个命令控制着一个装置*/
    Command slot;

    public SimpleRemoteControl() {
    }

    /*这个方法用来设插槽控制的命合。
    如果这段代码的客户想要改变遥控器控钮的行为.可以多次调用这个方法。
     */
    public void setCommand(Command command) {
        slot = command;
    }

    /*当控下控钮时,这个方法就会被调用,
    使得当前命令衔接插槽.并调用它的execute()方法。
     */
    public void buttonWasPressed() {
        slot.execute();
    }
}

接下来我们进行简单的测试:

public class SimpleRemoteControlTest {
    public static void main(String[] args) {
        /*遥控器就是一个调用者,会传入一个命令对象,可以用来发出请求*/
        SimpleRemoteControl remote = new SimpleRemoteControl();
        /*创建一个灯对象,此对象就是请求的接收者*/
        Light light = new Light();
        /*在这里创建一个命令,然后将接收者传给他*/
        LightOnCommand lightOn = new LightOnCommand(light);
        /*把命令传给调用者*/
        remote.setCommand(lightOn);
        /*按下模拟按钮*/
        remote.buttonWasPressed();

    }
}

运行结果:
在这里插入图片描述

为了继续让我们更加深入的了解这个这个模式,我们在遥控器上新增一个命令,打开车库大门的指令
GarageDoor:

public class GarageDoor {
    public GarageDoor() {
    }

    public void up() {
        System.out.println("Garage Door is Open");
    }

    public void down() {
        System.out.println("Garage Door is Closed");
    }

    public void stop() {
        System.out.println("Garage Door is Stopped");
    }

    public void lightOn() {
        System.out.println("Garage light is on");
    }

    public void lightOff() {
        System.out.println("Garage light is off");
    }
}

对应的开门指令如下:
GarageDoorOpenCommand:

public class GarageDoorOpenCommand implements Command {
    GarageDoor garageDoor;

    public GarageDoorOpenCommand(GarageDoor garageDoor) {

        this.garageDoor = garageDoor;
    }

    @Override
    public void execute() {
        garageDoor.up();
    }
}

这次连同开灯指令一起去做测试:

public class SimpleRemoteControlTest {
    public static void main(String[] args) {
        SimpleRemoteControl remoteControl = new SimpleRemoteControl();
        Light light = new Light();
        GarageDoor garageDoor = new GarageDoor();
        LightOnCommand lightOnCommand = new LightOnCommand(light);
        GarageDoorOpenCommand garageDoorOpenCommand = new GarageDoorOpenCommand(garageDoor);
        remoteControl.setCommand(lightOnCommand);
        remoteControl.buttonWasPressed();
        remoteControl.setCommand(garageDoorOpenCommand);
        remoteControl.buttonWasPressed();

    }
}

运行结果如下:
在这里插入图片描述

上面的程序就实现了我们先用一个“开灯”指令加载按钮槽,稍后又将命令替换成为另一个打开车库大门的指令,也就是遥控器插槽根本不在乎所拥有的是什么命令对象,只要这个指令实现了Command接口即可。

定义命令模式

命令模式将“请求”封装成对象,以便使用不同的请求队列或者日志来参数化其他对象。命令模式也支持可撒销的操作。
现在,仔细看这个定义。我们知道一个命令对象通过在特定接收者上绑定一组动作来封装一个请求。要达到这一点,命暴露出一个令对象将动作和接收者包进对象中。这个对象只有execute()方法,当此方法被调用的时候,接收者就会进行这些动作。从外面来看,其他对象不知道究竟哪个接收者进行了哪些动作,只知道如果调用execute()方法,请求的目的就能达到。

定义命令模式类图

在这里插入图片描述

将命令指定到插槽

就像我们在简单遥控器(SimpleRemote)中所做的一样,我们需要提供一个方法,将命令指定到插槽。实际上,我们有7个插槽,每个插槽都具备了“开”和“关”按钮,所以我们可以用类似方式,把命令指定给遥控器,像这样:

onCommands[0] =onCommand;
offCommands [0]=offCommand;

但是遥控器具体如何分辨是卧室的灯还是客厅的灯呢,因为遥控器在按下之后只知道执行了excute()方法。
我们的计划是这样的:我们打算将遥控器的每个插槽,对应到一个命令这样就让遥控器变成“调用者”。当按下按钮,相应命令对象的execute()方法就会被调用,其结果就是,接收者(例如:电灯、天花板电扇、音响)的动作被调用。
在这里插入图片描述

实现遥控器

NoCommand

/**
 * 无任何指令
 */
public class NoCommand implements Command {
    @Override
    public void execute() {

    }
}

public class RemoteControl {
    /*这个时候需要处理7个开与关的命令,
    所以使用相应的数组记录这些命令*/
    Command[] onCommands;
    Command[] offCommands;

    private final Integer commandNums = 7;

    /**
     * 构造器
     */
    public RemoteControl() {
        onCommands = new Command[commandNums];
        offCommands = new Command[commandNums];
        Command noCommand = new NoCommand();
        /*在构造器中实例化并初始化这 两个开关数组 */
        for (int i = 0; i < commandNums; i++) {
            onCommands[i] = noCommand;
            offCommands[i] = noCommand;
        }
    }

    /*setCommand()方法须有3个参数,分别是括槽的位置、开的命令、关的命令。
    这些命令将记录在关数组中对应的插槽位置,以供稍后使用。
     */
    public void setCommand(int solt, Command onCommand, Command offCommand) {
        onCommands[solt] = onCommand;
        offCommands[solt] = offCommand;

    }

    /*当按下开或关按钮,硬件就会负责调用相应的方法*/
    public void onButtonWasPushed(int solt) {
        onCommands[solt].execute();
    }

    public void offButtonWasPushed(int solt) {
        offCommands[solt].execute();
    }

    /**
     * 覆盖toString(),打印出每个插槽和它对应的命合。稍后在测试遥控器的时候,会用到这个方法。
     *
     * @return
     */
    public String toString() {
        StringBuffer stringBuff = new StringBuffer();
        stringBuff.append("\n------ Remote Control -------\n");
        for (int i = 0; i < onCommands.length; i++) {
            stringBuff.append("[slot " + i + "] " + onCommands[i].getClass().getName()
                    + "    " + offCommands[i].getClass().getName() + "\n");
        }
        return stringBuff.toString();
    }
}

接下来我们继续实现音响的控制命令
音响(Stereo):

public class Stereo {
    private String location;//音响位置

    public Stereo(String location) {
        this.location = location;
    }

    public void on() {//开
        System.out.println(location + " stereo is on");
    }

    public void off() {//关
        System.out.println(location + " stereo is off");
    }

    public void setCD() {//设置播放的音乐为CD输入源
        System.out.println(location + " stereo is set for CD input");
    }

    public void setDVD() {//设置播放的音乐为DVD输入源
        System.out.println(location + " stereo is set for DVD input");
    }

    public void setRadio() {//设置立体声
        System.out.println(location + " stereo is set for Radio");
    }

    public void setVolume(int volume) {//设置音量
        // code to set the volume
        // valid range: 1-11 (after all 11 is better than 10, right?)
        System.out.println(" stereo volume set to " + volume);
    }
}

设置音响指令

public class StereoOnWithCDCommand implements Command {
    Stereo stereo;

    /**
     * 传入音响的实例,然后将其存储在局部变量实例中
     *
     * @param stereo
     */
    public StereoOnWithCDCommand(Stereo stereo) {
        this.stereo = stereo;
    }

    @Override
    public void execute() {
        stereo.on();
        stereo.setCD();
        stereo.setVolume(11);
    }
}

public class StereoOffCommand implements Command {
    Stereo stereo;

    public StereoOffCommand(Stereo stereo) {
        this.stereo = stereo;
    }

    @Override
    public void execute() {
        stereo.off();
    }
}

吊扇:

/**
 * 吊扇
 */
public class CeilingFan {
    String location = "";
    int level;//档位
    public static final int HIGH = 2;//高档位
    public static final int MEDIUM = 1;//低档位
    public static final int LOW = 0;//关闭

    public CeilingFan(String location) {
        this.location = location;
    }

    public void high() {
        // turns the ceiling fan on to high
        level = HIGH;
        System.out.println(location + " ceiling fan is on high");

    }

    public void medium() {
        // turns the ceiling fan on to medium
        level = MEDIUM;
        System.out.println(location + " ceiling fan is on medium");
    }

    public void low() {
        // turns the ceiling fan on to low
        level = LOW;
        System.out.println(location + " ceiling fan is on low");
    }

    public void off() {
        // turns the ceiling fan off
        level = 0;
        System.out.println(location + " ceiling fan is off");
    }

    public int getSpeed() {
        return level;
    }
}

吊扇的指令:

public class CeilingFanOnCommand implements Command {
    CeilingFan ceilingFan;

    public CeilingFanOnCommand(CeilingFan ceilingFan) {
        this.ceilingFan = ceilingFan;
    }

    @Override
    public void execute() {
        ceilingFan.high();
    }
}

public class CeilingFanOffCommand implements Command {
    CeilingFan ceilingFan;

    public CeilingFanOffCommand(CeilingFan ceilingFan) {
        this.ceilingFan = ceilingFan;
    }

    @Override
    public void execute() {
        ceilingFan.off();
    }
}

对Light类的修改如下:

public class Light {
    /*灯的位置:比如卧室的灯,客厅的灯*/
    private String location;

    public Light(String location) {
        this.location = location;
    }

    public void on() {
        System.out.println(location + " Light is on");
    }

    public void off() {
        System.out.println(location + " Light is off");
    }
}

关灯指令:

public class LightOffCommand implements Command {
    Light light;

    public LightOffCommand(Light light) {
        this.light = light;
    }

    @Override
    public void execute() {
        light.off();
    }
}

GarageDoor:

public class GarageDoor {
    private String location;

    public GarageDoor() {
    }

    public GarageDoor(String location) {
        this.location = location;
    }

    public void up() {
        System.out.println(location + " Garage Door is Open");
    }

    public void down() {
        System.out.println(location + " Garage Door is Closed");
    }

    public void stop() {
        System.out.println(location + " Garage Door is Stopped");
    }

    public void lightOn() {
        System.out.println(location + " Garage light is on");
    }

    public void lightOff() {
        System.out.println(location + " Garage light is off");
    }
}

关门指令:

public class GarageDoorDownCommand implements Command {
    GarageDoor garageDoor;

    public GarageDoorDownCommand(GarageDoor garageDoor) {
        this.garageDoor = garageDoor;
    }

    @Override
    public void execute() {
        garageDoor.down();
    }
}

接下来开始测试遥控器功能

public class RemoteLoader {
    public static void main(String[] args) {
        /*声明遥控对象*/
        Light livingRoomLight = new Light("卧室");
        Light kitchenLight = new Light("厨房");
        CeilingFan ceilingFan = new CeilingFan("卧室");
        GarageDoor garageDoor = new GarageDoor("");
        Stereo stereo = new Stereo("客厅");
        /*创建电灯指令对象*/
        LightOnCommand livingRoomLightOn = new LightOnCommand(livingRoomLight);
        LightOffCommand livingRoomLightOff = new LightOffCommand(livingRoomLight);
        LightOnCommand kitchenLightOn = new LightOnCommand(kitchenLight);
        LightOffCommand kitchenLightOff = new LightOffCommand(kitchenLight);
        /*创建吊扇的开与关指令*/
        CeilingFanOnCommand ceilingFanOn = new CeilingFanOnCommand(ceilingFan);
        CeilingFanOffCommand ceilingFanOff = new CeilingFanOffCommand(ceilingFan);
        /*创建车库的开与关指令*/
        GarageDoorOpenCommand garageDoorOpen = new GarageDoorOpenCommand(garageDoor);
        GarageDoorDownCommand garageDoorDown = new GarageDoorDownCommand(garageDoor);
        /*创建音响的开与关指令*/
        StereoOnWithCDCommand stereoOpen = new StereoOnWithCDCommand(stereo);
        StereoOffCommand stereoOff = new StereoOffCommand(stereo);
        /**现在已经有了全部的命令,接下来将命令安装到遥控器的插槽中*/
        RemoteControl remoteControl = new RemoteControl();//声明一个遥控器对象
        remoteControl.setCommand(0, livingRoomLightOn, livingRoomLightOff);
        remoteControl.setCommand(1, kitchenLightOn, kitchenLightOff);
        remoteControl.setCommand(2, ceilingFanOn, ceilingFanOff);
        remoteControl.setCommand(3, garageDoorOpen, garageDoorDown);
        remoteControl.setCommand(4, stereoOpen, stereoOff);
        System.out.println(remoteControl.toString());

        /*一切准备就绪接下来开始逐个点击遥控器按钮*/
        remoteControl.onButtonWasPushed(0);
        remoteControl.offButtonWasPushed(0);
        remoteControl.onButtonWasPushed(1);
        remoteControl.offButtonWasPushed(1);
        remoteControl.onButtonWasPushed(2);
        remoteControl.offButtonWasPushed(2);
        remoteControl.onButtonWasPushed(3);
        remoteControl.offButtonWasPushed(3);
        remoteControl.onButtonWasPushed(4);
        remoteControl.offButtonWasPushed(4);
    }
}

运行结果:
在这里插入图片描述

写到此处我们可能忽略了我们使用的NoCommand
在这里插入图片描述

NoCommand对象是一个空对象(null object)的例子。
当你不想返回一个有意义的对象时,空对象就很有用。客户也可以将处理null的责任转移给空对象。
举例来说,遥控器不可能一出厂就设置了有意义的命令对象,所以提供了NoCommand对象作为代用品,当调用它的execute()方法时,这种对象什么事情都不做。
在许多设计模式中,都会看到空对象的使用。甚至有些时候,空对象本身也被视为是一种设计模模式
下面便是我们刚刚实现一系列命令的流程:
在这里插入图片描述

撤销功能

我们现在需要在遥控器上加上撤销的功能。
这个功能使用起来就像是这样的:比方说客厅的电灯是关闭的,然后你按下遥控器上的开启按钮,自然电灯就被打开了。现在如果按下撤销按钮,那么上一个动作将被倒转,在这个例子里,电灯将被关闭。
在进入更复杂的例子之前,先让撤销按钮能够处理电灯:

  • 1.当命令支持撤销时,该命令就必须提供和execute()方法相反的undo()方法。不管execute()刚才做什么,undo()都会倒转过来。在各个命令加入undo之前,我们必须先在Command接口中加入undo()方法:
public interface Command {
    public void execute();

    public void undo();
}

  • 2.我们从LightOnCommand开始下手:如果LightOnCommand的execute(方法被调用,那么最后被调用的是on(方法。我们知道undo()需要调用off()方法进行相反的动作。
public class LightOnCommand implements Command {
    Light light;

    public LightOnCommand(Light light) {
        this.light = light;
    }

    @Override
    public void execute() {
        light.on();
    }

    /**
     * execute执行的是打开灯,所以undo应该执行的是关闭灯
     */
    @Override
    public void undo() {
        light.off();
    }
}

接下来继续处理LightOffCommand

public class LightOffCommand implements Command {
    Light light;

    public LightOffCommand(Light light) {
        this.light = light;
    }

    @Override
    public void execute() {
        light.off();
    }

    @Override
    public void undo() {
        light.on();
    }
}

到此事情并没有结束。因为我们还需要让遥控器能够追踪最后被按下的按钮是什么

  • 3.要加上对撤销按钮的支持,我们必须对遥控器类做些小修改。我们打算这么做:加入一个新的实例变量,用来追踪最后被调用的命令,然后,不管何时撒销按钮被按下,我们都可以取出这个命令并调用它的undo()方法。
public class RemoteControlWithUndo {
    Command[] onCommands;
    Command[] offCommands;
    Command undoCommand;//用于记录上一个操作的命令

    private final Integer commandNums = 7;

    public RemoteControlWithUndo() {
        onCommands = new Command[commandNums];
        offCommands = new Command[commandNums];
        Command noCommand = new NoCommand();
        for (int i = 0; i < commandNums; i++) {
            onCommands[i] = noCommand;
            offCommands[i] = noCommand;
        }
        undoCommand = noCommand;//一开始,并没有任何前一个命令,所以设置NoCommand
    }

    public void setCommand(int slot, Command onCommand, Command offCommand) {
        onCommands[slot] = onCommand;
        offCommands[slot] = offCommand;
    }

    /**
     * 当控下控钮,我们取得这个命令.并优先执行它,然后将它记录在undoCommand实例变量中。
     * 不管是是“开”或“关”命合,我们的处理方法都是一样的。
     *
     * @param solt
     */
    public void onButtonWasPushed(int solt) {
        onCommands[solt].execute();
        undoCommand = onCommands[solt];
    }

    public void offButtonWasPushed(int solt) {
        offCommands[solt].execute();
        undoCommand = offCommands[solt];
    }

    /**
     * 当控下撤销按钮时﹒我们调用undoCoamand实例变量的undo()方法.就可以倒转前一个命令。
     *
     * @param solt
     */
    public void undoButtonWasPushed(int solt) {
        undoCommand.undo();
    }
}

接下来我们进行测试

public class RemoteLoader {
    public static void main(String[] args) {

        Light livingRoomLight = new Light("卧室");//声明电灯实例
        /**创建开和关两个电灯指令**/
        LightOnCommand livingRoomLightOn = new LightOnCommand(livingRoomLight);
        LightOffCommand livingRoomLightOff = new LightOffCommand(livingRoomLight);
        RemoteControlWithUndo remoteControl = new RemoteControlWithUndo();//声明一个遥控器
        remoteControl.setCommand(0, livingRoomLightOn,
                livingRoomLightOff);//将电灯的命令设置到0号插槽
        /**下面依次执行开灯,关灯,撤销按钮**/
        System.out.println("----------------开灯,关灯,撤销----------------");
        remoteControl.onButtonWasPushed(0);
        remoteControl.offButtonWasPushed(0);
        remoteControl.undoButtonWasPushed(0);
        /**下面依次执行关灯,开灯,撤销按钮**/
        System.out.println("----------------关灯,开灯,撤销----------------");
        remoteControl.offButtonWasPushed(0);
        remoteControl.onButtonWasPushed(0);
        remoteControl.undoButtonWasPushed(0);
    }
}

运行结果:
在这里插入图片描述

使用状态的撤销

接下来我们实现一个更有趣的例子,比方说厂商类中的天花板的吊扇,吊扇允许有多种转速,当然也允许被关闭。
吊扇代码:

public class CeilingFan {
    /**
     * 档位的转速
     **/
    public static final Integer HIGH = 3;
    public static final Integer MEDIUM = 2;
    public static final Integer LOW = 1;
    public static final Integer OFF = 0;
    private String location;//风扇的位置(比如卧室,客厅等)
    private Integer speed;//风扇当前的转速

    public CeilingFan(String location) {
        this.location = location;
        speed = OFF;
    }

    /**
     * 设置高转速
     */
    public void setHigh() {
        speed = HIGH;
        System.out.println(location + " 风扇档位调节至高速");
    }

    /**
     * 设置中等转速
     */
    public void setMedium() {
        speed = MEDIUM;
        System.out.println(location + " 风扇档位调节至中速");
    }

    /**
     * 设置低转速
     */
    public void setLow() {
        speed = HIGH;
        System.out.println(location + " 风扇档位调节至低速");
    }

    /**
     * 关闭风扇
     */
    public void setOff() {
        speed = OFF;
        System.out.println(location + " 风扇关闭");
    }

    /**
     * 获取当前转速
     *
     * @return
     */
    public Integer getSpeed() {
        System.out.println(location + " 风扇转速为:" + speed);
        return this.speed;
    }
}

吊扇指令

/**
 * 高速指令
 */
public class CeilingFanHighCommand implements Command {
    CeilingFan ceilingFan;
    int preSpeed;//增加局部状态以便追踪吊扇之前的状态

    public CeilingFanHighCommand(CeilingFan ceilingFan) {
        this.ceilingFan = ceilingFan;
    }

    @Override
    public void execute() {
        /*在execute()中,
        在我们改变吊扇的速度之前,需先将它之前的状态记录起来,以便需要撤销时使用。*/
        preSpeed = ceilingFan.getSpeed();
        ceilingFan.setHigh();
    }

    /**
     * 将吊扇的速度设置回之前的值达到撤销的目的
     */
    @Override
    public void undo() {
        if (preSpeed == CeilingFan.HIGH) {
            ceilingFan.setHigh();
        } else if (preSpeed == CeilingFan.MEDIUM) {
            ceilingFan.setMedium();
        } else if (preSpeed == CeilingFan.LOW) {
            ceilingFan.setLow();
        } else if (preSpeed == CeilingFan.OFF) {
            ceilingFan.setOff();
        }

    }
}

对于中速,低速,关闭的命令我们只需要仿照着高速的指令然后修改excute()方法中的风扇速度即可,修改方式如下:
在这里插入图片描述

接下来我们开始测试:

public class RemoteLoader_CeilingFan {
    public static void main(String[] args) {
        /*创建风扇对象*/
        CeilingFan ceilingFan = new CeilingFan("卧室");
        /**创建风扇的具体命令**/
        CeilingFanHighCommand ceilingFanHigh = new CeilingFanHighCommand(ceilingFan);
        CeilingFanMediumCommand ceilingFanMedium = new CeilingFanMediumCommand(ceilingFan);
        CeilingFanOffCommand ceilingFanOff = new CeilingFanOffCommand(ceilingFan);
        /*创建遥控器*/
        RemoteControlWithUndo remoteControl = new RemoteControlWithUndo();
        /**将指令放入遥控器的插槽**/
        remoteControl.setCommand(0, ceilingFanMedium, ceilingFanOff);
        remoteControl.setCommand(1, ceilingFanHigh, ceilingFanOff);
        /**依次点击遥控器按钮**/
        System.out.println("---------------中速开,关闭,回退---------------");
        remoteControl.onButtonWasPushed(0);
        remoteControl.offButtonWasPushed(0);
        remoteControl.undoButtonWasPushed();
        System.out.println("---------------高速开,回退---------------");
        remoteControl.onButtonWasPushed(1);
        remoteControl.undoButtonWasPushed();

    }
}

运行结果:
在这里插入图片描述

宏命令

我们接下来希望拥有一个遥控器,可以实现按下一个按钮,就同时能弄暗灯光、打开音响和电视、设置好DVD,并让热水器开始加温
我们首先最容易想到的就是制造一种新的命令用来执行其他一堆命令

public class MacroCommand implements Command {
    Command[] commands;

    /**
     * 在宏命令中,用命令数组存储一大堆命令
     *
     * @param commands
     */
    public MacroCommand(Command[] commands) {
        this.commands = commands;
    }

    /**
     * 当这个宏命令被遥控器执行时,就一次性执行数组里的每一个命令
     */
    @Override
    public void execute() {
        for (int i = 0; i < commands.length; i++) {
            commands[i].execute();
        }

    }

    @Override
    public void undo() {

    }
}

使用宏命令

  • TV
/**
 * 电视
 */
public class TV {
    String location;//电视位置
    int channel;//电视播放的频道

    public TV(String location) {
        this.location = location;
    }

    /**
     * 开电视
     */
    public void on() {
        System.out.println(location + " 电视已打开");
    }

    public void off() {
        System.out.println(location + " 电视已关闭");
    }

    /**
     * 设置输入源
     */
    public void setInputChannel() {
        this.channel = 3;
        System.out.println(location + " 电视频道输入源设置为DVD");
    }
}

public class TVOnCommand implements Command {
    TV tv;

    public TVOnCommand(TV tv) {
        this.tv = tv;
    }

    @Override
    public void execute() {
        tv.on();
        tv.setInputChannel();
    }

    @Override
    public void undo() {
        tv.off();
    }
}

public class TVOffCommand implements Command {
    TV tv;

    public TVOffCommand(TV tv) {
        this.tv = tv;
    }

    @Override
    public void execute() {
        tv.off();
    }

    @Override
    public void undo() {
        tv.on();
    }
}

  • Hot Tub
/**
 * 热水浴缸
 */
public class Hottub {
    boolean on;//开启标识
    int temperature;//当前浴缸的温度

    public Hottub() {
    }

    public void on() {
        on = true;
    }

    public void off() {
        on = false;
    }

    /**
     * 开始循环加热
     */
    public void circulate() {
        if (on) {
            System.out.println("浴缸加热完成!");
        }
    }

    /**
     * 注水
     */
    public void jetsOn() {
        if (on) {//保证浴缸是在启动状态
            System.out.println("正在注水");
        }
    }

    /**
     * 停止注水
     */
    public void jetsOff() {
        if (on) {
            System.out.println("浴缸注水结束");
        }
    }

    /**
     * 设置控制温度
     *
     * @param temperature
     */
    public void setTemperature(int temperature) {
        if (temperature > this.temperature) {
            System.out.println("热水器正在加热到蒸汽的温度 " + temperature + " degrees");
        } else {
            System.out.println("热水盆正在冷却  " + temperature + " degrees");
        }
        this.temperature = temperature;
    }
}

public class HottubOnCommand implements Command {
    Hottub hottub;

    public HottubOnCommand(Hottub hottub) {
        this.hottub = hottub;
    }

    @Override
    public void execute() {
        hottub.on();
        hottub.setTemperature(104);
        hottub.jetsOn();
        hottub.jetsOff();
        hottub.circulate();
    }

    @Override
    public void undo() {
        hottub.off();
    }
}

public class HottubOffCommand implements Command {
    Hottub hottub;

    public HottubOffCommand(Hottub hottub) {
        this.hottub = hottub;
    }

    @Override
    public void execute() {
        hottub.setTemperature(90);
        hottub.off();
    }

    @Override
    public void undo() {
        hottub.on();
    }
}

接下来开始测试:
步骤如下:

  • 1.创建想要进入宏的命令集合
    在这里插入图片描述

  • 2.接下来创建两个数组,其中一个用来记录开启命令,另一个用来记录关闭命令,并在数组内放入对应的命令
    在这里插入图片描述

  • 3.然后将宏命令指定给我们所希望的按钮:
    在这里插入图片描述

  • 4.最后,只需按下一.些按钮,测试是否正常工作
    在这里插入图片描述

具体代码如下:

public class RemoteLoader {
    public static void main(String[] args) {
        /**创建命令接收对象**/
        Light light = new Light("卧室");
        TV tv = new TV("卧室");
        Stereo stereo = new Stereo("卧室");
        Hottub hottub = new Hottub();
        /**创建具体的命令**/
        LightOnCommand lightOn = new LightOnCommand(light);
        StereoOnCommand stereoOn = new StereoOnCommand(stereo);
        TVOnCommand tvOn = new TVOnCommand(tv);
        HottubOnCommand hottubOn = new HottubOnCommand(hottub);
        LightOffCommand lightOff = new LightOffCommand(light);
        StereoOffCommand stereoOff = new StereoOffCommand(stereo);
        TVOffCommand tvOff = new TVOffCommand(tv);
        HottubOffCommand hottubOff = new HottubOffCommand(hottub);
        /**接下来创建两个数组,其中一个用来记录开启命令,
         * 另一个用来记录关闭命令,并在数组内放入对应的命令**/
        Command[] partyOn = {lightOn, stereoOn, tvOn, hottubOn};
        Command[] partyOff = {lightOff, stereoOff, tvOff, hottubOff};
        /**将指令数组放在命令的宏定义中**/
        MacroCommand partyOnMacro = new MacroCommand(partyOn);
        MacroCommand partyOffMacro = new MacroCommand(partyOff);
        /*创建遥控器*/
        RemoteControl remoteControl = new RemoteControl();
        /**将指令放在遥控器的卡槽**/
        remoteControl.setCommand(0, partyOnMacro, partyOffMacro);
        System.out.println("---------------------启动---------------------");
        remoteControl.onButtonWasPushed(0);
        System.out.println("---------------------关闭---------------------");
        remoteControl.offButtonWasPushed(0);
    }
}

运行结果:
在这里插入图片描述

如何能够实现多层次的撤销操作?换句话说,我希望能够按下撤销按钮许多次,撤销到很早很早以前的状态。

答:其实这相当容易做到,不要只是记录最后一个被执行的命令,而使用一个堆栈记录操作过程的每一个命令,然后,不管什么时候按下了撖销按钮,你都可以从堆栈中取出最上层的命令,然后调用它的undo()方法。

命令模式的更多请求

命令可以将运算块打包(一个接收者和一组动作),然后将它传来传去,就像是一般的对象一样。现在,即使在命令对象被创建许久之后,运算依然可以被调用。事实上,它甚至可以在不同的线程中被调用。我们可以利用这样的特性衍生一些应用,例如:日程安排(Scheduler) 、线程池、工作队列等。想象有一个工作队列:你在某一端添加命令,然后另端则是线程。线程进行下面的动作:从队列中取出一个命令,调用它的execute()方法,等待这个调用完成,然后将此命令对象丢弃,再取出下一个命令。

请注意,工作队列类和进行计算的对象之间完全是解耦的。
此刻线程可能在进行财务运算,下一.刻却在读取网络数据。工作队列
对象不在乎到底做些什么,它们只知道取出命令对象,然后调用其execute()方法。类似地,它们只要是实现命令模式的对象,就可以放入队列里,当线程可用时,就调用此对象的execute()方法。

下一篇:《适配器模式与外观模式》

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

ZNineSun

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值