26. 命令模式
定义
命令模式(Command Pattern):将一个请求封装为一个对象,从而让我们可用不同的请求对客户进行参数化;对请求排队或者记录请求日志,以及支持可撤销的操作。其别名为动作(Action)模式或事务(Transaction)模式。
通俗理解
我们都有找过10086的客服,如果你的手机出什么问题了,都会拨打这个号码,请求客服的解决。通常请求客服的时候,你都会说明一下你的问题,是流量问题、还是话费问题,然后客服会根据你的这些问题,进行解决,然后你就很愉快地挂了电话。
实际上却没有这么简单,如果一个人打电话过来了,说他们家的小区出现了“乒乓效应”[1],老是断线。估计客服听到这个词都会一脸懵逼,不知道怎么处理吧。再或者,有一个人的问题是他的流量充值了,但是还没有到账,客服查明之后往这个人的账户上冲流量,结果一个手抖,多打了两个零,系统还不支持撤销,遇到这种情况又得扣工资。还有... ...
可见,客服也不容易。那么怎么样可以让客服更好地进行服务,减少犯错的几率呢?我们保持一个观点就好:专业的事情让专业的人去做。这时候,客服只是一个客服系统的门面,当客服接听到电话的时候,他会将问题转发给专业的人士,例如流量问题就让流量组的人去处理、基站问题就让设备维护人员去处理;第二个是,专业人士有相关的撤销方法,如果手抖了输多了一个零,可以撤销这个请求,中午还能加鸡腿🍗,当然,用户不满意的时候,还可以重复用户请求,把他的要求再做一遍。
这个就是命令模式。用户(Client)通过和客服(Invoker)交流,客服根据不同的问题,将这些问题转发到不同的组(Command),然后相应的组的成员去完成这个工作。
示例
客服系统作为示例。
渣渣程序
话费服务和流量服务
public class TrafficService {
public void answer() {
System.out.println("流量服务");
}
}
public class PhoneChargeService {
public void answer() {
System.out.println("话费服务");
}
}
客服
public class CustomerService {
public void service(String type) {
switch (type){
case "phoneCharge":
new PhoneChargeService().answer();
break;
case "traffic":
new TrafficService().answer();
break;
default:
System.out.println("服务不支持");
break;
}
}
}
程序主入口
public class Main {
public static void main(String[] args) {
CustomerService service = new CustomerService();
service.service("phoneCharge");
}
}
//话费服务
缺点显而易见:
- 客服需要知道每一种问题的处理方法,即使
TrafficService
是其他客服,门面的客服也需要知道得调用TrafficService
什么方法; - 新增加处理类型需要修改
CustomerService.service(String type)
方法,违反开闭原则; - 没有撤销。
优化
类图
image程序
话费服务和流量服务
public class PhoneChargeService {
public void answer() {
System.out.println("话费服务");
}
public void undo() {
System.out.println("撤回话费服务");
}
}
//TrafficService类似,省略
命令接口和实现
public interface ICommand {
/**
* 执行
*/
void execute();
/**
* 撤销
*/
void undo();
/**
* 重做
*/
void redo();
}
public class PhoneChangeCommand implements ICommand {
private PhoneChargeService phoneChargeService;
private boolean change;
// 构造器,getter和setter方法
public void execute() {
phoneChargeService.answer();
change = true;
}
public void undo() {
if(change) {
phoneChargeService.undo();
} else {
System.out.println("没有操作话费服务,撤销失败");
}
}
public void redo() {
execute();
}
}
//TrafficCommand类似,省略
客服
public class Invoker {
private List<ICommand> commands = new ArrayList<>();
public List<ICommand> getCommands() {
return commands;
}
public void setCommands(List<ICommand> commands) {
this.commands = commands;
}
public void execumentCommand(ICommand command) {
commands.add(command);
command.execute();
}
public void redoCommand() {
if(commands.size() > 0) {
commands.get(commands.size()-1).redo();
commands.add(commands.get(commands.size()-1));
} else {
System.out.println("任务列表当中无任务,不能撤销");
}
}
public void undoCommand(){
if(commands.size() > 0) {
commands.get(commands.size()-1).undo();
commands.remove(commands.size()-1);
} else {
System.out.println("没有服务可以撤销");
}
}
}
主入口
public class Main {
public static void main(String[] args) {
ICommand phoneCommand = new PhoneChangeCommand(new PhoneChargeService());
ICommand trafficCommand = new TrafficCommand(new TrafficService());
Invoker server = new Invoker();
server.execumentCommand(phoneCommand);
server.execumentCommand(trafficCommand);
server.redoCommand();
server.undoCommand();
server.undoCommand();
server.undoCommand();
server.undoCommand();
}
}
//话费服务
//流量服务
//流量服务
//撤回流量服务
//撤回流量服务
//撤回话费服务
//没有服务可以撤销
优点
- 如果需要添加一个新的服务类型,不需要修改任何的内容,只需要添加一个服务的处理类,一个命令实现类实现命令接口,并在这个命令实现类里面注入服务的处理类即可,符合开闭原则;
- 请求和接收解耦,Invoker通过
ICommand
调用PhoneChargeService
,而Main则调动Invoker去调用服务方法; - 容易设计出一个命令队列或者组合命令;
- 撤销undo和恢复redo。
缺点
- 类™的真多。
应用场景
- 请求与调用的解耦,就像10086的客服一样,我只需要知道我的问题解决了就可以,不需要知道这个过程,另外相关的维护组的人员也只需要知道有工作要做就行,不需要知道这个工作室谁派下来的;
- 需要支持撤销和恢复的操作;
- 需要将命令组合在一起。
实例
JDK Awt和Swing包,MenuItem类
代码
https://www.jianshu.com/p/8be50cac2929
-
通信术语,指的是手机在两个基站覆盖范围的中间地带,手机不停切换对这两个基站的连接,造成信号的丢失,一般的解决办法是加大基站的功率,使得基站的覆盖范围更广,或者是增加基站的数量。 ↩