---
theme: nico
---
我正在参加「掘金·启航计划」
# 前言
>⛱ 小猪妖有个梦想,那就是离开浪浪山,出去闯荡,
>
>但是在短篇当中小猪妖并没有走出浪浪山,和我们普通人一样,都走不出自己的浪浪山。
>
>我知道浪浪山的那边还是山,可我还是想去看看。
>
>--《中国奇谭》
![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/ac0b5f47c630444290e2fd880f2fa091~tplv-k3u1fbpfcp-zoom-1.image)
# 一、入门
![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/28634e06aed74be5b9859d79fcc981f5~tplv-k3u1fbpfcp-zoom-1.image)
## 1.1 概述
将一个请求封装为一个对象,使发出请求的责任和执行请求的责任分割开。这样两者之间通过命令对象进行沟通,这样方便将命令对象进行存储、传递、调用、增加与管理。
命令模式的一个核心就是 "解耦" 。
那是谁发生了耦合呢?
答:请求发起者和请求接收者的耦合。典型的例子就是餐厅点餐,由顾客点好菜单,这个菜单就是"命令",然后 "接收者" 厨师开始制作;倘若没有解耦,那就是由顾客直接跟厨师交代事情。命令模式非常像消息队列,有消息生产者、消费者、以及消息命令。
本文以《小妖怪的夏天》中的场景来理解下命令模式。
>大王想吃唐僧肉,于是开了场项目立项大会,将这个命令分发下去,教头又把命令分发给不同的小弟,对于大王来说,它只需要关心消息发送下去了,而接收到消息的小妖怪们,就开始执行不同的命令,有烧水的、有制作箭头的、有砍菜的~
![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/0387e3c8f1fc412ea2a786e87225c95a~tplv-k3u1fbpfcp-zoom-1.image)
命令模式是一种数据驱动的设计模式,将请求命令封装成一个命令对象,由调用者将这个命令对象发给执行者。
## 1.2 结构
![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/de35df4429a54b18b4b72ecc4bf59159~tplv-k3u1fbpfcp-zoom-1.image)
命令模式包含以下主要角色:
- 抽象命令类(Command)角色: 定义命令的接口,声明执行的方法。
- 具体命令(Concrete Command)角色:具体的命令,实现命令接口;通常会持有接收者,并调用接收者的功能来完成命令要执行的操作。
- 实现者/接收者(Receiver)角色: 接收者,真正执行命令的对象。任何类都可能成为一个接收者,只要它能够实现命令要求实现的相应功能。
- 调用者/请求者(Invoker)角色: 要求命令对象执行请求,通常会持有命令对象,可以持有很多的命令对象。这个是客户端真正触发命令并要求命令执行相应操作的地方,也就是说相当于使用命令对象的入口。
# 二、案例实战
对小妖怪的案例做个代码演示。
实现者角色(无名的小妖怪),也可以定义一个抽象类,继承实现。
```
/**
* 一只无名的小妖怪
* @Author xiaolei
* @Date 2023/2/28 13:19
**/
public class Nobody {
/**
* 执行烧水工作
*/
public void execBoilWater(){
System.out.println("大王让我来烧水!");
}
/**
* 执行制箭工作
*/
public void execMakeArrow(){
System.out.println("大王让我做1000支箭!");
}
}
```
抽象命令接口
```
/**
* 小妖怪抽象命令接口
* @Author xiaolei
* @Date 2023/2/28 13:12
**/
public interface GoblinCommand {
void execute();
}
```
具体命令角色
```
/**
* 烧水具体命令
* 具体命令中需要传入一个具体干活的对象
* @Author xiaolei
* @Date 2023/2/28 13:23
**/
public class BoilWaterComman implements GoblinCommand {
private Nobody nobody;
/**
* 这里的 nobody 也可以抽象出一个抽象类
* @param nobody
*/
public BoilWaterComman(Nobody nobody){
this.nobody = nobody;
}
/**
* 该命令只负责让接收者干烧水的事情
*/
@Override
public void execute() {
nobody.execBoilWater();
}
}
// 制作箭头具体命令
public class MakeArrowCommand implements GoblinCommand{
private Nobody nobody;
/**
* 这里的 nobody 也可以抽象出一个抽象类
* @param nobody
*/
public MakeArrowCommand(Nobody nobody){
this.nobody = nobody;
}
/**
* 该命令只负责让接收者干制箭的事情
*/
@Override
public void execute() {
nobody.execMakeArrow();
}
}
```
调用者角色 (大王)
```
/**
* 大王 : 命令的发起者。
* 注意它是跟命令直接交互的,而命令是跟干活的直接交互的,所以需要传入 命令
* the king of the forest
* 森林之王
* @Author xiaolei
* @Date 2023/2/28 13:28
**/
public class TheKingInvoker {
private GoblinCommand command;
/**
* 根据命令创建调用者
* @param command
*/
public TheKingInvoker(GoblinCommand command){
this.command = command;
}
/**
* 大王开始吩咐命令
*/
public void action(){
command.execute();
}
}
```
客户端测试
```
public class ClientTest {
public static void main(String[] args) {
// 小妖怪上线
Nobody nobody = new Nobody();
BoilWaterComman boilWaterComman = new BoilWaterComman(nobody);
// 大王吩咐执行烧水命令
TheKingInvoker theKingInvoker = new TheKingInvoker(boilWaterComman);
// 开始执行
theKingInvoker.action();
System.out.println("===================================");
MakeArrowCommand makeArrowCommand = new MakeArrowCommand(nobody);
// 大王吩咐执行制箭命令
TheKingInvoker theKingInvoker2 = new TheKingInvoker(makeArrowCommand);
// 开始执行
theKingInvoker2.action();
}
}
```
![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/21c1cf20d49243b79899d0b50e8e31f9~tplv-k3u1fbpfcp-zoom-1.image)
# 三、总结
## 3.1 使用场景
- 系统需要将请求调用者和请求接收者解耦,使得调用者和接收者不直接交互。
- 系统需要在不同的时间指定请求、将请求排队和执行请求。
- 系统需要支持命令的撤销(Undo)操作和恢复(Redo)操作。
## 3.2 优缺点
**优点:**
- 降低系统的耦合度。命令模式能将调用操作的对象与实现该操作的对象解耦。
- 增加或删除命令非常方便。采用命令模式增加与删除命令不会影响其他类,它满足“开闭原则”,对扩展比较灵活。
- 可以实现宏命令。命令模式可以与组合模式结合,将多个命令装配成一个组合命令,即宏命令。
- 方便实现 Undo 和 Redo 操作。命令模式可以与后面介绍的备忘录模式结合,实现命令的撤销与恢复。
**缺点:**
- 使用命令模式可能会导致某些系统有过多的具体命令类。
- 系统结构更加复杂。
命令模式的入门就到这了,感觉命令模式在业务的使用应该很少,可能在重构的时候会考虑吧,一句话就是以数据驱动来完成调用者与执行者的解耦工作。
我想离开浪浪山,那你要变得更强呀~~
Copyright © 2024 妖气游戏网 www.17u1u.com All Rights Reserved