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

本文总结了《Head First 设计模式》中的命令模式

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

问题描述:

某电气自动化公司提供了一个可编程遥控器,遥控器上有若干个可编程插槽(Slot),用户可以为每个插槽添加不同的代码,让它们得以控制不同的家用电器。该公司还提供了很多电器类(电灯、电视机、音箱等),因为遥控器只能指挥电器执行相关操作,至于电器们“如何”执行动作,遥控器是不需要管的。电灯的“开、关”、音箱的“音量调大、调小”等操作都是电器们自身完成的。

现在遥控器已经到手了,家用电器的用法代码也有了,需要做的就是给遥控器编程,让它去指挥家用电器们。

书中用餐馆里面顾客订餐的过程引入

在这里插入图片描述

这个引入强调了对象之间的“解耦”问题,女招待员只需要将订单从顾客手里拿到并递交给厨师就行了,她只知道“有”、“无”订单,并不知道顾客点了哪些东西,也不知道厨师是怎么制作订单上的菜品的。订单将女招待员和厨师解耦了,他们俩不需要去沟通细节上的东西。

餐厅问题正反映了命令模式,下面给出命令模式的结构图:
在这里插入图片描述
上图的Command=“订单”,Invoker=“女招待员”,Receiver=“厨师“,Client=”顾客”。Invoker和Receiver解耦了,Invoker需要做的就是通过setCommand()方法,让之前从Client手中接下的command去命令Receiver执行相关操作。

下面将命令模式应用到之前的可编程遥控器问题上。
在遥控器问题中,Command=“开灯”、“关灯”、“调大音量”等,Invoker=“遥控器”,Receiver=“各种家用电器”,Client=“房屋主人”,这里遥控器和家用电器之间解耦了房屋主人通过setCommand()方法创建了很多Command对象在遥控器上的插槽(Slot)上,由于Command很多,可以将Command放在数组里,数组以Slot为编号,这样Command[slot]就代表一个Command。注意这里setCommand()方法是遥控器的方法,是房屋主人创建了Command对象和遥控器对象之后,通过“遥控器.setCommand()”给不同的Slot赋予不同的Command。

将所有Command(类似于按钮)部署在遥控器上之后,还需要赋予每个Command去操控家用电器的能力。因此,每个Command都得有一个自己的execute()方法,这个方法能控制家用电器,例如,LightOnCommand的execute()方法就是让一盏灯打开。当然,一盏灯怎么打开只有灯它自己最清楚,因此LightOnCommand得利用一个具体的Light(具体家用电器)对象去执行动作,可以通过LightOnCommand的构造器将Light对象传进来,如果要让客厅的灯打开,就把客厅的Light对象传进来,然后在execute()中调用Light对象的On()方法,就行了。这里的Light就是上图中的一个Receiver。
给出书上关于该问题的图
在这里插入图片描述
在这里插入图片描述

至此,命令模式的思路已经大致清晰了,下面给出书中的代码。

第一步:先弄一个Command接口

public interface Command {
    public void execute();
}

第二步:弄一个具体的Command类

public class LightOnCommand implements Command {
    Light light;
    LightOnCommand(Light light){
        this.light = light;
    }
    @Override
    public void execute() {
        light.on();
    }
}

电气自动化公司提供的家用电器Light类的代码应该是类似如下的形式:

public class Light {
    String type;
    Light(String type){
        this.type = type;
    }
    public void on(){
        System.out.println(type + " Light is on!");
    }
    public void off(){
        System.out.println(type + " Light is off !");
    }
}

第三步:完成遥控器类的编写

public class RemoteControl {
    Command[] onCommands;
    Command[] offCommands;
    public RemoteControl(){
    //创建数组用于装Command
        onCommands = new Command[7];
        offCommands = new Command[7];

        Command noCommand = new NoCommand(); //用到了空对象初始化
        for (int i=0; i<7;i++){
            onCommands[i] = noCommand;//没有被明确指定命令的插槽,会被赋予NoCommand对象
            offCommands[i] = noCommand;
        }
    }
    //给指定Slot部署相应的Commands
    public void setCommands(int slot,Command onCommand,Command offCommand){
        onCommands[slot] = onCommand;
        offCommands[slot] = offCommand;
    }
    //按键事件一旦发生就执行execute()
    public void onButtonWasPushed(int slot){
        onCommands[slot].execute();
    }
    public void offButtonWasPushed(int slot){
        offCommands[slot].execute();
    }
}

这里用到了空对象(NoCommand),这本身也是一种设计模式,这个可编程遥控器刚到用户手中的时候是没有控制功能的,按下按钮可能没有任何用,这时候就可以把“ 任何事都不做 ” 的NoCommand对象赋予这个按钮,用于按钮的初始化。
空对象的代码如下:

public class NoCommand implements Command {
    @Override
    public void execute() {}     //什么事都不做
}

开灯命令的代码如下:

public class LightOnCommand implements Command {
    Light light;
    LightOnCommand(Light light){
        this.light = light;
    }
    @Override
    public void execute() {
        light.on();
    }
}

关灯命令的代码如下:

public class LightOffCommand implements Command {
    Light light;
    public LightOffCommand(Light light){
        this.light = light;
    }
    @Override
    public void execute() {
        light.off();
    }
}

第四步:将要用的Command设置到遥控器上,并测试

public class RemoteLoader {
    public static void main(String[] args){
        //创建遥控器对象
        RemoteControl remoteControl = new RemoteControl();
        //创建家用电器对象
        Light livingRoomLight = new Light("Living room");
        Light kitchenLight = new Light("Kitchen");
        //创建一些命令灯的Command
        LightOnCommand livingRoomLightOn = new LightOnCommand(livingRoomLight);
        LightOffCommand livingRoomLightOff = new LightOffCommand(livingRoomLight);
        LightOnCommand kitchenLightOn = new LightOnCommand(kitchenLight);
        LightOffCommand kitchenLightOff = new LightOffCommand(kitchenLight);

        //让0号插槽去控制客厅灯的开关
        remoteControl.setCommands(0,livingRoomLightOn,livingRoomLightOff);
        //让1号插槽去控制厨房灯的开关
        remoteControl.setCommands(1,kitchenLightOn,kitchenLightOff);


        //房屋主人按下按钮去控制灯的开关
        //按下0号插槽的开按钮
        remoteControl.onButtonWasPushed(0);
        //按下1号插槽的关按钮
        remoteControl.offButtonWasPushed(1);
        //按下2号插槽的开按钮
        remoteControl.onButtonWasPushed(2);
    }
}

测试结果如下:

Living room Light is on!
Kitchen Light is off!

可以看到按下2号插槽的开按钮没有任何输出,因为我们没有给2号插槽设定任何Command,它根本不知道去打开谁,事实上它被空指令对象NoCommand初始化了,而NoCommand对象就是什么都不做。

下面是整个设计的全貌图:
在这里插入图片描述

撤销

还可以为遥控器增加撤销功能,书中的实现如下:
在这里插入图片描述

新建了一个撤销按钮,一旦撤销按钮被按下,将会执行遥控器先前执行的一个命令

宏命令

为了让遥控器的功能更高级,让它的一个按钮能一次性操作多个家用电器,书中还介绍了宏命令。这是通过宏指令来实现的,这是一个能同时调用多个命令的命令,代码如下:

public class MacroCommand implements Command {
    Command[] commands;
    public MacroCommand(Command[] commands){
        this.commands = commands;
    }
    @Override
    public void execute() {
        for (int i = 0;i<commands.length;i++){
            commands[i].execute();
        }
    }
}

设置一些将要加入宏命令的电器和正常指令

Light light = new Light("Living Room");
TV tv = new TV("Living Room");
Stereo stereo = new Stereo("Living Room");
Hottub hottub = new Hottub();

LightOnCommand lightOn = new LightOnCommand(light);
LightOffCommand lightOff = new LightOffCommand(light);
StereoOnCommand stereoOn = new SteroOncommand(stereo);
StereoOnCommand stereoOff = new SteroOffcommand(stereo);
TVOnCommand tvOn = new TVOnCommand(tv);
TVOffCommand tvOff = new TVOffCommand(tv);
HottubOnCommand hottubOn = new HottubOnCommand(hottub);
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);

将宏命令和0插槽绑定起来

remoteControl.setCommand(0,partyOnMacro,partyOffMacro);

下面一行代码将使之前的所有电器打开

remoteControl.onButtonWasPushed(0);

书中还介绍了命令模式的其他用途:

队列请求

直接上图
在这里插入图片描述

日志请求

在这里插入图片描述

总结

命令模式——将请求封装成对象,这可以让你使用不同的请求、队列或者日志请求来参数化其他对象,命令模式还支持撤销操作。
当需要将发出请求的对象和执行请求的对象解耦的时候,应该使用命令模式。
  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值