作者:猫十二懿
❤️账号:CSDN 、掘金 、个人博客 、Github
公众号:猫十二懿
命令模式(Command),将一个请求封装为一个对象,从而使你可用不同的请求对客户进行参数化;对请求排队或记录请求日志,以及支持可 撤销的操作。
命令模式(Command Pattern)结构图
在命令模式中,介绍上图中核心角色:
通过命令模式,客户端与调用者之间的耦合可以被解耦,客户端只需创建具体的命令对象并将其传递给调用者,而不需要了解具体的接收者和操作细节。这样可以实现请求的发送者和接收者之间的解耦,并且支持对请求进行排队、记录日志、撤销和重做等操作。
Command类,用来声明执行操作的接口。
/**
* @author Shier
* CreateTime 2023/5/20 14:58
* 抽象命令类
*/
public abstract class Command {
protected Receiver receiver;
public Command(Receiver receiver) {
this.receiver = receiver;
}
/**
* 执行命令
*/
public abstract void excuteCommand();
}
ConcreteCommand类,将一个接收者对象绑定于一个动作,调用接收者相应的操作,以实现executeCommand。
/**
* @author Shier
* CreateTime 2023/5/20 15:01
* 具体执行命令
*/
public class ConcreteCommand extends Command{
public ConcreteCommand(Receiver receiver) {
super(receiver);
}
@Override
public void excuteCommand() {
receiver.action();
}
}
Invoker类,要求该命令执行这个请求。
/**
* @author Shier
* CreateTime 2023/5/20 15:02
*/
public class Invoker {
private Command command;
public void setCommand(Command command) {
this.command = command;
}
public void excuteCommand(){
command.excuteCommand();
}
}
Receiver类,知道如何实施与执行一个与请求相关的操作,任何类都可 能作为一个接收者。
/**
* @author Shier
* CreateTime 2023/5/20 15:00
*/
public class Receiver {
public void action(){
System.out.println("执行请求!!");
}
}
客户端代码,创建一个具体命令对象并设定它的接收者。
/**
* @author Shier
* CreateTime 2023/5/20 15:03
*/
public class ComClient {
public static void main(String[] args) {
Receiver receiver = new Receiver(); // 将具体怎么执行请求封装
ConcreteCommand command = new ConcreteCommand(receiver);
Invoker invoker = new Invoker(); // 将调用命令执行请求
invoker.setCommand(command);
invoker.excuteCommand();
}
}
摆摊烧烤 VS 开店烧烤:
摆摊烧烤:就一个人进行烧烤,客户一多,就很难记清楚谁点了什么烧烤,要什么程度的烧烤(不辣、微辣等),而且这里需要知道谁是烧烤者
开店烧烤:有服务员记录了你点的烧烤,同时是服务员通知后厨去进行烧烤,客户根本就不知道谁烧的,还有等你觉得点了太多的烧烤,还可以叫服务员撤销一些,不够吃再点同样可以的。这样就避免了混乱的情况发生。服务员做记录在程序里面就是日志信息
//烤肉串者
class Barbecuer{
//烤羊肉
public void bakeMutton(){
System.out.println("烤羊肉串!");
}
//烤鸡翅
public void bakeChickenWing(){
System.out.println("烤鸡翅!");
}
}
客户端代码:
public class Test {
public static void main(String[] args) {
Barbecuer boy = new Barbecuer();
boy.bakeMutton();
boy.bakeMutton();
boy.bakeMutton();
boy.bakeChickenWing();
boy.bakeMutton();
boy.bakeMutton();
boy.bakeChickenWing();
}
}
存在问题:用户多了,请求就多,就容易混乱。使用命令模式进行改进,也就是进步到开店烧烤的程度了
看看代码结构图:
烧烤类:
/**
* @author Shier
* CreateTime 2023/5/20 15:20
* 烧烤者
*/
public class Barbecuer {
public void bakeMutton(){
System.out.println("烤羊肉串!");
}
public void bakeChickenWing(){
System.out.println("考鸡翅!");
}
}
抽象命令类:
/**
* @author Shier
* CreateTime 2023/5/20 15:21
* 抽象命令类
*/
public abstract class Command {
public Barbecuer barbecuer;
public Command(Barbecuer barbecuer) {
this.barbecuer = barbecuer;
}
/**
*执行命令
*/
public abstract void excuteCommand();
}
具体命令类(烤什么东西):
/**
* @author Shier
* CreateTime 2023/5/20 15:22
* 烤羊肉命令类
*/
public class BakeMuttonCommand extends Command {
public BakeMuttonCommand(Barbecuer barbecuer) {
super(barbecuer);
}
@Override
public void excuteCommand() {
barbecuer.bakeMutton();
}
}
/**
* @author Shier
* CreateTime 2023/5/20 15:22
* 烤鸡翅命令类
*/
public class BakeChickenWingCommand extends Command {
public BakeChickenWingCommand(Barbecuer barbecuer) {
super(barbecuer);
}
@Override
public void excuteCommand() {
barbecuer.bakeChickenWing();
}
}
服务员:
/**
* @author Shier
* CreateTime 2023/5/20 15:24
* 服务员
*/
public class Waiter {
private Command command;
public void setCommand(Command command) {
this.command = command;
}
/**
* 通知后厨进行烧烤
*/
public void notifyCommand(){
command.excuteCommand();
}
}
客户端:
/**
* @author Shier
* CreateTime 2023/5/20 15:25
*/
public class BarBecuerClient {
public static void main(String[] args) {
//开店前工作
Barbecuer barbecuer = new Barbecuer(); // 后厨厨师
BakeMuttonCommand muttonCommand = new BakeMuttonCommand(barbecuer); // 厨师进行烤羊肉串
BakeChickenWingCommand chickenWingCommand = new BakeChickenWingCommand(barbecuer); // 厨师烤鸡翅
Waiter waiter = new Waiter(); // 服务员
// 开门营业
waiter.setCommand(muttonCommand); // 下单烤羊肉串
waiter.notifyCommand(); // 通知厨师烤羊肉串
waiter.setCommand(chickenWingCommand); // 下单烤鸡翅
waiter.notifyCommand(); // 通知厨师烤鸡翅
}
}
结果:
烤羊肉串!
考鸡翅!
但是你有没有发现问题呀,客户每次下单完成一个服务员就要立马去通知厨师,这样子你的服务员很快就会走了,没人愿意来帮你这样干(累死累活挣几个钱)
存在以下几个问题:
- 第一,真实的情况其实并不是用户点一个菜,服务员就通知 厨房去做一个,那样不科学,应该是点完烧烤后,服务员一次通知制作;
- 第 二,如果此时鸡翅没了,不应该是客户来判断是否还有,客户哪知道有没有呀,应该是服务员或烤肉串者来否决这个请求;
- 第三,客户到底点了哪些烧烤或饮料,这是需要记录日志的,以备收费,也包括后期的统计;
- 第四,客 户完全有可能因为点的肉串太多而考虑取消一些还没有制作的肉串。这些问题都需要得到解决。
解决办法:
重构 一 下服务员 Waiter 类 , 尝试改一下 。 将 private Command command;改成一个ArrayList,就能解决了
/**
* @author Shier
* CreateTime 2023/5/20 15:24
* 服务员
*/
public class Waiter {
/**
* 增加存放具体命令的容器
*/
private ArrayList<Command> orders = new ArrayList<Command>();
/**
* 下单
*
* @param commands
*/
public void setOrders(Command commands) {
String name = commands.getClass().getSimpleName();
if (name.equals("BakeChickenWingCommand")) {
System.out.println("服务员:鸡翅没有了,请点别的烧烤");
} else {
this.orders.add(commands);
System.out.println("增加订单:" + name + " 时间:" + getNowTime());
}
}
//取消订单
public void cancelOrder(Command command) {
String className = command.getClass().getSimpleName();
// 可以取消部门订单
orders.remove(command);
System.out.println("取消订单:" + className + " 时间:" + getNowTime());
}
//通知执行
public void notifyCommand() {
// 根据用户点的烧烤订单通知后厨制作
for (Command command : orders) {
command.excuteCommand();
}
}
private String getNowTime() {
SimpleDateFormat formatter = new SimpleDateFormat("HH:mm:ss");
return formatter.format(new Date()).toString();
}
}
/**
* @author Shier
* CreateTime 2023/5/20 15:25
*/
public class BarBecuerClient {
public static void main(String[] args) {
//开店前工作
Barbecuer barbecuer = new Barbecuer(); // 后厨厨师
BakeMuttonCommand muttonCommand = new BakeMuttonCommand(barbecuer); // 厨师进行烤羊肉串
BakeChickenWingCommand chickenWingCommand = new BakeChickenWingCommand(barbecuer); // 厨师烤鸡翅
Waiter waiter = new Waiter(); // 服务员
System.out.println("开门营业");
waiter.setOrders(muttonCommand); // 下单烤羊肉串
waiter.setOrders(muttonCommand); // 下单烤羊肉串
waiter.setOrders(muttonCommand); // 下单烤羊肉串
waiter.setOrders(muttonCommand); // 下单烤羊肉串
waiter.setOrders(muttonCommand); // 下单烤羊肉串
waiter.cancelOrder(muttonCommand); // 取消一穿羊肉串
waiter.setOrders(chickenWingCommand); // 下单烤鸡翅
System.out.println("点菜完成,通知后厨烧菜");
waiter.notifyCommand(); // 通知厨师
}
}
输出结果:
这样就是比较符合常理的烧烤点菜,说着也饿了,下面再来总结一个命令模式
命令模式把请求一个操作的对象与知道怎么执行一个 操作的对象分割开
一切都要根据实际的业务情况来开发,不要过度开发,在敏捷开发原则中:
不要为代码添加基于猜测的、实际不需要的功能。如果不清楚一个系统是否需要命令模式,一般就不要着急去实现它,事实上,在需要的时候通过重构实现这个模式并不困 难,只有在真正需要如撤销/恢复操作等功能时,把原来的代码重构为命令模式才有意义。
命令模式的优点:
命令模式的缺点:
命令模式适用于以下场景:
命令模式的优点:
命令模式的缺点:
命令模式适用于以下场景: