五分钟搞定模板模式

153

img

概述

模板模式就是定义一个操作中的算法骨架,然后将一些步骤延迟到子类中。模板方法使得子类在不改变算法的结构即可重定义该算法的某些步骤。

使用场景

喝茶水

我们都知道泡茶基本步骤(算法骨架)有:

烧水、泡茶、喝茶水。

整个过程中很关键的步骤是泡茶,泡茶需要跑什么茶呢?泡多久?(留给子类自己去实现)。

img

API

写过API接口的码友们都知道,写API一般有四个步骤:

参数解析、参数校验、处理业务、组织返回参数。

把请求参数解析成该业务的请求参数json解析成实体类;参数校验,您可以使用通用的方式就是判断参数是否为空,也可以自己定义特殊的校验方式;处理业务一般每个接口都是不一样的,基本上都是自己去实现;至于返回参数,可能您得根据该API接口业务来返回。

支付订单

做过支付相关的系统的人都清楚,支付订单大致分这三个步骤:

组织请求银行或者第三方支付公司的请求参数、发起支付、处理返回结果。

以上三个场景中的步骤就是算法骨架,至于每个步骤可能每个人喝茶偏好不一样,API接口业务不一样、银行或者第三方支付的支付处理不一样,可能需要自己做特殊的处理。

场景现实

实现一个API接口

算法类

package com.tian.springbootdemo.controller;
import com.tian.springbootdemo.rep.Result;
/**
 * @auther: 老田
 * @Description: 模板类
 */
public abstract class AbstractTemplate {

    /**
     * 算法骨架
     */
    public Result execute() {
        //第一步:解析参数
        parseRequestParameters();
        //第二步:校验参数
        checkRequestParameters();
        //第三步:业务处理
        Object data= doBusiness();
        //第四步:组织返回参数
        return assembleResponseParameters(data);
    }

    /**
     * 解析参数
     */
    public abstract void parseRequestParameters();

    /**
     * 校验参数
     */
    public abstract void checkRequestParameters();

    /**
     * 业务处理
     */
    public abstract Object doBusiness();

    /**
     * 组织返回参数
     */
    public abstract Result assembleResponseParameters(Object object);
}

实现类一

import com.tian.springbootdemo.rep.Result;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * @auther: 老田
 * @Description: api接口
 */
@RequestMapping("/api")
@Controller
public class MyApiController extends AbstractTemplate {

    @RequestMapping(value = "/users", method = RequestMethod.POST)
    @ResponseBody
    @Override
    public Result execute() {
        return super.execute();
    }

    @Override
    public void parseRequestParameters() {
        System.out.println("*****解析参数*****");
    }

    @Override
    public void checkRequestParameters() {
        System.out.println("*****校验参数*****");
    }

    @Override
    public Object doBusiness() {
        System.out.println("*****处理业务*****");
        // TODO: 2018/11/17 调用service处理业务
        User user = new User();
        user.setName("小田哥");
        user.setId(1);
        user.setAge(20);
        user.setSex("man");
        return user;
    }

    @Override
    public Result assembleResponseParameters(Object object) {
        System.out.println("*****返回参数*****");
        Result result = new Result("200", "处理成功");
        result.setData(object);
        return result;
    }
}

实现类二

import com.tian.springbootdemo.dao.domain.User;
import com.tian.springbootdemo.rep.Result;
import org.springframework.web.bind.annotation.*;

/**
 * @auther: 老田
 * @Description: api接口
 */
@RequestMapping("/api")
@RestController
public class LoginController extends AbstractTemplate {
    @PostMapping(value = "/login")
    @Override
    public Result execute() {
        return super.execute();
    }
    @Override
    public void parseRequestParameters() {
        System.out.println("解析登录参数");
    }

    @Override
    public void checkRequestParameters() {
        System.out.println("校验登录用户名是否为空,密码是否为空");
    }

    @Override
    public Object doBusiness() {
        System.out.println("通过用户名查询是否存在此用户");
        System.out.println("校验用户密码是否正确");
        System.out.println("登录成功");
        User user = new User();
        user.setName("小田哥");
        user.setId(1);
        user.setAge(20);
        user.setSex("man");
        return user;
    }

    @Override
    public Result assembleResponseParameters(Object object) {
        System.out.println("*****返回参数*****");
        Result result = new Result("200", "登录成功");
        result.setData(object);
        return result;
    }
}

相关类

/**
 * @auther: 老田
 * @Description: 返回信息
 */
public class Result {
    //返回码
    private String responseCode;
    //描述
    private String message;
    //数据
    private Object data;

    public Result() {
    }

    public Result(String responseCode, String message) {
        this.responseCode = responseCode;
        this.message = message;
    }

    public Result(String responseCode, String message, Object data) {
        this.responseCode = responseCode;
        this.message = message;
        this.data = data;
    }

    public String getResponseCode() {
        return responseCode;
    }

    public void setResponseCode(String responseCode) {
        this.responseCode = responseCode;
    }

    public String getMessage() {
        return message;
    }

    public void setMessage(String message) {
        this.message = message;
    }

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }
}

import java.io.Serializable;

/**
 * @auther: 老田
 * @Description: 数据
 */
public class User implements Serializable {
    //id
    private Integer id;
    //用户姓名
    private String name;
    //性别
    private String sex;
    //年龄
    private int age;

    public User() {
    }

    public User(Integer id, String name, String sex, int age) {
        this.id = id;
        this.name = name;
        this.sex = sex;
        this.age = age;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public int getAge() {
        return age;
    }

    public void setAge(int age) {
        this.age = age;
    }
}

测试

这里使用的是ideaTools下面的REST Client进行接口测试:

img

enter image description here

img

enter image description here

再看看控制台Console打印出来的信息:

img

enter image description here

img

enter image description here

这样我们就把模板设计模式应用到我们的具体代码里了,同样的我们也可以实现其他API的实现类。

另外,参数校验也可以在 AbstractTemplate 中实现一个 default 的方式,比如说:校验参数是否为空,但是子类也可以重写这个方法,自己做一个特殊的校验;比如说:如果参数中有手机号码,那么我们不仅要校验手机号是否为空,还可以校验这个手机号码是不是11位,是否合法的校验等等。

模板模式优缺点

优点

  • 提高代码的复用性,将相同部分的代码放到抽象类里;
  • 提高拓展性,将不同的放到不同的实现类里,通过实现类的扩展增加一些自己需要的行为;
  • 实现反向控制,通过一个父类调用实现类的操作,通过对实现类的扩展增加新行为,实现反向控制。

缺点

  • 因为引入了抽象类,每个不同的实现都需要一个子类来现实,这样会导致类的数量增多,从而导致系统实现的复杂度。

大佬们在框架里是怎么使用的?

Spring中

AbstractApplicationContext 中的refreash方法就是模板方法,源码为:

@Override
public void refresh() throws BeansException, IllegalStateException {
        synchronized (this.startupShutdownMonitor) { 
            //调用容器准备刷新的方法,获取容器的当时时间,
            //同时给容器设置同步标识
            prepareRefresh();
            //告诉子类启动refreshBeanFactory()方法,
            //Bean定义资源文件的载入从
            //子类的refreshBeanFactory()方法启动
            ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
            //为BeanFactory配置容器特性,例如类加载器、事件处理器等
            prepareBeanFactory(beanFactory);
            try { 
                //为容器的某些子类指定特殊的BeanPost事件处理器
                //-----子类实现
                postProcessBeanFactory(beanFactory);
                //调用所有注册的BeanFactoryPostProcessor的Bean
                invokeBeanFactoryPostProcessors(beanFactory);
                //为BeanFactory注册BeanPost事件处理器.
                //BeanPostProcessor是Bean后置处理器,
                //用于监听容器触发的事件
                registerBeanPostProcessors(beanFactory);
                //初始化信息源,和国际化相关.
                initMessageSource();
                //初始化容器事件传播器.
                initApplicationEventMulticaster();
                //调用子类的某些特殊Bean初始化方法
                //-----子类实现
                onRefresh();
                //为事件传播器注册事件监听器.
                registerListeners();
                //初始化所有剩余的单例Bean
                finishBeanFactoryInitialization(beanFactory);
                //初始化容器的生命周期事件处理器,
                //并发布容器的生命周期事件
                finishRefresh();
                //.....

该方法就是上下文启动模板方法。这就是模板模式在Spring中应用场景之一。

Mybatis中

img

BaseExecutor中的update方法就是一个模板方法

 /**
     * SqlSession.update/insert/delete会调用此方法
     * 模板方法
     */
    @Override
    public int update(MappedStatement ms, Object parameter) throws SQLException {
         ErrorContext.instance().resource(ms.getResource()).activity("executing an                update").object(ms.getId());
        if (closed) {
            throw new ExecutorException("Executor was closed.");
        }
        //先清局部缓存,再更新,如何更新交由子类,
        //模板方法模式
        clearLocalCache();
        //由子类实现(钩子方法)
        return doUpdate(ms, parameter);
    }

BaseExecutor里只是定义了方法,但是实现是在子类里

//更新 
protected abstract int doUpdate(MappedStatement ms, Object parameter)
                                                     throws SQLException;
//查询
protected abstract <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds 
                    rowBounds, ResultHandler resultHandler, BoundSql boundSql)
                   throws SQLException;
 //...do开头的方法都是交给具体子类自己去实现

BaseExecutor的实现类如下:

img

enter image description here

实现类SimpleExecutor中的doUpdate方法的实现

@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    Statement stmt = null;
    try {
      Configuration configuration = ms.getConfiguration();
      //新建一个StatementHandler
      //这里看到ResultHandler传入的是null
      StatementHandler handler = configuration.newStatementHandler(
          this, ms, parameter,          RowBounds.DEFAULT, null, null);
      //准备语句
      stmt = prepareStatement(handler, ms.getStatementLog());
      //StatementHandler.update
      return handler.update(stmt);
    } finally {
      closeStatement(stmt);
    }
}

实现类ReuseExecutor中的doUpdate方法的实现

@Override
  public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
    Configuration configuration = ms.getConfiguration();
     //和SimpleExecutor一样,
     //新建一个StatementHandler
     //这里看到ResultHandler传入的是null
     StatementHandler handler = configuration.newStatementHandler(
             this, ms, parameter,       RowBounds.DEFAULT, null, null);
     //准备语句
     Statement stmt = prepareStatement(handler, ms.getStatementLog());
     return handler.update(stmt);
  }

这就是Mybatis中的模板方法模式的经典应用。

总结

模板方法模式就是定义了一个算法骨架,然后每个实现类自己去实现自己的业务逻辑。在Spring、Mybatis、Dubbo等框架中有很好实现案例。相对来说模板方法模式是算比较简单的哈,在面试中也能和面试官扯一会儿了。

「为了未来好一点 ,现在苦一点算什么」