在软件设计中,我们经常需要向某些对象发送一些请求,但是并不知道请求的接收者是谁,也不知道被请求的操作是哪个,我们只需要在程序运行时指定具体的请求接收者即可,此时,可以使用命令模式来设计,使得请求发送者与请求接收者消除彼此之间的耦合,让对象之间的调用关系更加灵活。
命令模式(Command Pattern)可以对发送者和接收者完全解耦,发送者与接收者之间没有直接引用关系,发送请求的对象只需要知道如何发送请求,而不必知道如何完成请求。
命令模式将一个请求封装为一个对象,从而使我们可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。
Invoker:调用者,发送命令
Receiver:接收者,接收命令
Command:抽象命令类,定义了所有的命令
ConcreteCommand:具体命令类,调用接收者的操作
现有许多家电(电灯、电视机、空调....),每个家电都有自己的控制装置,如果需要控制它们,需要逐个开启、逐个关闭,这时候,如果有一个万能遥控器(如下如所示),操作不同的家电只需要按对应的按钮即可,如何使用命令模式实现?
1、创建电灯的命令接收者
public class LightReceiver { public void on() { System.out.println("电灯打开了"); } public void off() { System.out.println("电灯关闭了"); }}
2、创建抽象的命令接口
public interface Command { /** * 执行命令 */ void execute(); /** * 撤销命令 */ void undo();}
3、创建电灯的开启、关闭命令类,实现命令接口
public class LightOnCommand implements Command { private LightReceiver lightReceiver; public LightOnCommand(LightReceiver lightReceiver) { this.lightReceiver = lightReceiver; } @Override public void execute() { lightReceiver.on(); } @Override public void undo() { lightReceiver.off(); }}
public class LightOffCommand implements Command { private LightReceiver lightReceiver; public LightOffCommand(LightReceiver lightReceiver) { this.lightReceiver = lightReceiver; } @Override public void execute() { lightReceiver.off(); } @Override public void undo() { lightReceiver.on(); }}
4、创建空的命令类,用于初始化按钮
public class NullCommand implements Command { @Override public void execute() { } @Override public void undo() { }}
5、创建命令调用者
public class RemoteController { /** * 操作对象的个数 */ private static final int COUNT = 5; /** * 开启命令组 */ Command[] onCommands; /** * 关闭命令组 */ Command[] offCommands; /** * 执行撤销的命令 */ Command undoCommand; /** * 初始化 */ public RemoteController() { onCommands = new Command[COUNT]; offCommands = new Command[COUNT]; for (int i = 0; i < COUNT; i++) { onCommands[i] = new NullCommand(); offCommands[i] = new NullCommand(); } } /** * 设置按钮 */ public void setCommand(int no, Command onCommand, Command offCommand) { onCommands[no] = onCommand; offCommands[no] = offCommand; } /** * 按下开按钮 */ public void pressOnButton(int no) { onCommands[no].execute(); //记录按钮,以便撤销 undoCommand = onCommands[no]; } /** * 按下关按钮 */ public void pressOffButton(int no) { offCommands[no].execute(); //记录按钮,以便撤销 undoCommand = offCommands[no]; } /** * 按下撤销按钮 */ public void pressUndoButton(int no) { if (undoCommand != null) { undoCommand.undo(); } }}
6、测试类
public class Client { @Test public void testLight() { //创建命令接收者 LightReceiver lightReceiver = new LightReceiver(); //创建电灯的一组操作(开和关) LightOnCommand lightOnCommand = new LightOnCommand(lightReceiver); LightOffCommand lightOffCommand = new LightOffCommand(lightReceiver); //创建命令调用者 RemoteController remoteController = new RemoteController(); //设置命令 remoteController.setCommand(0, lightOnCommand, lightOffCommand); //执行命令 System.out.println("----按下开灯按钮----"); remoteController.pressOnButton(0); System.out.println("----按下关灯按钮----"); remoteController.pressOffButton(0); System.out.println("----按下撤销按钮----"); remoteController.pressUndoButton(0); }}
7、运行结果
----按下开灯按钮----电灯打开了----按下关灯按钮----电灯关闭了----按下撤销按钮----电灯打开了
8、如果再添加一个电视机,不需要改动任何已有的代码,添加 TvReceiver
、TvOnCommand
、TvOffCommand
这几个类即可。
?? 优点:
?? 缺点:
?? 适用环境:
?? 实际应用场景:
在 Spring 的 JdbcTemplate 这个类中有 query() 方法,query() 方法中定义了一个内部类 QueryStatementCallback,QueryStatementCallback 又实现了 StatementCallback 接口,另外还有其它类实现了该接口,StatementCallback 接口中又有一个抽象方法 doInStatement()。在 execute() 中又调用了 query()。
将其关系缕一缕就是: