命令模式
# 定义
命令模式是行为模式中的一种,以数据驱动的方式将命令对象,可以使用构造函数的方式传递给调用者。调用者再提供相应的实现为命令执行提供操作方法。可能会感觉这部分有一些饶,可以通过对代码的实现进行理解,在通过实操来熟练。
在这个设计模式的实现过程中有如下几个比较重要的点;
- 抽象命令类:声明执行命令的接口和方法
- 具体的命令实现类:接口类的具体实现,可以是一组相似的行为逻辑
- 实现者:也就是为命令做实现的具体实现类
- 调用者:处理命令、实现的具体操作者,负责对外提供命令服务
优点:
1. 降低了系统耦合度。
2. 新的命令可以很容易添加到系统中去。
缺点:使用命令模式可能会导致某些系统有过多的具体命令类。
# 实践
这里模拟在餐厅中点餐交给厨师👨🍳烹饪的场景。
>命令场景的核心的逻辑是调用方与不需要去关心具体的逻辑实现,在这个场景中也就是点餐人员只需要把需要点的各种菜系交个小二就可以,小二再把各项菜品交给各个厨师进行烹饪。也就是点餐人员不需要跟各个厨师交流,只需要在统一的环境里下达命令就可以。
>
>在这个场景中可以看到有不同的菜品;山东(鲁菜)、四川(川菜)、江苏(苏菜)、广东(粤菜)、福建(闽菜)、浙江(浙菜)、湖南(湘菜),每种菜品都会有不同的厨师👩🍳进行烹饪。而客户并不会去关心具体是谁烹饪,厨师也不会去关心谁点的餐。客户只关心早点上菜,厨师只关心还有多少个菜要做。而这中间的衔接的过程,由小二完成。
>
>那么在这样的一个模拟场景下,可以先思考哪部分是命令模式的拆解,哪部分是命令的调用者以及命令的实现逻辑。
命令模式可以将上述的模式拆解三层大块,命令、命令实现者、命令的调用者,当有新的菜品或者厨师扩充时候就可以在指定的类结构下进行实现添加即可,外部的调用也会非常的容易扩展。
抽象命令定义(菜品接口)
```java
public interface ICuisine {
// 烹调、制作
void cook();
}
```
具体命令实现(四种菜品)
```java
// 广东(粤菜)
public class GuangDoneCuisine implements ICuisine {
private ICook cook;
public GuangDoneCuisine(ICook cook) {
this.cook = cook;
}
@Override
public void cook() {
cook.doCooking();
}
}
// 江苏(苏菜)
public class JiangSuCuisine implements ICuisine {
private ICook cook;
public JiangSuCuisine(ICook cook) {
this.cook = cook;
}
@Override
public void cook() {
cook.doCooking();
}
}
// 山东(鲁菜)
public class ShanDongCuisine implements ICuisine {
private ICook cook;
public ShanDongCuisine(ICook cook) {
this.cook = cook;
}
@Override
public void cook() {
cook.doCooking();
}
}
// 四川(川菜)
public class SiChuanCuisine implements ICuisine {
private ICook cook;
public SiChuanCuisine(ICook cook) {
this.cook = cook;
}
@Override
public void cook() {
cook.doCooking();
}
}
```
- 以上是四种菜品的实现,在实现的类中都有添加了一个厨师类(ICook),并通过这个类提供的方法进行操作命令(烹饪菜品)cook.doCooking()。
- 命令的实现过程可以是按照逻辑进行添加补充,目前这里抽象的比较简单,只是模拟一个烹饪的过程,相当于同时厨师进行菜品烹饪。
抽象实现者定义(厨师接口)
```java
public interface ICook {
void doCooking();
}
```
实现者具体实现(四类厨师)
```java
// 粤菜,厨师
@Slf4j
public class GuangDongCook implements ICook {
@Override
public void doCooking() {
log.info("广东厨师,烹饪鲁菜,宫廷最大菜系,以孔府风味为龙头");
}
}
// 苏菜,厨师
@Slf4j
public class JiangSuCook implements ICook {
@Override
public void doCooking() {
log.info("江苏厨师,烹饪苏菜,宫廷第二大菜系,古今国宴上最受人欢迎的菜系。");
}
}
// 鲁菜,厨师
@Slf4j
public class ShanDongCook implements ICook {
@Override
public void doCooking() {
log.info("山东厨师,烹饪鲁菜,宫廷最大菜系,以孔府风味为龙头");
}
}
// 川菜,厨师
@Slf4j
public class SiChuanCook implements ICook {
@Override
public void doCooking() {
log.info("四川厨师,烹饪川菜,中国最有特色的菜系,也是民间最大菜系。");
}
}
```
- 这里是四类不同菜品的厨师👩🍳,在这个实现的过程是模拟打了日志,相当于通知了厨房里具体的厨师进行菜品烹饪。
- 从以上可以看到,当我们需要进行扩从的时候是可以非常方便的进行添加的,每一个类都具备了单一职责原则。
调用者(小二)
```java
public class XiaoEr {
private List<ICuisine> cuisineList = new ArrayList<ICuisine>();
public void order(ICuisine cuisine) {
cuisineList.add(cuisine);
}
public synchronized void placeOrder() {
for (ICuisine cuisine : cuisineList) {
cuisine.cook();
}
cuisineList.clear();
}
}
```
- 在调用者的具体实现中,提供了菜品的添加和菜单执行烹饪。这个过程是命令模式的具体调用,通过外部将菜品和厨师传递进来而进行具体的调用。
测试类
```java
public class ApiTest {
@Test
public void test(){
// 菜系 + 厨师;广东(粤菜)、江苏(苏菜)、山东(鲁菜)、四川(川菜)
ICuisine guangDoneCuisine = new GuangDoneCuisine(new GuangDongCook());
JiangSuCuisine jiangSuCuisine = new JiangSuCuisine(new JiangSuCook());
ShanDongCuisine shanDongCuisine = new ShanDongCuisine(new ShanDongCook());
SiChuanCuisine siChuanCuisine = new SiChuanCuisine(new SiChuanCook());
// 点单
XiaoEr xiaoEr = new XiaoEr();
xiaoEr.order(guangDoneCuisine);
xiaoEr.order(jiangSuCuisine);
xiaoEr.order(shanDongCuisine);
xiaoEr.order(siChuanCuisine);
// 下单
xiaoEr.placeOrder();
}
}
```
- 这里可以主要观察菜品与厨师的组合;new GuangDoneCuisine(new GuangDongCook());,每一个具体的命令都拥有一个对应的实现类,可以进行组合。
- 当菜品和具体的实现定义完成后,由小二进行操作点单,xiaoEr.order(guangDoneCuisine);,这里分别添加了四种菜品,给小二。
- 最后是下单,这个是具体命令实现的操作,相当于把小二手里的菜单传递给厨师。当然这里也可以提供删除和撤销,也就是客户取消了自己的某个菜品。
测试结果
```
10:31:06.960 [main] INFO fun.lixj.design.cook.impl.GuangDongCook - 广东厨师,烹饪鲁菜,宫廷最大菜系,以孔府风味为龙头
10:31:06.962 [main] INFO fun.lixj.design.cook.impl.JiangSuCook - 江苏厨师,烹饪苏菜,宫廷第二大菜系,古今国宴上最受人欢迎的菜系。
10:31:06.962 [main] INFO fun.lixj.design.cook.impl.ShanDongCook - 山东厨师,烹饪鲁菜,宫廷最大菜系,以孔府风味为龙头
10:31:06.962 [main] INFO fun.lixj.design.cook.impl.SiChuanCook - 四川厨师,烹饪川菜,中国最有特色的菜系,也是民间最大菜系。
Process finished with exit code 0
```
- 从上面的测试结果可以看到,我们已经交给调用者(小二)的点单,由不同的厨师具体实现(烹饪)。
- 此外当我们需要不同的菜品时候或者修改时候都可以非常方便的添加和修改,在具备单一职责的类下,都可以非常方便的扩展。
# 总结
- 从以上的内容和例子可以感受到,命令模式的使用场景需要分为三个比较大的块;命令、实现、调用者,而这三块内容的拆分也是选择适合场景的关键因素,经过这样的拆分可以让逻辑具备单一职责的性质,便于扩展。
- 通过这样的实现方式与if语句相比,降低了耦合性也方便其他的命令和实现的扩展。但同时这样的设计模式也带来了一点问题,就是在各种命令与实现的组合下,会扩展出很多的实现类,需要进行管理。
- 设计模式的学习一定要勤加练习,哪怕最开始是模仿实现也是可以的,多次的练习后再去找到一些可以优化的场景,并逐步运用到自己的开发中。提升自己对代码的设计感觉,让代码结构更加清晰易扩展。
>GitHub地址:https://github.com/fuzhengwei/CodeGuide/blob/master/docs/md/develop/design-pattern/2020-06-21-%E9%87%8D%E5%AD%A6%20Java%20%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F%E3%80%8A%E5%AE%9E%E6%88%98%E5%91%BD%E4%BB%A4%E6%A8%A1%E5%BC%8F%E3%80%8B.md

【设计模式】命令模式