学习设计模式之命令模式

Posted 南淮北安

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了学习设计模式之命令模式相关的知识,希望对你有一定的参考价值。

文章目录

一、定义


命令模式在我们通常的互联网开发中相对来说用的比较少,但这样的模式在我们的日常中却经常使用到,那就是Ctrl+C、Ctrl+V。当然如果你开发过一些桌面应用,也会感受到这样设计模式的应用场景。从这样的模式感受上,可以想到这是把逻辑实现与操作请求进行分离,降低耦合方便扩展。

命令模式是行为模式中的一种,以数据驱动的方式将命令对象,可以使用构造函数的方式传递给调用者。调用者再提供相应的实现为命令执行提供操作方法

可能会感觉这部分有一些饶,可以通过对代码的实现进行理解,在通过实操来熟练。

在这个设计模式的实现过程中有如下几个比较重要的点:

  • 抽象命令类;声明执行命令的接口和方法
  • 具体的命令实现类;接口类的具体实现,可以是一组相似的行为逻辑
  • 实现者;也就是为命令做实现的具体实现类
  • 调用者;处理命令、实现的具体操作者,负责对外提供命令服务

二、问题背景


在这个案例中我们模拟在餐厅中点餐交给厨师👨‍🍳烹饪的场景

命令场景的核心的逻辑是调用方与不需要去关心具体的逻辑实现,在这个场景中也就是点餐人员只需要把需要点的各种菜系交个小二就可以,小二再把各项菜品交给各个厨师进行烹饪。也就是点餐人员不需要跟各个厨师交流,只需要在统一的环境里下达命令就可以。

在这个场景中可以看到有不同的菜品;山东(鲁菜)、四川(川菜)、江苏(苏菜)、广东(粤菜)、福建(闽菜)、浙江(浙菜)、湖南(湘菜),每种菜品都会有不同的厨师👩‍🍳进行烹饪。而客户并不会去关心具体是谁烹饪,厨师也不会去关心谁点的餐。客户只关心早点上菜,厨师只关心还有多少个菜要做。而这中间的衔接的过程,由小二完成。

那么在这样的一个模拟场景下,可以先思考🤔哪部分是命令模式的拆解,哪部分是命令的调用者以及命令的实现逻辑。

三、违背设计模式的设计实现

不考虑设计模式的情况下,在做这样一个点单系统,有一个类就够了

像是这样一个复杂的场景,如果不知道设计模式直接开发,也是可以达到目的的。但对于后续的各项的菜品扩展、厨师实现以及如何调用上会变得非常耦合难以扩展。

1. 工程结构

itstack-demo-design-14-01
└── src
    └── main
        └── java
            └── org.itstack.demo.design
                └── XiaoEr.java

这里只有一个饭店小二的类,通过这样的一个类实现整个不同菜品的点单逻辑

2. 代码实现

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()

从上面可以看到有比较多的if语句判断类型进行添加菜品,那么对于这样的代码后续就需要大量的经历进行维护,同时可能实际的逻辑要比这复杂的多。都写在这样一个类里会变得耦合的非常严重。

四、问题改进

接下来使用命令模式来进行代码优化,也算是一次很小的重构。

命令模式可以将上述的模式拆解三层大块,命令、命令实现者、命令的调用者,当有新的菜品或者厨师扩充时候就可以在指定的类结构下进行实现添加即可,外部的调用也会非常的容易扩展

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. 代码实现

(1)抽象命令定义(菜品接口)

/**
 * 菜系
 * 01、山东(鲁菜)——宫廷最大菜系,以孔府风味为龙头。
 * 02、四川(川菜)——中国最有特色的菜系,也是民间最大菜系。
 * 03、江苏(苏菜)——宫廷第二大菜系,古今国宴上最受人欢迎的菜系。
 * 04、广东(粤菜)——国内民间第二大菜系,国外最有影响力的中国菜系,可以代表中国。
 * 05、福建(闽菜)——客家菜的代表菜系。
 * 06、浙江(浙菜)——中国最古老的菜系之一,宫廷第三大菜系。
 * 07、湖南(湘菜)——民间第三大菜系。
 * 08、安徽(徽菜)——徽州文化的典型代表。
 */
public interface ICuisine 

    void cook(); // 烹调、制作



这是命令接口类的定义,并提供了一个烹饪方法。后面会选四种菜品进行实现
(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()

命令的实现过程可以是按照逻辑进行添加补充,目前这里抽象的比较简单,只是模拟一个烹饪的过程,相当于同时厨师进行菜品烹饪。

(3)抽象实现者定义(厨师接口)

public interface ICook 

    void doCooking();



这里定义的是具体的为命令的实现者,这里也就是菜品对应的厨师烹饪的指令实现。

(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("四川厨师,烹饪川菜,中国最有特色的菜系,也是民间最大菜系。");
    



(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. 测试验证

@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);,这里分别添加了四种菜品,给小二。

最后是下单,这个是具体命令实现的操作,相当于把小二手里的菜单传递给厨师。当然这里也可以提供删除和撤销,也就是客户取消了自己的某个菜品

五、总结

从以上的内容和例子可以感受到,命令模式的使用场景需要分为三个比较大的块;命令、实现、调用者,而这三块内容的拆分也是选择适合场景的关键因素,经过这样的拆分可以让逻辑具备单一职责的性质,便于扩展。

通过这样的实现方式与if语句相比,降低了耦合性也方便其他的命令和实现的扩展。但同时这样的设计模式也带来了一点问题,就是在各种命令与实现的组合下,会扩展出很多的实现类,需要进行管理。

设计模式的学习一定要勤加练习,哪怕最开始是模仿实现也是可以的,多次的练习后再去找到一些可以优化的场景,并逐步运用到自己的开发中。提升自己对代码的设计感觉,让代码结构更加清晰易扩展

以上是关于学习设计模式之命令模式的主要内容,如果未能解决你的问题,请参考以下文章

大话设计模式读书笔记--18.命令模式

面向对象编程思想-命令模式

设计模式之静态代理模式

设计模式学习笔记之命令模式

设计模式之命令模式学习理解

设计模式学习之命令模式