前言:继续学习设计模式,今天学习命令模式,命令模式就是为了将一组行为抽象为对象,实现二者之间的松耦合。
命令模式(Command Pattern)
定义:将“请求”封装为对象,以便使用不同的请求、队列或者日志来参数化其他对象。命令模式也支持可撤销的操作。
类图:
1. Client创建一个ConcreteCommand对象并指定他的Receiver对象
2. 某个Invoker对象存储该ConcreteCommand对象
3. 该Invoker通过调用Command对象的Execute操作来提交一个请求。若该命令是可撤销的,ConcreteCommand就在执行Execute操作之前存储当前状态以用于取消该命令
模式分析:
1.命令模式的本质是对命令进行封装,将发出命令的责任和执行命令的责任分割开。
2.每一个命令都是一个操作:请求的一方发出请求,要求执行一个操作;接收的一方收到请求,并执行操作。
3.命令模式允许请求的一方和接收的一方独立开来,使得请求的一方不必知道接收请求的一方的接口,更不必知道请求是怎么被接收,以及操作是否被执行、何时被执行,以及是怎么被执行的。
4.命令模式使请求本身成为一个对象,这个对象和其他对象一样可以被存储和传递。
5.命令模式的关键在于引入了抽象命令接口,且发送者针对抽象命令接口编程,只有实现了抽象命令接口的具体命令才能与接收者相关联。
优点:
1.降低对象之间的耦合度。
2.新的命令可以很容易地加入到系统中。
3.可以比较容易地设计一个组合命令。
4.调用同一方法实现不同的功能
缺点:
1.使用命令模式可能会导致某些系统有过多的具体命令类。因为针对每一个命令都需要设计一个具体命令类,因此某些系统可能需要大量具体命令类,这将影响命令模式的使用。
使用场景:
当需要将发出请求的对象和执行请求的对象解耦的时候,使用命令模式。
1.系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。
2.系统需要在不同的时间指定请求、将请求排队和执行请求。
3.系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。
4.系统需要将一组操作组合在一起,即支持宏命令。
更多用途:
队列请求:将命令排成一个队列打包,一个个调用 execute 方法,如线程池的任务队列,线程不关心任务队列中是读 IO 还是计算,只取出命令后执行,接着进行下一个。
日志请求:某些应用需要我们将所有的动作记录在日志中,然后在系统死机等情况出现时,重新调用这些动作恢复到之前的状态。如数据库事务。
宏命令模式:命令模式 加 组合模式,我们可以将多个命令组合到一起来实现命令的批处理。
示例:
这里有一个家电遥控器,上面有一些可以控制家电的开关,有开、关、和一个撤销一步的操作,比如电视机的开关、灯的开关等等。
其中灯、电视机就是Receiver,遥控器就是Invoker。
创建灯、电视的实体
/** * 灯 * Created by yule on 2018/7/21 21:02. */ public class Light { public void on(){ System.out.println("开灯了"); } public void off(){ System.out.println("关灯了"); } }
/** * 电视机 * Created by yule on 2018/7/21 21:05. */ public class Television { public void on(){ System.out.println("开电视了"); } public void off(){ System.out.println("关电视机了"); } }
定义命令接口类
/** * 命令接口类 * Created by yule on 2018/7/21 21:00. */ public interface Command { /** * 执行操作 */ void execute(); /** * 撤销操作 */ void undo(); }
灯实现命令
/** * 开灯命令 * Created by yule on 2018/7/21 21:02. */ public class LightOnCommand implements Command{ private Light light; public LightOnCommand(Light light){ this.light = light; } @Override public void execute() { this.light.on(); } @Override public void undo() { //开灯命令的撤销就是关灯。 this.light.off(); } }
/** * 关灯命令 * Created by yule on 2018/7/21 21:04. */ public class LightOffCommand implements Command { private Light light; public LightOffCommand(Light light){ this.light = light; } @Override public void execute() { this.light.off(); } @Override public void undo() { //关灯命令的撤销就是开灯。 this.light.on(); } }
电视实现命令
/** * 电视机开命令 * Created by yule on 2018/7/21 21:05. */ public class TelevisionOnCommand implements Command{ private Television television; public TelevisionOnCommand(Television television){ this.television = television; } @Override public void execute() { this.television.on(); } @Override public void undo() { this.television.off(); } }
/** * 电视机关命令 * Created by yule on 2018/7/21 21:13. */ public class TelevisionOffCommand implements Command{ private Television television; public TelevisionOffCommand(Television television){ this.television = television; } @Override public void execute() { this.television.off(); } @Override public void undo() { this.television.on(); } }
定义一个什么都不操作的命令类
/** * 什么都不做的命令类,用于初始化 * Created by yule on 2018/7/21 21:19. */ public class NoCommand implements Command { @Override public void execute() { } @Override public void undo() { } }
实现家电遥控器
/** * 家电遥控器 * Created by yule on 2018/7/21 21:15. */ public class RemoteControl { Command[] onCommands; Command[] offCommands; Command undoCommand;//前一个命令将被记录在这里,这里暂时只支持撤销一步 public RemoteControl(int count){ onCommands = new Command[count]; offCommands = new Command[count]; //全部初始化为什么都不做的命令类 for(int i = 0; i < count; i++){ onCommands[i] = new NoCommand(); offCommands[i] = new NoCommand(); } undoCommand = new NoCommand(); } public void setCommand(int slot, Command onCommand, Command offCommand){ onCommands[slot] = onCommand; offCommands[slot] = offCommand; } /** * 点击开启按钮 * @param slot */ public void onButtonClick(int slot){ onCommands[slot].execute(); undoCommand = onCommands[slot]; } /** * 点击关闭按钮 * @param slot */ public void offButtonClick(int slot){ offCommands[slot].execute(); undoCommand = offCommands[slot]; } /** * 点击撤销按钮 */ public void undoButtonClick(){ undoCommand.undo(); } }
测试
/** * 测试类 * Created by yule on 2018/7/21 20:55. */ public class Client { public static void main(String[] args){ //先创建一个控制两个家电的遥控器 RemoteControl remoteControl = new RemoteControl(2); //创建家电 Light light = new Light(); Television tv = new Television(); //创建家电的命令 LightOnCommand lightOnCommand = new LightOnCommand(light); LightOffCommand lightOffCommand = new LightOffCommand(light); TelevisionOnCommand televisionOnCommand = new TelevisionOnCommand(tv); TelevisionOffCommand televisionOffCommand = new TelevisionOffCommand(tv); //给遥控器设置命令 remoteControl.setCommand(0, lightOnCommand, lightOffCommand); remoteControl.setCommand(1, televisionOnCommand, televisionOffCommand); //操作遥控器 remoteControl.onButtonClick(0); remoteControl.onButtonClick(1); remoteControl.offButtonClick(1); remoteControl.undoButtonClick(); remoteControl.offButtonClick(0); remoteControl.undoButtonClick(); } }
运行结果
如何实现多层的撤销操作:
这里示例实现的撤销操作,只支持撤销一步,想要达到多层次的撤销操作,就不能只记录最后一个被执行的命令,可以使用堆栈记录操作过程的每一个命令。然后,不管什么时候按下了撤销按钮,都可以从堆栈中取出最上层的命令,然后调用它的 undo() 方法。
如何按下一个按钮就实现多个按钮的操作:
可以使用宏命令。制造一个新的命令实现命令接口类,
/** * 宏命令 * Created by yule on 2018/7/21 21:37. */ 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(); } } }
参考:《Head first 设计模式》