重学 Java 设计模式:实战命令模式「模拟高档餐厅八大菜系,小二点单厨师烹饪场景」


做者:小傅哥
博客:https://bugstack.cn - 原创系列专题文章html

沉淀、分享、成长,让本身和他人都能有所收获!😄

1、前言

锲而不舍的重要性java

初学编程每每都很懵,几乎在学习的过程当中会遇到各类各样的问题,哪怕别人那运行好好的代码,但你照着写完就报错。但好在你坚持住了,不然你可能看不到这篇文章。时间和成长就是相互关联着,你在哪条路上坚持走的久,就能看见那条的终点有多美,但若是你浪费了一次又一次努力的机会,那么你也会一样错过不少机遇,由于你的路换了。坚持学习、努力成长,持以恒的付出必定会有所收获。编程

学习方法的重要性设计模式

不会学习每每会耽误不少时间,又没有可观的收成。但不会学习有时候是由于形成的,尤为是学习视频、书籍资料、技术文档等,若是只是看了却不是实际操做验证,那么真的很难把别人的知识让本身吸取,即便是当时感受会了也很快就会忘记。时而也常常会有人找到你说;“这个我不知道,你先告诉我,事后我就学。”但事后你学了吗?微信

你愿意为一个知识盲区付出多长时间框架

你内心时而会蹦出这样的词吗;太难了我不会找我的帮一下吧放弃了放弃了,其实谁均可能遇到很很差解决的问题,也是能够去问去咨询的。但,若是在这以前你没有在本身的大脑中反复的寻找答案,那么你的大脑中就不会造成一个凸点的知识树,缺乏了这个学习过程也就缺乏了查阅各类资料给本身大脑填充知识的机会,哪怕是问到了答案最终也会因时间流逝而忘记。函数

2、开发环境

  1. JDK 1.8
  2. Idea + Maven
  3. 涉及工程三个,能够经过关注公众号bugstack虫洞栈,回复源码下载获取(打开获取的连接,找到序号18)
工程 描述
itstack-demo-design-14-01 使用一坨代码实现业务需求
itstack-demo-design-14-02 经过设计模式优化代码结构,增长扩展性和维护性

3、命令模式介绍

命令模式,图片来自 refactoringguru.cn

命令模式在咱们一般的互联网开发中相对来讲用的比较少,但这样的模式在咱们的平常中却常用到,那就是Ctrl+CCtrl+V。固然若是你开发过一些桌面应用,也会感觉到这样设计模式的应用场景。从这样的模式感觉上,能够想到这是把逻辑实现与操做请求进行分离,下降耦合方便扩展。学习

命令模式是行为模式中的一种,以数据驱动的方式将命令对象,可使用构造函数的方式传递给调用者。调用者再提供相应的实现为命令执行提供操做方法。可能会感受这部分有一些饶,能够经过对代码的实现进行理解,在经过实操来熟练。测试

在这个设计模式的实现过程当中有以下几个比较重要的点;优化

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

4、案例场景模拟

场景模拟;大餐厅点餐场景

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

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

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

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

5、用一坨坨代码实现

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

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

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语句判断类型进行添加菜品,那么对于这样的代码后续就须要大量的经历进行维护,同时可能实际的逻辑要比这复杂的多。都写在这样一个类里会变得耦合的很是严重。

6、命令模式重构代码

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

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

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
 *
 * 菜系
 * 0一、山东(鲁菜)——宫廷最大菜系,以孔府风味为龙头。
 * 0二、四川(川菜)——中国最有特点的菜系,也是民间最大菜系。
 * 0三、江苏(苏菜)——宫廷第二大菜系,古今国宴上最受人欢迎的菜系。
 * 0四、广东(粤菜)——国内民间第二大菜系,国外最有影响力的中国菜系,能够表明中国。
 * 0五、福建(闽菜)——客家菜的表明菜系。
 * 0六、浙江(浙菜)——中国最古老的菜系之一,宫廷第三大菜系。
 * 0七、湖南(湘菜)——民间第三大菜系。
 * 0八、安徽(徽菜)——徽州文化的典型表明。
 */
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
  • 从上面的测试结果能够看到,咱们已经交给调用者(小二)的点单,由不一样的厨师具体实现(烹饪)。
  • 此外当咱们须要不一样的菜品时候或者修改时候均可以很是方便的添加和修改,在具有单一职责的类下,均可以很是方便的扩展。

7、总结

  • 从以上的内容和例子能够感觉到,命令模式的使用场景须要分为三个比较大的块;命令实现调用者,而这三块内容的拆分也是选择适合场景的关键因素,通过这样的拆分可让逻辑具有单一职责的性质,便于扩展。
  • 经过这样的实现方式与if语句相比,下降了耦合性也方便其余的命令和实现的扩展。但同时这样的设计模式也带来了一点问题,就是在各类命令与实现的组合下,会扩展出不少的实现类,须要进行管理。
  • 设计模式的学习必定要勤加练习,哪怕最开始是模仿实现也是能够的,屡次的练习后再去找到一些能够优化的场景,并逐步运用到本身的开发中。提高本身对代码的设计感受,让代码结构更加清晰易扩展。

8、推荐阅读

相关文章
相关标签/搜索