• 源码中的设计模式--模板方法模式


    本文要解决的几个问题,

    1、什么是模板方法模式;

    2、模板方法模式的使用场景;

    3、模板方法模式的优点;

    4、源码中有哪些地方使用到了模板方法模式;

    带着这几个问题,我们开始今天的设计模式的分享。

    一、模式入场

      大家在日常的工作生活中肯定碰到过这样的场景,比如,你要转正答辩了,总要有个PPT吧,这时你是不是会问你同事要个述职的PPT模板,有个模板的好处这里自不用说。你去帮助单位去投标拿项目了,你是不是要问甲方爸爸要个模板,按照模板准备你的材料。生活中这样的例子太多了,有模板好办事。

      在平时的开发过程中,不知道你是否碰到过类似的情形,你要调用系统A和系统B的接口,把系统A和系统B的数据读取过来,经过处理存储到自己的数据库里。

      针对这样的场景你要怎么设计呐,首先,针对这样一个场景进行分析,要明确的是需要调用两个系统的接口,这两个系统返回的数据是不一样的,并且要存储到不同的表中,下面先试图实现下这个场景。有两个类SyncSystemA和SyncSystemB分别表示处理系统A和系统B的接口数据,

    SyncSystemA.java

    package com.example.template;
    
    public class SyncSystemA {
        public void syncData(){
            //1、组装参数
            String url="http://a.com/query";
            String param="A";
            //2、发送请求
            String result=sendRequest(url,param);
            //3、解析
            String result2=parse(result);
            //4、保存数据
            saveData(result2);
        }
        private String sendRequest(String url,String param){
            System.out.println("发送请求到A");
            return "";
        }
        private String parse(String result){
            System.out.println("对A返回结果进行解析");
            return "";
        }
        private void saveData(String result){
            System.out.println("保存A的数据");
        }
    }

    SyncSystemB.java

    package com.example.template;
    
    public class SyncSystemB {
        public void syncData(){
            //1、组装参数
            String url="http://b.com/query";
            String param="A";
            //2、发送请求
            String result=sendRequest(url,param);
            //3、解析
            String result2=parse(result);
            //4、保存数据
            saveData(result2);
            
        }
        private String sendRequest(String url,String param){
            System.out.println("发送请求到B");
            return "";
        }
        private String parse(String result){
            System.out.println("对B的返回结果进行解析");
            return "";
        }
        private void saveData(String result){
            System.out.println("保存B的数据");
        }
    }

    下面看测试方法,Test.java

    package com.example.template;
    
    public class Test {
        public static void main(String[] args) {
            SyncSystemA syncSystemA=new SyncSystemA();
            SyncSystemB syncSystemB=new SyncSystemB();
            syncSystemA.syncData();
            System.out.println("-----------");
            syncSystemB.syncData();
        }
    }

    返回结果如下,

    发送请求到A
    对A返回结果进行解析
    保存A的数据
    -----------
    发送请求到B
    对B的返回结果进行解析
    保存B的数据
    
    Process finished with exit code 0

    可以看到很好的完成了我们的目标,那就是同步系统A和系统B的数据。但是从上面的代码中也能发现一些问题,在SyncSystemA和SyncSystemB中有很多的重复代码,追求极简的我们怎么能容忍这样的代码。

    二、深入模板方法模式

    上面的处理步骤其实可以归纳为下面的流程,如下图,

    我们把这样一个过程抽象出了这样几步:组装参数、发送请求、解析参数、保存数据,在这样几步中组装参数和解析参数肯定是不同的,对于发送请求和保存数据我们可以把它们处理成一致的。既然有一样的处理步骤,为了减少重复的代码,我们可以进行优化,把公共的部分抽取出来,那么如何才能实现这样的目的,可以把公共的部分封装到工具类中,在不同的地方进行调用,但这些方法又不能算的上是工具类。还有一个方法在java基础中有抽象类的概念,今天就使用下抽象类,那么如何设计抽象类,下面看,

    AbstractSyncData.java

    package com.example.template;
    
    import java.util.Map;
    
    public abstract class AbstractSyncData {
        //定义好同步数据的步骤
        public void syncData() {
            //1、组装参数
            Map param = assembleParam();
            //2、发送请求
            String result = sendRequest(param);
            //3、解析
            String result2 = parse(result);
            //4、保存数据
            saveData(result2);
        }
    
        //1、组装参数,供子类实现自己的逻辑
        protected abstract Map assembleParam();
    
        //2、发送请求
        private String sendRequest(Map map) {
    
            //实际发送请求,并把数据返回
            System.out.println("发送请求");
            return "";
        }
    
        //3、解析返回结果,供子类实现自己的逻辑
        protected abstract String parse(String result);
    
        //4、保存数据
        private void saveData(String result) {
            System.out.println("保存数据");
        }
    }

    从上面的AbstractSyncData抽象类中,可以看到把syncData放到了抽象类中,并且在该类中定义了完成此功能的步骤:组装参数、发送请求、解析返回结果、保存数据,其中组装参数、解析返回结果两步在抽象类中定义了抽象方法,定义抽象方法的目的是为了让自己去实现自己的逻辑,看下两个子类的实现,

    SyncSystemAImpl.java

    package com.example.template;
    
    import java.util.HashMap;
    import java.util.Map;
    
    public class SyncSystemAImpl extends AbstractSyncData{
        @Override
        protected Map assembleParam() {
            System.out.println("组装发送到系统A的参数");
            return new HashMap();
        }
    
        @Override
        protected String parse(String result) {
            System.out.println("解析系统A的返回结果");
            return "";
        }
    }

    SyncSystemBImpl.java

    package com.example.template;
    
    import java.util.HashMap;
    import java.util.Map;
    
    public class SyncSystemBImpl extends AbstractSyncData{
        @Override
        protected Map assembleParam() {
            System.out.println("组装发送到系统B的参数");
            return new HashMap();
        }
    
        @Override
        protected String parse(String result) {
            System.out.println("解析系统B的返回结果");
            return "";
        }
    }

    看下测试结果

    组装发送到系统A的参数
    发送请求
    解析系统A的返回结果
    保存数据
    -----------
    组装发送到系统B的参数
    发送请求
    解析系统B的返回结果
    保存数据
    
    Process finished with exit code 0

    看到上面的结果同样实现了功能,而且从代码风格上是不是更简洁,而且使用到了模板方法模式。

    看下《Head First 设计模式》一书中给模板方法模式下的定义

    模板方法模式在一个方法中定义一个算法的骨架,而将一些步骤延迟到子类中。模板方法使得子类可以在不改变算法结构的情况下,重新定义算法中的某些步骤

    上面的释义定义的太完美了,多读几遍上面的释义和我们上面的AbstractSyncData类对比下

    算法的骨架对应syncData方法

    一些步骤延迟到子类对应assembleParam和parse方法

    重新定义算法中的某些步骤对应assembleParam和parse方法,因为针对不同的实现有不同的处理逻辑。

    模板方法的使用场景上面已经提到过,在开发中要善于抽象,把一个场景中的步骤抽象成不同的几步,如果有多种实现,那么此时便是使用模板方法的大好时机。

    番外

    想多说一句的是,现在不是都谈面向接口编程,那么针对面向接口编程我们要如何改造上面的模板方法模式呐,只需要把syncData放到接口中即可,

    package com.example.template;
    
    public interface SyncData {
        //同步数据
        void syncData();
    }

    相应的抽象类实现该接口即可,

    其UML图如下,

    三、追寻源码

    上面已经系统的学习了模板方法模式,下面看下在源码中的使用,

    1、mybatis的BaseExecutor

    在mybatis的BaseExecutor类中有update方法,

    该方法来自于接口Executor,该方法又调用了doUpdate方法,该方法在BaseExecutor中是抽象方法,

    看下实现的子类,

    和我们上面的例子是不是很像,或者说就是同一个,再看下其uml

    2、spring的AbstractApplicationContext

    在spring的AbstractApplicaitonContext类中有fresh()方法,该方法中调用了obtainFreshBeanFactory方法,

    obtainFreshBeanFactory方法,

    看下这两个方法,

    这两个方法是抽象的,肯定也是模板方法了。

    四、总结

      模板方法模式的精髓在于抽象,抽象出完成某个功能的步骤,再把个性化的步骤做为抽象方法,让子类延迟实现,公有的方法在抽象类中完成。在使用模板方法时由于存在抽象类,会出现多个继承子类的情况,需要视情况而定。另外,模板方法模式可以结合接口使用,实现面向接口编程。

    首发于:https://www.toutiao.com/article/7097584508639183367/

  • 相关阅读:
    渗透测试-信息收集思路&工具分享
    [读论文]深度孵化 Deep Incubation: Training Large Models by Divide-and-Conquering
    一起来庆祝属于GISer的节日GIS DAY!
    什么是RabbitMQ
    Vue模板语法(下)
    如何去正确理解股票量化接口的真实用途?
    初识Java 17-2 反射
    vulnhub靶机Presidential
    java代码审计-文件操作
    计算机毕业设计ssm健身房管理系统y7i9n系统+程序+源码+lw+远程部署
  • 原文地址:https://www.cnblogs.com/teach/p/16271356.html