设计模式第六篇-命令模式
一、引言
先看需求:设计一个家电遥控器系统,每个家电由开、关两个按钮控制, 每个家电都由各自的厂商提供了实现方法,我们只需要调用这些方法即可,如图所示:
如何实现这个功能呢?
第一步我们要排除的实现方式就是if条件判断,因为一旦增加家电,我们就必须修改代码,这不符合我们的设计思路。
然后我们想想,遥控按钮只是发出一个请求,具体的实现是通过各自厂商的API,我们应该让遥控器(动作的请求者)从厂商的API(动作的执行者)中解耦出来。可是怎么去解耦呢?毕竟按钮动作和家电行为是息息相关的。
这个时候就可以使用命令模式,来认识一下命令模式。
二、命令模式
先看定义:命令模式是把一个操作或者行为抽象为一个对象中,通过对命令的抽象化来使得发出命令的责任和执行命令的责任分隔开。命令模式的实现可以提供命令的撤销和恢复功能
听着还是很抽象,我们再举一个例子,《破产姐妹》中熟悉的场景 ,顾客到餐厅点单,服务员MAX拿了订单,放在订单柜台,厨师Oleg根据订单准备餐点。
分析下其中的过程,把订单想象成一个用来请求准备餐点的对象,订单对象可以被传递,服务员MAX负责传递对象,订单的接口中只包含一个orderUp()方法,这个方法封装了而准备餐点所需的动作,订单内有一个“需要进行准备工作的对象”(也就是厨师Oleg)的引用,这一切都封装起来,MAX甚至不需要知道订单上有什么,她只负责传递。
有没有清楚一些,转成类图:
类图中可以看出,其中的几个角色:
- 客户角色(Client):发出一个具体的命令并设置其接受者。
- 调用者(Invoker):持有一个命令对象,负责命令对象执行命令。
- 命令角色(Command):声明命令接口,并具备一个让接收者执行的方法。
- 具体命令角色(ConreteCommand):定义了动作和接收者之间的绑定关系,负责调用接收者的方法。
- 接受者(Receiver):负责具体动作的执行。
三、代码实现
命令接口:
//命令接口 public interface Command { //命令方法 void execute(); }
命令接收者
//命令接收者(Receiver),电灯 public class Light { private String name; public Light(String name){ this.name=name; } //开灯操作 public void on(){ System.out.println(name+":开灯!"); } //关灯操作 public void off(){ System.out.println(name+":关灯"); } }
绑定命令与接收者关系
//绑定命令与接收者关系ConreteCommand public class LightOnCommand implements Command { Light light; public LightOnCommand(Light light){ this.light=light; } //具体命令方法 public void execute() { light.on(); } }
调用者(Invoker)
//命令模式的客户(Invoker) public class SimpleRemoteControl { //命令接口 Command solt; public void setCommand(Command command){ this.solt=command; } //命令方法 public void buttonWasPressed(){ solt.execute(); } }
运行:
private static void simpleControl() { //遥控器调用者 SimpleRemoteControl control=new SimpleRemoteControl(); //电灯 Light light=new Light("客厅"); //具体命令类 LightOnCommand lightOnCommand=new LightOnCommand(light); //设置命令 control.setCommand(lightOnCommand); //命令方法 control.buttonWasPressed(); }
结果:
这里其实只实现了其中一个按钮,让我们来补充一些代码
增加多一个接收者:
//另外一个接受者吊灯 public class CeilingFan { private String name; public CeilingFan(String name){ this.name=name; } public void on(){ System.out.println(name+":打开"); } public void off(){ System.out.println(name+":关闭"); } }
//吊灯的开灯命令 public class CeilingFanOffCommand implements Command { CeilingFan ceilingFan; public CeilingFanOffCommand(CeilingFan ceilingFan){ this.ceilingFan=ceilingFan; } //具体命令方法 public void execute() { ceilingFan.off(); } } //吊灯的关灯命令 public class CeilingFanOnCommand implements Command { CeilingFan ceilingFan; public CeilingFanOnCommand(CeilingFan ceilingFan){ this.ceilingFan = ceilingFan; } //具体命令方法 public void execute() { ceilingFan.on(); } }
调用者遥控:
//遥控调用 public class RemoteControl { //申明命令数组 Command[] onCommands; Command[] offCommands; public RemoteControl(){ onCommands=new Command[4]; offCommands=new Command[4]; } //设置命令 public void setCommand(int solt,Command onCommand,Command offCommand){ onCommands[solt]=onCommand; offCommands[solt]=offCommand; } //打开按钮 public void onButtonWasPressed(int solt){ onCommands[solt].execute(); } //关闭按钮 public void offButtonWasPressed(int solt){ offCommands[solt].execute(); } }
实际操作遥控:
private static void control() { RemoteControl remoteControl=new RemoteControl(); Light roomLight=new Light("客厅灯"); Light kitchLight=new Light("厨房灯"); CeilingFan roomCeilingFan=new CeilingFan("客厅吊扇"); CeilingFan kitchCeilingFan=new CeilingFan("厨房吊扇"); //电灯相关命令 LightOnCommand roomLightOnCommand=new LightOnCommand(roomLight); LightOnCommand kitchLightOnCommand=new LightOnCommand(kitchLight); LightOffCommand roomLightOffCommand=new LightOffCommand(roomLight); LightOffCommand kitchLightOffCommand=new LightOffCommand(kitchLight); //吊扇相关命令 CeilingFanOnCommand roomCeilingFanOnCommand =new CeilingFanOnCommand(roomCeilingFan); CeilingFanOnCommand kitchCeilingFanOnCommand =new CeilingFanOnCommand(kitchCeilingFan); CeilingFanOffCommand roomCeilingFanOffCommand =new CeilingFanOffCommand(roomCeilingFan); CeilingFanOffCommand kitchCeilingFanOffCommand =new CeilingFanOffCommand(kitchCeilingFan); //将命令加载到卡槽中 remoteControl.setCommand(0,roomLightOnCommand,roomLightOffCommand); remoteControl.setCommand(1,kitchLightOnCommand,kitchLightOffCommand); remoteControl.setCommand(2,roomCeilingFanOnCommand,roomCeilingFanOffCommand); remoteControl.setCommand(3,kitchCeilingFanOnCommand,kitchCeilingFanOffCommand); //使用遥控 remoteControl.onButtonWasPressed(0); remoteControl.offButtonWasPressed(0); remoteControl.onButtonWasPressed(1); remoteControl.offButtonWasPressed(1); remoteControl.onButtonWasPressed(2); remoteControl.offButtonWasPressed(2); remoteControl.onButtonWasPressed(3); remoteControl.offButtonWasPressed(3); }
运行结果:
四、总结
在下面的情况下可以考虑使用命令模式:
- 系统需要支持命令的撤销(undo)。命令对象可以把状态存储起来,等到客户端需要撤销命令所产生的效果时,可以调用undo方法吧命令所产生的效果撤销掉。命令对象还可以提供redo方法,以供客户端在需要时,再重新实现命令效果。
- 系统需要在不同的时间指定请求、将请求排队。一个命令对象和原先的请求发出者可以有不同的生命周期。意思为:原来请求的发出者可能已经不存在了,而命令对象本身可能仍是活动的。这时命令的接受者可以在本地,也可以在网络的另一个地址。命令对象可以串行地传送到接受者上去。
- 如果一个系统要将系统中所有的数据消息更新到日志里,以便在系统崩溃时,可以根据日志里读回所有数据的更新命令,重新调用方法来一条一条地执行这些命令,从而恢复系统在崩溃前所做的数据更新。
- 系统需要使用命令模式作为“CallBack(回调)”在面向对象系统中的替代。Callback即是先将一个方法注册上,然后再以后调用该方法。
优点: 1、降低了系统耦合度。 2、新的命令可以很容易添加到系统中去。
缺点:使用命令模式可能会导致某些系统有过多的具体命令类。