Head First 设计模式(六)命令模式

来源:互联网 发布:天猫销售数据分析报告 编辑:程序博客网 时间:2024/06/05 00:43

1. 定义

命令模式将“请求”封装成对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持可撤销的操作

这个模式我自己感觉理解不深,敲代码时没怎么遇见过这种设计模式。所以引用JAVA设计模式(15):行为型-命令模式(Command)中的一段话来进一步解释概念:

命令模式可以将请求发送者和接收者完全解耦,发送者与接收者之间没有直接引用关系,发送请求的对象只需要知道如何发送请求,而不必知道如何完成请求。

命令模式的核心在于引入了命令类,通过命令类来降低发送者和接收者的耦合度,请求发送者只需指定一个命令对象,再通过命令对象来调用请求接收者的处理方法

2. 类图

从类图中我们可以了解到,命令模式主要分为了四块:

  1. 命令(Command)
  2. 命令接受者(Receiver)
  3. 命令调用者(Invoker)
  4. 客户(Client)

整个流程可以看成,客户通过命令调用者来调用命令,而命令再调动命令接受者来处理。

如果不按照命令模式,那客户就要直接想办法调用Receiver的方法,耦合度大大增加。下面我们用场景代码来进一步说明

3. 场景+代码

场景:

现在我要设计一个遥控器类,上面有7对按钮,分别对应不同的功能(例如风扇开和风扇关、电灯开和电灯关),该如何设计?

我们先准备几个“功能类”,分别代码遥控器按钮要执行的功能:

/** * 电灯 */public class Light {    public void on() {        System.out.println("电灯打开了");    }    public void off() {        System.out.println("电灯关闭了");    }}/** * 车库门 */public class GarageDoor {    public void on() {        System.out.println("车库门打开了");    }    public void off() {        System.out.println("车库门关闭了");    }}

然后我们按最简单的逻辑,直接来设计这个遥控器:

public class OldRemoteControl {    //遥控器控制的家居-电视    Light light;    //遥控器控制的家居-车库门    GarageDoor garageDoor;    ……    public OldRemoteControl() {        light = new Light();        garageDoor = new GarageDoor();        ……    }    /**     * 按下某一开按钮     */    public void onButtonWasPressed(int i) {        //假设遥控器第一个按钮控制电灯        if(i == 0){            light.on();        }        //假设遥控器第二个按钮控制车库门        else if(i == 1){            garageDoor.on();        }        ……    }    /**     * 按下某一关按钮     */    public void offButtonWasPressed(int i) {        //假设遥控器第一个按钮控制电灯        if(i == 0){            light.off();        }        //假设遥控器第二个按钮控制车库门        else if(i == 1){            garageDoor.off();        }        ……    }}

这样写没什么问题。遥控器可以很正常的运行。

但是,我们很快发现:

  1. 每次遥控器按钮功能改变时,我们都需要改动代码,这违反了我们之前所说的“开闭设计原则”:类应该对拓展开发,对修改关闭。
  2. 遥控器和功能类的依赖太深,换句话说,这两个类耦合很严重。

如何改进?现在就是我们用命令模式的时刻啦~

代码

之前的定义讲过,命令模式的核心在于Command类,“命令发送者”调用“命令类”,而“命令类”调用“命令接受者”,从而降低发送者和接收者的耦合度,接下来看它的详细实现:

command

/** * 命令接口 */public interface Command {    /**     * 执行命令     */    public void execute();}/** * 电灯打开命令类 */public class LightOnCommand implements Command{    /**receiver*/    private Light light;    public LightOnCommand(Light light) {        this.light = light;    }    public void execute(){        light.on();    }}/** * 电灯关闭命令类 */public class LightOffCommand implements Command{    private Light light;    public LightOffCommand(Light light) {        this.light = light;    }    public void execute(){        light.off();    }}/** * 车库门打开命令类 */public class GarageDoorOnCommand implements Command{    private GarageDoor garageDoor;    public GarageDoorOnCommand(GarageDoor garageDoor) {        this.garageDoor = garageDoor;    }    public void execute(){        garageDoor.on();    }}/** * 车库门关闭命令类 */public class GarageDoorOffCommand implements Command{    private GarageDoor garageDoor;    public GarageDoorOffCommand(GarageDoor garageDoor) {        this.garageDoor = garageDoor;    }    public void execute(){        garageDoor.off();    }}

receiver

/** * 电灯 */public class Light {    public void on() {        System.out.println("电灯打开了");    }    public void off() {        System.out.println("电灯关闭了");    }}/** * 车库门 */public class GarageDoor {    public void on() {        System.out.println("车库门打开了");    }    public void off() {        System.out.println("车库门关闭了");    }}

invoker

我们现在来看一下,加了Command后的遥控器类:

/** * 遥控器,有多个指令 */public class RemoteControl {    /**开命令按钮*/    Command[] onCommands;    /**关命令按钮*/    Command[] offCommands;    /**空对象*/    public static Command noCommand = new NoCommand();    public RemoteControl() {        //设置遥控器有7对按钮,分别对应打开、关闭        onCommands = new Command[7];        offCommands = new Command[7];        //将所有按钮初始化为空按钮,反正空指针报错        for(int i = 0 ; i < 7 ; i++){            onCommands[i] = noCommand;            offCommands[i] = noCommand;        }    }    /**     * 设置一对按钮     */    public void setCommand(int i,Command onCommand,Command offCommand) {        onCommands[i] = onCommand;        offCommands[i] = offCommand;    }    /**     * 按下某一开按钮     */    public void onButtonWasPressed(int i) {        onCommands[i].execute();    }    /**     * 按下某一关按钮     */    public void offButtonWasPressed(int i) {        offCommands[i].execute();    }}/** * 一般invoker,只对应一个Command。此处给出作demo */public class SimpleRemoteControl {    Command command;    public void setCommand(Command command) {        this.command = command;    }    public void buttonWasPressed() {        command.execute();    }}

这段代码用到了“空对象”的概念。

我们想象一下,假如遥控器只设置了前两对按钮的功能,那为了我们按到其余按钮时程序不出错,需要进行判空操作:

public void onButtonWasPressed(int i) {        if(onCommands[i] != null)            onCommands[i].execute();    }

这样做麻烦很多,我们需要谨慎的判断各种可能出现空指针的情况,另外一旦出现空指针,程序都会被中断,而且没有任何空对象的提示。

为了改善这种情况,我们定义一个空对象命令类:

/** * 空指令,null的取代对象 */public class NoCommand implements Command{    @Override    public void execute() {        System.out.println("什么都不做……");    }    @Override    public void undo() {        System.out.println("什么都不做……");    }}

再在命令调用类(遥控器)初始化时,指定所有按钮为空命令

public RemoteControl() {        //设置遥控器有7对按钮,分别对应打开、关闭        onCommands = new Command[7];        offCommands = new Command[7];        //将所有按钮初始化为空按钮,反正空指针报错        for(int i = 0 ; i < 7 ; i++){            onCommands[i] = noCommand;            offCommands[i] = noCommand;        }    }

这样,便不需要再进行空判断了,而且即使按错了空功能的按钮,也会进行提示。

client

最后,由客户,也就是遥控器操纵者来发送命令,进行测试:

/** * 遥控器客户 */public class RemoteControlClient {    public static void main(String[] args) {        //客户创建一组命令对象,并将其放入调用者中(想要的遥控器)        RemoteControl remoteControl = generateControl();        //客户发送命令请求(操纵遥控器)        remoteControl.onButtonWasPressed(0);        System.out.println("=================");        remoteControl.onButtonWasPressed(1);    }    /**     *  创建一个遥控器     */    public static RemoteControl generateControl(){        Light light = new Light();        GarageDoor garageDoor = new GarageDoor();        Command lightOn = new LightOnCommand(light);        Command lightOff = new LightOffCommand(light);        Command garageDoorOn = new GarageDoorOnCommand(garageDoor);        Command garageDoorOff = new GarageDoorOffCommand(garageDoor);        RemoteControl remoteControl = new RemoteControl();        remoteControl.setCommand(0, lightOn, lightOff);        remoteControl.setCommand(1, garageDoorOn,garageDoorOff);        return remoteControl;    }}

拓展

撤销功能

现在我想要遥控器有撤销功能,即按下撤销键,把最近的一次操作撤销掉,该如何做?

首先,给每个命令类都增加一个撤销的方法:

/** * 命令接口 */public interface Command {    /**     * 执行命令     */    public void execute();    /**     * 撤销命令     */    public void undo();}/** * 电灯打开命令类 */public class LightOnCommand implements Command{    /**receiver*/    private Light light;    public LightOnCommand(Light light) {        this.light = light;    }    public void execute(){        light.on();    }    @Override    public void undo() {        light.off();    }}/** * 电灯关闭命令类 */public class LightOffCommand implements Command{    private Light light;    public LightOffCommand(Light light) {        this.light = light;    }    public void execute(){        light.off();    }    @Override    public void undo() {        light.on();    }}/** * 车库门打开命令类 */public class GarageDoorOnCommand implements Command{    private GarageDoor garageDoor;    public GarageDoorOnCommand(GarageDoor garageDoor) {        this.garageDoor = garageDoor;    }    public void execute(){        garageDoor.on();    }    @Override    public void undo() {        garageDoor.off();    }}/** * 车库门关闭命令类 */public class GarageDoorOffCommand implements Command{    private GarageDoor garageDoor;    public GarageDoorOffCommand(GarageDoor garageDoor) {        this.garageDoor = garageDoor;    }    public void execute(){        garageDoor.off();    }    @Override    public void undo() {        garageDoor.on();    }}

然后用一个变量,记录每次命令调用者(遥控器)的最后一次操作

public class RemoteControl {    /**开命令按钮*/    Command[] onCommands;    /**关命令按钮*/    Command[] offCommands;    /**最后一次执行的命令*/    Command lastCommand;    /**空对象*/    public static Command noCommand = new NoCommand();    public RemoteControl() {        //设置遥控器有7对按钮,分别对应打开、关闭        onCommands = new Command[7];        offCommands = new Command[7];        //将所有按钮初始化为空按钮,反正空指针报错        for(int i = 0 ; i < 7 ; i++){            onCommands[i] = noCommand;            offCommands[i] = noCommand;        }        lastCommand = noCommand;    }    /**     * 设置一对按钮     */    public void setCommand(int i,Command onCommand,Command offCommand) {        onCommands[i] = onCommand;        offCommands[i] = offCommand;    }    /**     * 按下某一开按钮     */    public void onButtonWasPressed(int i) {        lastCommand = onCommands[i];        if(onCommands[i] != null)        onCommands[i].execute();    }    /**     * 按下某一关按钮     */    public void offButtonWasPressed(int i) {        lastCommand = offCommands[i];        offCommands[i].execute();    }    /**     *  撤销最近一次的操作     */    public void cancelButtonWasPressed() {        lastCommand.undo();    }}

这样便成功了,如果想多次撤销之前的命令,同理,只需设置一个集合存储之前操作的所有命令,再一一调用undo()方法即可。

多功能综合按钮

看到之前的代码,可能有人会疑惑“接受者”存在的意义。为什么不直接在命令类的execute()方法中直接实现所有的逻辑呢?

之前设计的命令类,都是“简单式”的命令类。即它只懂得调用一个接受者的一个行为。当我们要实现“复杂式”的命令类,调用多个接受者行为时,“接受者”存在便很重要,它可以帮助我们解耦。看下面的例子。

现在我嫌遥控器按钮功能太单一,例如我想要按一个按钮同时开灯和开车库门,如何设计这种“批处理”按钮?

设计一个“批处理命令类”

/** * 复杂命令,由多个命令组成 */public class MacroCommand implements Command{    private Command[] commands;    public MacroCommand(Command[] commands) {        this.commands = commands;    }    @Override    public void execute(){        for (Command command : commands) {            command.execute();        }    }    @Override    public void undo() {        for (Command command : commands) {            command.undo();        }    }}

将批处理命令与按钮绑定,测试

/** * 遥控器客户 */public class RemoteControlClient {    public static void main(String[] args) {        //客户创建一组命令对象,并将其放入调用者中(想要的遥控器)        RemoteControl remoteControl = generateControl();        //客户发送命令请求(操纵遥控器)        System.out.println("========测试开灯=========");        remoteControl.onButtonWasPressed(0);        System.out.println("========测试开车库门=========");        remoteControl.onButtonWasPressed(1);        System.out.println("=========测试综合按钮========");        remoteControl.onButtonWasPressed(2);        System.out.println("=========测试撤销按钮========");        remoteControl.cancelButtonWasPressed();    }    /**     *  创建一个遥控器     */    public static RemoteControl generateControl(){        Light light = new Light();        GarageDoor garageDoor = new GarageDoor();        //创建“傻瓜式”命令        Command lightOn = new LightOnCommand(light);        Command lightOff = new LightOffCommand(light);        Command garageDoorOn = new GarageDoorOnCommand(garageDoor);        Command garageDoorOff = new GarageDoorOffCommand(garageDoor);        //创建“批处理”命令        Command[] maxOn = new Command[]{lightOn,garageDoorOn};        Command[] maxOff = new Command[]{lightOff,garageDoorOff};        MacroCommand partyOn =  new MacroCommand(maxOn);        MacroCommand partyOff =  new MacroCommand(maxOff);        //将命令与调用者绑定        RemoteControl remoteControl = new RemoteControl();        remoteControl.setCommand(0, lightOn, lightOff);        remoteControl.setCommand(1, garageDoorOn,garageDoorOff);        remoteControl.setCommand(2, partyOn,partyOff);        return remoteControl;    }}/**Output:========测试开灯=========电灯打开了========测试开车库门=========车库门打开了=========测试综合按钮========电灯打开了车库门打开了=========测试撤销按钮========电灯关闭了车库门关闭了*/

3. 用途

命令模式运用于:线程池、工作队列和日志请求等等


本文总结自
《Head First 设计模式》第六章:命令模式

部分参考于:
JAVA设计模式(15):行为型-命令模式(Command)

0 0
原创粉丝点击