设计模式之命令模式(二)

 

上一次留给大家去做的实践,不知道大家执行的怎么样了呢。

我们通过一个简单的练习,完成了一个控制开关。那现在,我们打算将遥控器的每个插槽,对应到一个命令这样就要遥控器变成“调用者”。当按下按钮,相应命令对象的execute()方法就会被调用,其结果就是,接收者(例如电灯、风扇、音响)的动作被调用。

实现遥控器

public class RemoteControl {    Command[] onCommands;    Command[] offCommands;    public RemoteControl() {        onCommands = new Command[7];        offCommands = new Command[7];// 在构造器中,只需实例化并初始化这两个开与关的数组        Command noCommand = new NoCommand();        for (int i = 0; i < 7; i++) {            onCommands[i] = noCommand;            offCommands[i] = noCommand;        }    }// 这个方法有三个参数,分别是插槽的位置、开的命令、关的命令。这些命令将记录开关数组中对应的插槽位置,以供稍后使用    public void setCommand(int slot, Command onCommand, Command offCommand) {        onCommands[slot] = onCommand;        offCommands[slot] = offCommand;    }// 当按下开或关的按钮,硬件就会负责调用对应的方法,也就是onButtonWasPushed或offButtonWasPushed    public void onButtonWasPushed(int slot) {        onCommands[slot].execute();    }    public void offButtonWasPushed(int slot) {        offCommands[slot].execute();    }    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();    }}class RemoteControl {
    Command[] onCommands;
    Command[] offCommands;


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

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

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

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

    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();
    }
}

实现命令

此前我们已经动手实现过LightOnCommand,纯粹就是简单的开和关命令。那现在,我们来为音响编写开与关的命令。

音响的关闭是毫无难度,就是开启的时候有点复杂,你知道为什么吗?难道音响开了就好了?是否还需要后续其他的动作才能让音响响起来了?哎呀,小编多嘴了好像。

public class StereoOnWithCDCommand implements Command {    Stereo stereo;    public StereoOnWithCDCommand(Stereo stereo) {        this.stereo = stereo;    }// 打开音响,需要三个步骤,开启音响,设置CD播放,设置音量,不然就成哑巴了    public void execute() {        stereo.on();        stereo.setCD();        stereo.setVolume(11);    }}class StereoOnWithCDCommand implements Command {
    Stereo stereo;

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

// 打开音响,需要三个步骤,开启音响,设置CD播放,设置音量,不然就成哑巴了
    public void execute() {
        stereo.on();
        stereo.setCD();
        stereo.setVolume(11);
    }
}

这里列举了一个电灯,一个音响,差不多就把其他类似的都已经搞定了,比如电扇、门,对吧。所以,赶紧看看你之前动手的操作,是不是和小编的差不多。

让我们继续看下,多个的是怎么实现的呢。

public class RemoteLoader {    public static void main(String[] args) {        RemoteControl remoteControl = new RemoteControl();// 将所有的装置创建在合适的位置        Light livingRoomLight = new Light("Living Room");        Light kitchenLight = new Light("Kitchen");        CeilingFan ceilingFan= new CeilingFan("Living Room");        GarageDoor garageDoor = new GarageDoor("");        Stereo stereo = new Stereo("Living Room");// 创建所有的电灯命令对象        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);// 创建车库门的上与下命令        GarageDoorUpCommand garageDoorUp =                new GarageDoorUpCommand(garageDoor);        GarageDoorDownCommand garageDoorDown =                new GarageDoorDownCommand(garageDoor);// 创建音响的开与关命令        StereoOnWithCDCommand stereoOnWithCD =                new StereoOnWithCDCommand(stereo);        StereoOffCommand  stereoOff =                new StereoOffCommand(stereo);// 现在已经有了全部的命令,我们将它们加载到遥控器插槽中        remoteControl.setCommand(0, livingRoomLightOn, livingRoomLightOff);        remoteControl.setCommand(1, kitchenLightOn, kitchenLightOff);        remoteControl.setCommand(2, ceilingFanOn, ceilingFanOff);        remoteControl.setCommand(3, stereoOnWithCD, stereoOff);        System.out.println(remoteControl);// 在这里逐步按下每个插槽的开与关按钮        remoteControl.onButtonWasPushed(0);        remoteControl.offButtonWasPushed(0);        remoteControl.onButtonWasPushed(1);        remoteControl.offButtonWasPushed(1);        remoteControl.onButtonWasPushed(2);        remoteControl.offButtonWasPushed(2);        remoteControl.onButtonWasPushed(3);        remoteControl.offButtonWasPushed(3);    }}class RemoteLoader {

    public static void main(String[] args) {
        RemoteControl remoteControl = new RemoteControl();
// 将所有的装置创建在合适的位置
        Light livingRoomLight = new Light("Living Room");
        Light kitchenLight = new Light("Kitchen");
        CeilingFan ceilingFan= new CeilingFan("Living Room");
        GarageDoor garageDoor = new GarageDoor("");
        Stereo stereo = new Stereo("Living Room");

// 创建所有的电灯命令对象
        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);

// 创建车库门的上与下命令
        GarageDoorUpCommand garageDoorUp =
                new GarageDoorUpCommand(garageDoor);
        GarageDoorDownCommand garageDoorDown =
                new GarageDoorDownCommand(garageDoor);

// 创建音响的开与关命令
        StereoOnWithCDCommand stereoOnWithCD =
                new StereoOnWithCDCommand(stereo);
        StereoOffCommand  stereoOff =
                new StereoOffCommand(stereo);

// 现在已经有了全部的命令,我们将它们加载到遥控器插槽中
        remoteControl.setCommand(0, livingRoomLightOn, livingRoomLightOff);
        remoteControl.setCommand(1, kitchenLightOn, kitchenLightOff);
        remoteControl.setCommand(2, ceilingFanOn, ceilingFanOff);
        remoteControl.setCommand(3, stereoOnWithCD, stereoOff);

        System.out.println(remoteControl);

// 在这里逐步按下每个插槽的开与关按钮
        remoteControl.onButtonWasPushed(0);
        remoteControl.offButtonWasPushed(0);
        remoteControl.onButtonWasPushed(1);
        remoteControl.offButtonWasPushed(1);
        remoteControl.onButtonWasPushed(2);
        remoteControl.offButtonWasPushed(2);
        remoteControl.onButtonWasPushed(3);
        remoteControl.offButtonWasPushed(3);
    }
}

写文档的时候到了

我们这个主要的设计目标就是让遥控器代码尽可能地简单,这样一来,新的厂商类一旦出现,遥控器并不需要随之修改。因为,我们才用了命令模式,从逻辑上将遥控器的类和厂商的类解耦。我们相信这将降低遥控器的生产成本,并大大地减少维护时所需的费用。

下面的类图提供了设计的全貌:

640?wx_fmt=jpeg

撤销哪去了?

别急别急,小编说的功能都会有的。撤销功能使用起来就是这样的:比如说客厅的电灯是关闭的,然后你按下遥控器上的开启按钮,自然电灯就被打开了。现在如果按下撤销按钮,那么上一个动作将被倒转,在这个例子里,电灯将被关闭。

同样,我们先来一个简单的撤销示例。之前我们用的是execute()方法实现开启或者关闭的调用,那么我们用undo()方法来执行撤销操作。即在Command接口里实现一个同execute()相反的方法undo(),然后在实现类里将undo()的动作做成和execute()相反的操作即可。

讲的有点笼统?在这里小编就不提供具体的代码了,详细的请看GitHub我的分享吧。

使用状态实现撤销

因为电灯这个开关已经撤销,是很简单的入门,小编没有提供源码在文中,但是因为还有电风扇这个存在,小编还不得不继续搞一个高大上的方式。电扇不仅仅是开关,还有档位的存在,对吧,是不是瞬间有思路了呢?

public class CeilingFan {    public static final int HIGH = 3;    public static final int MEDIUM = 2;    public static final int LOW = 1;    public static final int OFF = 0;    String location;    int speed;    public CeilingFan(String location) {        this.location = location;        speed = OFF;    }    public void high() {        speed = HIGH;        System.out.println(location + " ceiling fan is on high");    }     public void medium() {        speed = MEDIUM;        System.out.println(location + " ceiling fan is on medium");    }    public void low() {        speed = LOW;        System.out.println(location + " ceiling fan is on low");    }    public void off() {        speed = OFF;        System.out.println(location + " ceiling fan is off");    }    public int getSpeed() {        return speed;    }}class CeilingFan {
    public static final int HIGH = 3;
    public static final int MEDIUM = 2;
    public static final int LOW = 1;
    public static final int OFF = 0;
    String location;
    int speed;

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

    public void high() {
        speed = HIGH;
        System.out.println(location + " ceiling fan is on high");
    } 

    public void medium() {
        speed = MEDIUM;
        System.out.println(location + " ceiling fan is on medium");
    }

    public void low() {
        speed = LOW;
        System.out.println(location + " ceiling fan is on low");
    }

    public void off() {
        speed = OFF;
        System.out.println(location + " ceiling fan is off");
    }

    public int getSpeed() {
        return speed;
    }
}

现在我们就来实现风扇的撤销。这么做,需要追踪吊扇的最后设置速度,如果undo方法被调用了,就要恢复成之前吊扇速度的设置值。就如下面这样:

public class CeilingFanHighCommand implements Command {    CeilingFan ceilingFan;// 增加局部状态以便追踪吊扇之前的速度    int prevSpeed;    public CeilingFanHighCommand(CeilingFan ceilingFan) {        this.ceilingFan = ceilingFan;    }    public void execute() {// 我们改变吊扇的速度之前,需要先将它之前的状态记录起来,以便需要撤销时使用        prevSpeed = ceilingFan.getSpeed();        ceilingFan.high();    }// 将吊扇的速度设置会之前的值,达到撤销的目的    public void undo() {        if (prevSpeed == CeilingFan.HIGH) {            ceilingFan.high();        } else if (prevSpeed == CeilingFan.MEDIUM) {            ceilingFan.medium();        } else if (prevSpeed == CeilingFan.LOW) {            ceilingFan.low();        } else if (prevSpeed == CeilingFan.OFF) {            ceilingFan.off();        }    }}class CeilingFanHighCommand implements Command {
    CeilingFan ceilingFan;
// 增加局部状态以便追踪吊扇之前的速度
    int prevSpeed;

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

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

// 将吊扇的速度设置会之前的值,达到撤销的目的
    public void undo() {
        if (prevSpeed == CeilingFan.HIGH) {
            ceilingFan.high();
        } else if (prevSpeed == CeilingFan.MEDIUM) {
            ceilingFan.medium();
        } else if (prevSpeed == CeilingFan.LOW) {
            ceilingFan.low();
        } else if (prevSpeed == CeilingFan.OFF) {
            ceilingFan.off();
        }
    }
}

让我们来测试下风扇吧

条件都具备了,那我们来测试下吧。我们打算把0号插槽的开启按钮设置为中速,把第1号插槽的开启按钮设置成高速,代码如下:

public class RemoteLoader {    public static void main(String[] args) {        RemoteControlWithUndo remoteControl = new RemoteControlWithUndo();        CeilingFan ceilingFan = new CeilingFan("Living Room");        CeilingFanMediumCommand ceilingFanMedium =                 new CeilingFanMediumCommand(ceilingFan);        CeilingFanHighCommand ceilingFanHigh =                 new CeilingFanHighCommand(ceilingFan);        CeilingFanOffCommand ceilingFanOff =                 new CeilingFanOffCommand(ceilingFan);        remoteControl.setCommand(0, ceilingFanMedium, ceilingFanOff);        remoteControl.setCommand(1, ceilingFanHigh, ceilingFanOff);        // 首先,我们以中速开启吊扇        remoteControl.onButtonWasPushed(0);        // 然后关闭        remoteControl.offButtonWasPushed(0);        System.out.println(remoteControl);        // 撤销,应该会回到中速        remoteControl.undoButtonWasPushed();        // 这个时候开启高速        remoteControl.onButtonWasPushed(1);        System.out.println(remoteControl);        // 再进行一次撤销,应该会回到中速        remoteControl.undoButtonWasPushed();    }}class RemoteLoader {

    public static void main(String[] args) {
        RemoteControlWithUndo remoteControl = new RemoteControlWithUndo();

        CeilingFan ceilingFan = new CeilingFan("Living Room");

        CeilingFanMediumCommand ceilingFanMedium = 
                new CeilingFanMediumCommand(ceilingFan);
        CeilingFanHighCommand ceilingFanHigh = 
                new CeilingFanHighCommand(ceilingFan);
        CeilingFanOffCommand ceilingFanOff = 
                new CeilingFanOffCommand(ceilingFan);

        remoteControl.setCommand(0, ceilingFanMedium, ceilingFanOff);
        remoteControl.setCommand(1, ceilingFanHigh, ceilingFanOff);

        // 首先,我们以中速开启吊扇
        remoteControl.onButtonWasPushed(0);
        // 然后关闭
        remoteControl.offButtonWasPushed(0);
        System.out.println(remoteControl);
        // 撤销,应该会回到中速
        remoteControl.undoButtonWasPushed();

        // 这个时候开启高速
        remoteControl.onButtonWasPushed(1);
        System.out.println(remoteControl);
        // 再进行一次撤销,应该会回到中速
        remoteControl.undoButtonWasPushed();
    }
}

好了,至此我们不仅仅实现了单个的开与关,还实现了一整个遥控器所有控件的开与关,甚至是复杂的家电的开与关(音响、电扇的开启略复杂),而且均实现了撤销。作为程序员的你是不是经常使用撤销功能呢,反正我是经常使用的噢。

但是,这还不是终极状态。我们在这里只能实现一个家电的开与关,如果光凭按下一个按钮,不能实现灯光、电视、音响的同步使用,那这个遥控器对我们来说是不是还是有点low呢?是吧,确实有点low,如何破解,敬请期待我们的下一篇。

爱生活,爱学习,爱感悟,爱挨踢

640?wx_fmt=jpeg

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

程序员小跃

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

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

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

打赏作者

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

抵扣说明:

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

余额充值