重学Java设计模式-行为型模式-命令模式
内容摘自:https://bugstack.cn/md/develop/design-pattern/2020-06-21-重学 Java 设计模式《实战命令模式》.html#重学-java-设计模式-实战命令模式「模拟高档餐厅八大菜系-小二点单厨师烹饪场景」
命令模式介绍
- 图片来自:https://refactoringguru.cn/design-patterns/command(opens new window)
命令模式在我们通常的互联网开发中相对来说用的比较少,但这样的模式在我们的日常中却经常使用到,那就是Ctrl+C
、Ctrl+V
。当然如果你开发过一些桌面应用,也会感受到这样设计模式的应用场景。从这样的模式感受上,可以想到这是把逻辑实现与操作请求进行分离,降低耦合方便扩展。
命令模式是行为模式中的一种,以数据驱动的方式将命令对象
,可以使用构造函数的方式传递给调用者。调用者再提供相应的实现为命令执行提供操作方法。可能会感觉这部分有一些饶,可以通过对代码的实现进行理解,在通过实操来熟练。
在这个设计模式的实现过程中有如下几个比较重要的点;
- 抽象命令类;声明执行命令的接口和方法
- 具体的命令实现类;接口类的具体实现,可以是一组相似的行为逻辑
- 实现者;也就是为命令做实现的具体实现类
- 调用者;处理命令、实现的具体操作者,负责对外提供命令服务
案例场景模拟
在这个案例中我们模拟在餐厅中点餐交给厨师👨🍳烹饪的场景
命令场景的核心的逻辑是调用方与不需要去关心具体的逻辑实现,在这个场景中也就是点餐人员只需要把需要点的各种菜系交个小二
就可以,小二再把各项菜品交给各个厨师进行烹饪。也就是点餐人员不需要跟各个厨师交流,只需要在统一的环境里下达命令就可以。
在这个场景中可以看到有不同的菜品;山东(鲁菜)、四川(川菜)、江苏(苏菜)、广东(粤菜)、福建(闽菜)、浙江(浙菜)、湖南(湘菜),每种菜品都会有不同的厨师👩🍳进行烹饪。而客户并不会去关心具体是谁烹饪,厨师也不会去关心谁点的餐。客户只关心早点上菜,厨师只关心还有多少个菜要做。而这中间的衔接的过程,由小二完成。
那么在这样的一个模拟场景下,可以先思考🤔哪部分是命令模式的拆解,哪部分是命令的调用者以及命令的实现逻辑。
命令模式重构代码
接下来使用命令模式来进行代码优化,也算是一次很小的重构。
命令模式可以将上述的模式拆解三层大块,命令、命令实现者、命令的调用者,当有新的菜品或者厨师扩充时候就可以在指定的类结构下进行实现添加即可,外部的调用也会非常的容易扩展。
1. 工程结构
itstack-demo-design-14-02
└── src
├── main
│ └── java
│ └── org.itstack.demo.design
│ ├── cook
│ │ ├── impl
│ │ │ ├── GuangDongCook.java
│ │ │ ├── JiangSuCook.java
│ │ │ ├── ShanDongCook.java
│ │ │ └── SiChuanCook.java
│ │ └── ICook.java
│ ├── cuisine
│ │ ├── impl
│ │ │ ├── GuangDoneCuisine.java
│ │ │ ├── JiangSuCuisine.java
│ │ │ ├── ShanDongCuisine.java
│ │ │ └── SiChuanCuisine.java
│ │ └── ICuisine.java
│ └── XiaoEr.java
└── test
└── java
└── org.itstack.demo.test
└── ApiTest.java
命令模式模型结构
- 从上图可以看到整体分为三大块;命令实现(菜品)、逻辑实现(厨师)、调用者(小二),以上这三面的实现就是命令模式的核心内容。
- 经过这样的拆解就可以非常方面的扩展菜品、厨师,对于调用者来说这部分都是松耦合的,在整体的框架下可以非常容易加入实现逻辑。
2. 代码实现
2.1 抽象命令定义(菜品接口)
/**
* 博客:https://bugstack.cn - 沉淀、分享、成长,让自己和他人都能有所收获!
* 公众号:bugstack虫洞栈
* Create by 小傅哥(fustack) @2020
*
* 菜系
* 01、山东(鲁菜)——宫廷最大菜系,以孔府风味为龙头。
* 02、四川(川菜)——中国最有特色的菜系,也是民间最大菜系。
* 03、江苏(苏菜)——宫廷第二大菜系,古今国宴上最受人欢迎的菜系。
* 04、广东(粤菜)——国内民间第二大菜系,国外最有影响力的中国菜系,可以代表中国。
* 05、福建(闽菜)——客家菜的代表菜系。
* 06、浙江(浙菜)——中国最古老的菜系之一,宫廷第三大菜系。
* 07、湖南(湘菜)——民间第三大菜系。
* 08、安徽(徽菜)——徽州文化的典型代表。
*/
public interface ICuisine {
void cook(); // 烹调、制作
}
- 这是命令接口类的定义,并提供了一个烹饪方法。后面会选四种菜品进行实现。
2.2 具体命令实现(四种菜品)
广东(粤菜)
public class GuangDoneCuisine implements ICuisine {
private ICook cook;
public GuangDoneCuisine(ICook cook) {
this.cook = cook;
}
public void cook() {
cook.doCooking();
}
}
江苏(苏菜)
public class JiangSuCuisine implements ICuisine {
private ICook cook;
public JiangSuCuisine(ICook cook) {
this.cook = cook;
}
public void cook() {
cook.doCooking();
}
}
山东(鲁菜)
public class ShanDongCuisine implements ICuisine {
private ICook cook;
public ShanDongCuisine(ICook cook) {
this.cook = cook;
}
public void cook() {
cook.doCooking();
}
}
四川(川菜)
public class SiChuanCuisine implements ICuisine {
private ICook cook;
public SiChuanCuisine(ICook cook) {
this.cook = cook;
}
public void cook() {
cook.doCooking();
}
}
- 以上是四种菜品的实现,在实现的类中都有添加了一个厨师类(
ICook
),并通过这个类提供的方法进行操作命令(烹饪菜品)cook.doCooking()
。 - 命令的实现过程可以是按照逻辑进行添加补充,目前这里抽象的比较简单,只是模拟一个烹饪的过程,相当于同时厨师进行菜品烹饪。
2.3 抽象实现者定义(厨师接口)
public interface ICook {
void doCooking();
}
- 这里定义的是具体的为命令的实现者,这里也就是菜品对应的厨师烹饪的指令实现。
2.4 实现者具体实现(四类厨师)
粤菜,厨师
public class GuangDongCook implements ICook {
private Logger logger = LoggerFactory.getLogger(ICook.class);
public void doCooking() {
logger.info("广东厨师,烹饪鲁菜,宫廷最大菜系,以孔府风味为龙头");
}
}
苏菜,厨师
public class JiangSuCook implements ICook {
private Logger logger = LoggerFactory.getLogger(ICook.class);
public void doCooking() {
logger.info("江苏厨师,烹饪苏菜,宫廷第二大菜系,古今国宴上最受人欢迎的菜系。");
}
}
鲁菜,厨师
public class ShanDongCook implements ICook {
private Logger logger = LoggerFactory.getLogger(ICook.class);
public void doCooking() {
logger.info("山东厨师,烹饪鲁菜,宫廷最大菜系,以孔府风味为龙头");
}
}
苏菜,厨师
public class SiChuanCook implements ICook {
private Logger logger = LoggerFactory.getLogger(ICook.class);
public void doCooking() {
logger.info("四川厨师,烹饪川菜,中国最有特色的菜系,也是民间最大菜系。");
}
}
- 这里是四类不同菜品的厨师👩🍳,在这个实现的过程是模拟打了日志,相当于通知了厨房里具体的厨师进行菜品烹饪。
- 从以上可以看到,当我们需要进行扩从的时候是可以非常方便的进行添加的,每一个类都具备了单一职责原则。
2.5 调用者(小二)
public class XiaoEr {
private Logger logger = LoggerFactory.getLogger(XiaoEr.class);
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();
}
}
- 在调用者的具体实现中,提供了菜品的添加和菜单执行烹饪。这个过程是命令模式的具体调用,通过外部将菜品和厨师传递进来而进行具体的调用。
3. 测试验证
3.1 编写测试类
@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);
,这里分别添加了四种菜品,给小二。 - 最后是下单,这个是具体命令实现的操作,相当于把小二手里的菜单传递给厨师。当然这里也可以提供删除和撤销,也就是客户取消了自己的某个菜品。
3.2 测试结果
22:12:13.056 [main] INFO org.itstack.demo.design.cook.ICook - 广东厨师,烹饪鲁菜,宫廷最大菜系,以孔府风味为龙头
22:12:13.059 [main] INFO org.itstack.demo.design.cook.ICook - 江苏厨师,烹饪苏菜,宫廷第二大菜系,古今国宴上最受人欢迎的菜系。
22:12:13.059 [main] INFO org.itstack.demo.design.cook.ICook - 山东厨师,烹饪鲁菜,宫廷最大菜系,以孔府风味为龙头
22:12:13.059 [main] INFO org.itstack.demo.design.cook.ICook - 四川厨师,烹饪川菜,中国最有特色的菜系,也是民间最大菜系。
Process finished with exit code 0
- 从上面的测试结果可以看到,我们已经交给调用者(小二)的点单,由不同的厨师具体实现(烹饪)。
- 此外当我们需要不同的菜品时候或者修改时候都可以非常方便的添加和修改,在具备单一职责的类下,都可以非常方便的扩展。