• 设计模式学习笔记(十五)命令模式及在Spring JdbcTemplate 中的实现


    命令(Command)模式是指将请求封装成为一个对象,使发出请求和执行请求的责任分割开,方便将命令对象进行存储、传递、调用、增加与管理。

    也就是将发送者、接收者和调用命令封装成独立的对象,来供客户端调用。属于行为模式的一种。

    一、命令模式介绍#

    命令模式将发送者与接受者完全解耦,发送者与接收者之间没有直接的联系,发送者只需要如何发送请求,而不需要关心请求是如何完成的。下面就来看看命令模式的结构和实现:

    1.1 命令模式的结构#

    将调用者和实现者进行分离,其结构如下所示:

    image-20220405200443779

    • Command:抽象命令角色,声明执行命令的接口
    • Command1、Command2:具体命令角色,是抽象命令角色的具体实现类
    • ReceiverA、ReceiverB:具体实现,具体命令对象的真正实现者
    • Invoker:调用者,处理命令、实现命令的具体操作者,负责对外提供命令服务
    • Client:客户端

    1.2 命令模式的实现#

    根据上面的结构图,可以实现如下代码:

    /**
     * @description: 抽象命令类
     * @author: wjw
     * @date: 2022/4/5
     */
    public interface Command {
    
        public abstract void execute();
    }
    /**
     * @description: 命令具体实现类1
     * @author: wjw
     * @date: 2022/4/5
     */
    public class Command1 implements Command{
    
        private ReceiverA receiverA = new ReceiverA();
    
        @Override
        public void execute() {
            receiverA.action();
        }
    }
    /**
     * @description: 命令具体实现类2
     * @author: wjw
     * @date: 2022/4/5
     */
    public class Command1 implements Command{
    
        private ReceiverA receiverA = new ReceiverA();
    
        @Override
        public void execute() {
            receiverA.action();
        }
    }
    /**
     * @description: 接收者类A
     * @author: wjw
     * @date: 2022/4/5
     */
    public class ReceiverA {
    
        public void action() {
            System.out.println("我是ReceiverA");
        }
    }
    /**
     * @description: 具体实现者
     * @author: wjw
     * @date: 2022/4/5
     */
    public class ReceiverB {
    
        public void action() {
            System.out.println("我是ReceiverB");
        }
    }
    /**
     * @description: 命令调用者
     * @author: wjw
     * @date: 2022/4/5
     */
    public class Invoker {
    
        private Command command;
    
        public Invoker(Command command) {
            this.command = command;
        }
    
        public void setCommand(Command command) {
            this.command = command;
        }
    
        public void call() {
            System.out.println("调用者执行命令command");
            command.execute();
        }
    }
    /**
     * @description: 客户端
     * @author: wjw
     * @date: 2022/4/5
     */
    public class Client {
    
        public static void main(String[] args) {
            Command command1 = new Command1();
            Invoker invoker1 = new Invoker(command1);
            invoker1.call();
        }
    }
    

    最后的客户端运行结果为:

    调用者执行命令command
    我是ReceiverA
    

    下面来看看命令模式的应用场景

    二、命令模式的应用场景#

    2.1 Spring 框架中的 JdbcTemplate#

    本文选取的Spring版本是5.3.1,来看看JdbcTemplate类中的query()方法:

    image-20220405165646955

    我们看到,上面的query()方法中定义了一个内部类QueryStatementCallback,并实现了StatementCallback接口,点开查看详细内容:

    @FunctionalInterface
    public interface StatementCallback<T> {
        //唯一的抽象方法
        @Nullable
        T doInStatement(Statement var1) throws SQLException, DataAccessException;
    }
    

    回到query()方法中,我们发现最后返回的execute(new QueryStatementCallback())中是将内部类QueryStatementCallback当做参数进行返回。这里QueryStatementCallback就相当于命令模式中的具体命令对象,而StatementCallback则是抽象命令对象。比如还有其他具体命令实现类,比如BatchUpdateStatementCallbackExecuteStatementCallback等等:

    image-20220405174432889

    看看execute()方法,为了方便理解,代码做了精简:

    @Nullable
    private <T> T execute(StatementCallback<T> action, boolean closeResources) throws DataAccessException {
        Assert.notNull(action, "Callback object must not be null");
        Connection con = DataSourceUtils.getConnection(this.obtainDataSource());
        Statement stmt = null;
    
        Object var12;
        try {
            stmt = con.createStatement();
            this.applyStatementSettings(stmt);
            //执行doInStatement方法
            T result = action.doInStatement(stmt);
            this.handleWarnings(stmt);
            //赋值为var12
            var12 = result;
        } catch (SQLException var10) {
           //...
        } finally {
          //...
        }
    	//最后返回statementCallback对象
        return var12;
    }
    

    根据上面的代码,可以梳理整个执行流程:

    image-20220405195756378

    实际上JdbcTemplate这个类是调用者(Invoker)、实现者(Receiver)和具体命令实现(Concrete Command)的集合, statementCallback则是命令的抽象接口。

    三、命令模式实战#

    模拟在餐厅中点餐交给初始烹饪的场景,在该场景中点餐人员只需要把需要点的各种菜系交给服务员,服务员再把各项菜品交给厨师进行烹饪。如下图所示:

    image-20220405205119777

    我们先分析一下,命令是菜品具体实现是菜系,命令实现是厨师,调用者是服务员。所以该场景下的命令模式结构应该为:

    image-20220405205915202

    代码目录结构为:

    ├─src
    │  ├─main
    │  │  ├─java
    │  │  │  └─cn
    │  │  │      └─ethan
    │  │  │          └─design
    │  │  │              └─command
    │  │  │                  │  Waiter.java
    │  │  │                  │
    │  │  │                  ├─cook
    │  │  │                  │  │  ICook.java
    │  │  │                  │  │
    │  │  │                  │  └─impl
    │  │  │                  │          GuangDongCook.java
    │  │  │                  │          JiangSuCook.java
    │  │  │                  │          ShanDongCook.java
    │  │  │                  │          SiChuangCook.java
    │  │  │                  │
    │  │  │                  └─cuisine
    │  │  │                      │  ICuisine.java
    │  │  │                      │
    │  │  │                      └─impl
    │  │  │                              GuangDongCuisine.java
    │  │  │                              JiangSuCuisine.java
    │  │  │                              ShanDongCuisine.java
    │  │  │                              SiChuangCuisine.java
    │  │  │
    │  │  └─resources
    │  └─test
    │      └─java
    │          └─cn
    │              └─ethan
    │                  └─disign
    │                          ApiTest.java
    
    

    具体代码如下:

    1. 抽象命令者及其具体实现
    /**
     * @description: 抽象命令接口(八大菜系)
     * @author: wjw
     * @date: 2022/4/5
     */
    public interface ICuisine {
    
        /**烹调公共接口*/
        void cook();
    }
    /**
     * @description: 具体命令实现(广东菜)
     * @author: wjw
     * @date: 2022/4/5
     */
    public class GuangDongCuisine implements ICuisine {
    
        private ICook cook;
    
        public GuangDongCuisine(ICook cook) {
            this.cook = cook;
        }
    
        @Override
        public void cook() {
            cook.doCooking();
        }
    }
    /**
     * @description: 命令具体实现(江苏菜)
     * @author: wjw
     * @date: 2022/4/5
     */
    public class JiangSuCuisine implements ICuisine {
    
        private ICook cook;
    
        public JiangSuCuisine(ICook cook) {
            this.cook = cook;
        }
    
        @Override
        public void cook() {
            cook.doCooking();
        }
    }
    /**
     * @description: 具体命令实现(山东菜)
     * @author: wjw
     * @date: 2022/4/5
     */
    public class ShanDongCuisine implements ICuisine {
    
        private ICook cook;
    
        public ShanDongCuisine(ICook cook) {
            this.cook = cook;
        }
    
        @Override
        public void cook() {
            cook.doCooking();
        }
    }
    /**
     * @description: 具体命令实现(四川菜)
     * @author: wjw
     * @date: 2022/4/5
     */
    public class SiChuangCuisine implements ICuisine {
    
        private ICook cook;
    
        public SiChuangCuisine(ICook cook) {
            this.cook = cook;
        }
    
        @Override
        public void cook() {
            cook.doCooking();
        }
    }
    
    1. 抽象实现者及其具体实现
    /**
     * @description: 抽象实现者接口
     * @author: wjw
     * @date: 2022/4/5
     */
    public interface ICook {
    
        /**厨师烹调*/
        void doCooking();
    }
    /**
     * @description: 具体实现者(广东厨师)
     * @author: wjw
     * @date: 2022/4/5
     */
    public class GuangDongCook implements ICook {
    
        private Logger logger = LoggerFactory.getLogger(GuangDongCook.class);
    
        @Override
        public void doCooking() {
            logger.info("广东厨师,会做广东菜");
        }
    }
    /**
     * @description: 具体实现类(江苏厨师)
     * @author: wjw
     * @date: 2022/4/5
     */
    public class JiangSuCook implements ICook {
    
        private Logger logger = LoggerFactory.getLogger(JiangSuCook.class);
    
        @Override
        public void doCooking() {
            logger.info("江苏厨师,会烧江苏菜");
        }
    }
    /**
     * @description: 具体实现类(山东厨师)
     * @author: wjw
     * @date: 2022/4/5
     */
    public class ShanDongCook implements ICook {
    
        private Logger logger = LoggerFactory.getLogger(ShanDongCook.class);
    
        @Override
        public void doCooking() {
            logger.info("山东厨师会烧山东菜");
        }
    }
    /**
     * @description: 具体实现类(四川厨师)
     * @author: wjw
     * @date: 2022/4/5
     */
    public class SiChuangCook implements ICook {
    
        private Logger logger = LoggerFactory.getLogger(SiChuangCook.class);
    
        @Override
        public void doCooking() {
            logger.info("四川厨师会烧四川菜");
        }
    }
    
    1. 调用者及客户端
    /**
     * @description: 调用者(服务员)
     * @author: wjw
     * @date: 2022/4/5
     */
    public class Waiter {
    
        private Logger logger = LoggerFactory.getLogger(Waiter.class);
    
        private List<ICuisine> cuisineList = new ArrayList<>();
    
        public void order(ICuisine cuisine) {
            cuisineList.add(cuisine);
        }
    
        public synchronized void placeOrder() {
            for (ICuisine cuisine : cuisineList) {
                cuisine.cook();
            }
            cuisineList.clear();
        }
    }
    /**
     * @description: 客户端
     * @author: wjw
     * @date: 2022/4/5
     */
    public class ApiTest {
    
        @Test
        public void test_command() {
            //菜和厨师命令实现
            ICuisine guangDongCuisine = new GuangDongCuisine(new GuangDongCook());
            ICuisine shanDongCuisine = new ShanDongCuisine(new ShanDongCook());
            ICuisine siChuangCuisine = new SiChuangCuisine(new SiChuangCook());
            ICuisine jiangSuCuisine = new JiangSuCuisine(new JiangSuCook());
    
            //调用者进行点单
            Waiter waiter = new Waiter();
            waiter.order(guangDongCuisine);
            waiter.order(shanDongCuisine);
            waiter.order(siChuangCuisine);
            waiter.order(jiangSuCuisine);
    
            //下单操作
            waiter.placeOrder();
    
        }
    }
    

    最终测试结果如下:

    23:16:40.512 [main] INFO  c.e.d.c.cook.impl.GuangDongCook - 广东厨师,会做广东菜
    23:16:40.518 [main] INFO  c.e.d.command.cook.impl.ShanDongCook - 山东厨师会烧山东菜
    23:16:40.518 [main] INFO  c.e.d.command.cook.impl.SiChuangCook - 四川厨师会烧四川菜
    23:16:40.518 [main] INFO  c.e.d.command.cook.impl.JiangSuCook - 江苏厨师,会烧江苏菜
    

    参考资料#

    《重学Java设计模式》

    http://c.biancheng.net/view/1380.html

  • 相关阅读:
    《小猫猫大课堂》1——小喵是如何开启敲代码之路的?
    Google Earth Engine(GEE)——用不同方法计算slope对比案例分析
    Python之Web开发初学者教程—ubuntu中安装配置redis
    Python爬虫_案例分析(二)
    java毕业设计网上主题超市系统源码+lw文档+mybatis+系统+mysql数据库+调试
    uview组件使用笔记
    java后端开发面试准备(1)-Redis缓存
    操作系统·进程同步
    Linux(CentOS/Ubuntu)——安装nginx
    有关 M91 快速霍尔测量仪的更多信息
  • 原文地址:https://www.cnblogs.com/EthanWong/p/16104549.html