认识设计模式(八)---命令模式
项目需求
- 我们买了一套智能家电,有灯、风扇、洗衣机等等,我们只要在手机上安装app就可以控制这些家电的工作
- 这些智能家电来自不桶的厂家,我们不想针对每一种家电都安装一个app分别控制,所以我们希望只要一个app就能控制所有家电
- 要实现一个app控制所有智能家电的需要,需要每个只能家电厂家都提供一个统一的接口给app调用,考虑使用命令模式
- 命令模式可以把“动作的请求者”从“动作的执行者”对象中解耦出来。动作的执行者是手机app,动作的执行者是每个厂商的一个家电产品
基本介绍
- 在软件是设计中,我们经常需要向某些对象发送请求,但是并不知道请求的执行者是谁,也不知道被请求的操作是哪个。我们只知道在程序运行时指定具体的请求接受者即可,此时就可以用命令模式来进行设计
- 命令模式使得请求发送者和请求接受者清除彼此之间的耦合,让对象之间的调用关系更加灵活,实现解耦
- 在命名模式中,会把一个请求封装为一个对象,一边使用不通畅桉树来表示不同的请求,桶时命令模式也支持可撤销的操作
代码示例
(1)电器的接收器类
有两个方法可以调用,on和off
public class LightReceiver {
public void on(){
System.out.println("电灯打开了...");
}
public void off(){
System.out.println("电灯关掉了...");
}
}
public class TvReceiver {
public void on(){
System.out.println("电视打开了...");
}
public void off(){
System.out.println("电视关掉了...");
}
}
(2)命令的接口
有两个命令,execute执行方法、undo撤销方法
public interface Command {
//执行动作(操作)
public void execute();
//撤销动作(操作)
public void undo();
}
(3)具体的命令类
- 开电视的命令类,继承Command接口,可以重写execute和undo方法了
- 创建命令接收器类TvReceiver 的对象,并且在构造器里完成对象实例化,可以调用on和off方法了
- 在重写execute方法中,调用需要的on命令;在重写undo方法中,调用需要的off命令,完成了对命令的撤销
- 下面类似,写出关电视的命令类、开灯的命令类、关灯的命令类
public class TvOffCommand implements Command {
//命令接收器
TvReceiver tv;
//构造器
public TvOffCommand(TvReceiver tv) {
this.tv = tv;
}
//聚合电灯reciver
@Override
public void execute() {
//调用接受者的方法
tv.off();
}
@Override
public void undo() {
tv.on();
}
}
public class TvOnCommand implements Command {
//命令接收器
TvReceiver tv;
//构造器
public TvOnCommand(TvReceiver tv) {
this.tv = tv;
}
//聚合电灯reciver
@Override
public void execute() {
//调用接受者的方法
tv.on();
}
@Override
public void undo() {
tv.off();
}
}
public class LightOffCommand implements Command {
//命令接收器
LightReceiver light;
//构造器
public LightOffCommand(LightReceiver light) {
this.light = light;
}
//聚合电灯reciver
@Override
public void execute() {
//调用接受者的方法
light.off();
}
@Override
public void undo() {
light.on();
}
}
public class LightOnCommand implements Command {
//命令接收器
LightReceiver light;
//构造器
public LightOnCommand(LightReceiver light) {
this.light = light;
}
//聚合电灯reciver
@Override
public void execute() {
//调用接受者的方法
light.on();
}
@Override
public void undo() {
light.off();
}
}
(4)无命令类
public class NoCommand implements Command {
//不执行任何命令,用于初始化每个按钮
//这也是一种设计模式,可以节省对空的判断
@Override
public void execute() {
}
@Override
public void undo() {
}
}
(5)遥控器app类
- 创建两个数据,用来分别放置不同电器的开关命令
- 在构造器中对数组进行初始化,先把数组中的每个命令都设置为空命令
- setCommand方法设置命令,参数是no和Command的开关对象
- onbuttonWasPushed和offbuttonWasPushed方法,执行Command对象里的具体方法(Command是个接口,它的对象也包括它所有实现类的对象)
public class RemoteController {
//开关按钮的命令数组
Command[] onCommands;
Command[] offCommands;
//执行撤销的命令
Command undoCommand;
//构造器,完成对按钮的初始化
public RemoteController() {
onCommands = new Command[5];
offCommands = new Command[5];
for (int i = 0; i < 5; i++) {
onCommands[i]=new NoCommand();
offCommands[i]=new NoCommand();
}
}
//给我们的按钮设置你需要的命令
public void setCommand(int no, Command onCommand, Command offCommand){
onCommands[no]=onCommand;
offCommands[no]=offCommand;
}
//按下开按钮
public void onbuttonWasPushed(int no){
//找到你按下的开的按钮,并调用对应的方法
onCommands[no].execute();
//记录这次的操作,用于撤销
undoCommand=onCommands[no];
}
//按下关按钮
public void offbuttonWasPushed(int no){
//找到你按下的开的按钮,并调用对应的方法
offCommands[no].execute();
//记录这次的操作,用于撤销
undoCommand=offCommands[no];
}
//按下撤销按钮
public void undobuttonWasPushed(){
//执行相反的操作,也就是撤销
undoCommand.undo();
}
}
(6)测试类
5. 先创建一个接收器的对象lightReceiver,可以调用on和off方法了
6. 再创建具体命令类的对象lightOnCommand,lightReceiver作为参数传进构造器,on和off方法就可以写进execute和undo方法中了
7. 创建遥控器类对象remoteController,调用setCommand方法把上面写好的命令lightOnCommand设置到数组里去
8. 然后通过onbuttonWasPushed方法就可以实现对应的方法了
public class Client {
public static void main(String[] args) {
//使用命令模式,完成通过遥控器,对电灯的操作
//创建一个电灯对象
LightReceiver lightReceiver=new LightReceiver();
//创建电灯相关的开关命令
LightOnCommand lightOnCommand=new LightOnCommand(lightReceiver);
LightOffCommand lightOffCommand=new LightOffCommand(lightReceiver);
//创建一个电灯对象
TvReceiver tvReceiver=new TvReceiver();
//创建电灯相关的开关命令
TvOnCommand tvOnCommand=new TvOnCommand(tvReceiver);
TvOffCommand tvOffCommand=new TvOffCommand(tvReceiver);
//需要一个遥控器
RemoteController remoteController=new RemoteController();
//给遥控器设置相关的命令,no=0是电灯的开关操作
remoteController.setCommand(0,lightOnCommand,lightOffCommand);
remoteController.setCommand(1,tvOnCommand,tvOffCommand);
System.out.println("-----按下灯开的按钮-----");
remoteController.onbuttonWasPushed(0);
System.out.println("-----按下灯关的按钮-----");
remoteController.offbuttonWasPushed(0);
System.out.println("-----按下撤销的按钮-----");
remoteController.undobuttonWasPushed();
System.out.println("-----按下电视开的按钮-----");
remoteController.onbuttonWasPushed(1);
System.out.println("-----按下电视关的按钮-----");
remoteController.offbuttonWasPushed(1);
System.out.println("-----按下撤销的按钮-----");
remoteController.undobuttonWasPushed();
}
}
UML图
命令模式的注意事项和细节
- 把发起请求的对象和执行请求的额对象解耦,发起请求的对象是调用者,调用者只要调用命令对象的execute方法就可以让接受者工作,而不需要知道具体的接受者对象是谁、具体是如何实现的,命令对象会负责让接受者执行请求的动作。。也就是说请求发起者和请求执行者之间的解耦是通过命令对象实现的,命令对象起到了纽带桥梁的作用
- 容易设计一个命令队列,只要把命令对象放到列队, 就可以多线程的执行命令
- 容易实现对请求的撤销和重做
- 命令模式的不足:可能导致某些系统有过多的具体命令类,增加系统的复杂度
- 空命令也是一种设计模式,它为我们省去了判断空的操作。如果没有使用空命令,我们每按下一个按键都要判空,干脆只让给空写一个命令,当为空时就什么都不执行就好了
- 命令模式经典的应用场景:界面的一个按钮都是一条命令,模拟CMD订单的撤销/恢复