设计模式之命令模式
命令模式
命令模式在我们通常的互联网开发中相对来说用的比较,但这样的模式在我们的日常中却经常使用到,那就是Ctrl+C
、Ctrl+V
。当然如果你开发过一些桌面应用,也会感受到这样设计模式的应用场景。从这样的模式感受上,可以想到这是把逻辑实现与操作请求进行分离,降低耦合方便扩展。
命令模式是行为模式中的一中,以数据驱动的方式将命令对象
,可以使用构造函数的方式传递给调用者。调用者再提供相应的实现为命令执行提供操作方法。可能会感觉这部分有一些绕,可以通过代码的实现进行理解,再通过实操来熟练。
在这个设计模式的实现过程中由如下几个比较重要的点:
- 抽象命令类:声明执行命令的接口和方法。
- 具体的命令实现类:接口类的具体实现,可以是一组相似的行为逻辑。
- 实现者:也就是为命令做实现的具体实现类。
- 调用者:处理命令、实现的具体操作者,负责对外提供命令服务。
案例场景模拟
在这个案例中我们模拟在餐厅中点餐交给厨师烹饪的场景
命令场景的核心的逻辑是调用方不需要去关心具体的逻辑实现,在这个场景中也就是点餐人员只需要把需要点的各种菜系交给小二
就行、小二在把各项菜品交给各个厨师进行烹饪。也就是点餐人员不需要跟各个初始交流,只需要在统一的环境里下达命令就可以。
在这个场景中可以看到有不同的菜品,每种菜品都会有不同的厨师进行烹饪。而客户并不会去关心是谁在烹饪,厨师也不会去关心是谁在点单。客户只关心早点上菜,厨师只关心还有多少菜需要做。而中间的衔接的过程,由小二完成。
常规实现
public class XiaoEr {
private Logger logger = LoggerFactory.getLogger(XiaoEr.class);
private Map<Integer, String> cuisineMap = new ConcurrentHashMap<Integer, String>();
public void order(int cuisine) {
// 广东(粤菜)
if (1 == cuisine) {
cuisineMap.put(1, "广东厨师,烹饪鲁菜,宫廷最大菜系,以孔府风味为龙头");
}
// 江苏(苏菜)
if (2 == cuisine) {
cuisineMap.put(2, "江苏厨师,烹饪苏菜,宫廷第二大菜系,古今国宴上最受人欢迎的菜系。");
}
// 山东(鲁菜)
if (3 == cuisine) {
cuisineMap.put(3, "山东厨师,烹饪鲁菜,宫廷最大菜系,以孔府风味为龙头.");
}
// 四川(川菜)
if (4 == cuisine) {
cuisineMap.put(4, "四川厨师,烹饪川菜,中国最有特色的菜系,也是民间最大菜系。");
}
}
public void placeOrder() {
logger.info("菜单:{}", JSON.toJSONString(cuisineMap));
}
}
- 在这个类的实现中提供了两个方法,一个方法用于点单添加菜品
order()
,另外一个方法展示菜品的信息placeOrder()
。 - 从上面可以看到有比较多的id语句判断类型进行添加菜品,那么对于这样的代码后续就需要大量的进行维护,同时可能实际的逻辑要比者复杂的多,都写在这样的类里会变的耦合的非常严重。
使用命令模式
命令模式可以将上述的模式拆解为三层,命令、命令实现者、命令的调用者,当有新的菜品或者厨师扩充的时候就可以在指定的类结构下进行实现添加即可,外部的调用也会非常的容易扩展。
命令模式结构模型
- 从上图可以看出整体分为三大部分:命令实现(菜品)、逻辑实现(厨师)、调用者(小二),以上这三个的实现就是命令模式的核心内容。
- 经过这样的拆解就可以非常方便的扩展菜品、厨师,对于调用者来说这部分都是松耦合的,在整体的框架下可以非常容易的加入实现逻辑。
代码实现
抽象命令接口(菜品接口)
public interface ICuisine {
void cook(); // 烹调、制作
}
- 这是命令接口类的定义,并提供了一个烹饪方法。后面会选四种菜品进行实现。
具体命令实现
广东菜
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();
}
}
四川菜
ublic class SiChuanCuisine implements ICuisine {
private ICook cook;
public SiChuanCuisine(ICook cook) {
this.cook = cook;
}
public void cook() {
cook.doCooking();
}
}
- 以上是四种菜品的实现,在实现的类中都有添加了一个厨师类
ICook
,并通过这个类提供的方法进行操作命令(烹饪菜品)cook.doCooking()
- 命令的实现过程可以是按照逻辑进行添加补充,目前这里的抽象比较简单,只是模拟一个烹饪的过程,相当于同时厨师进行菜品烹饪。
抽象实现者接口(厨师接口)
public interface ICook {
void doCooking();
}
实现者具体实现(四类厨师)
广东厨师
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("四川厨师,烹饪川菜,中国最有特色的菜系,也是民间最大菜系。");
}
}
- 这里是四类不同菜品的厨师,在这个实现的过程是模拟了打了日志,相当于通知了厨房里具体的厨师进行烹饪。
- 从以上来看,当我们需要进行扩容的时候是可以非常方便的进行添加的,每个类都具备了单一职责原则。
调用者(小二)
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();
}
}
- 在调用者的具体实现中,提供了菜品的添加和菜单执行烹饪。这个过程是命令模式的具体调用,通过外部将菜品和厨师传递进来而进行具体的调用。
测试验证
测试类
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(guangDongCuisine)
,这里分别添加了四种菜系给小二。 - 最后是下单,这个是具体命令的实现过程,相当于把小二手里的菜单传给厨师,当然这里也可以提供删除和撤销,也就是客户自己取消了某个菜品。
result:
19:50:37.145 [main] INFO org.itstack.demo.design.cook.ICook - 广东厨师,烹饪鲁菜,宫廷最大菜系,以孔府风味为龙头
19:50:37.165 [main] INFO org.itstack.demo.design.cook.ICook - 江苏厨师,烹饪苏菜,宫廷第二大菜系,古今国宴上最受人欢迎的菜系。
19:50:37.165 [main] INFO org.itstack.demo.design.cook.ICook - 山东厨师,烹饪鲁菜,宫廷最大菜系,以孔府风味为龙头
19:50:37.165 [main] INFO org.itstack.demo.design.cook.ICook - 四川厨师,烹饪川菜,中国最有特色的菜系,也是民间最大菜系。
- 从上面的测试结果可以看到,我们已经交给小二的点单,由不同的厨师实现烹饪。
- 此外当我们需要不同的菜品时候或者修改的时候都可以非常方便的添加和修改,在具备单一职责原则的类下,都可以非常方便的进行扩展。
总结
- 从以上的例子中可以看到,命令模式的使用场景需要分为三个比较大的快:
命令
、实现
、调用者
。二者三个内容的拆分也是选择适合场景的关键因素,经过这样的拆分可以让逻辑具备单一职责的性质,便于扩展。 - 通过这样的实现方式与if语句相比,降低了耦合性也方便其他命令和实现的扩展。但同时这样的设计模式也带来一些问题,就是在各种命令与实现的组合下,会扩展出很多的实现类,需要进行管理。
评论区