五分鐘搞定模板模式

img

概述

模板模式就是定義一個操做中的算法骨架,而後將一些步驟延遲到子類中。模板方法使得子類在不改變算法的結構便可重定義該算法的某些步驟。java

使用場景

喝茶水web

咱們都知道泡茶基本步驟(算法骨架)有:面試

燒水、泡茶、喝茶水。算法

整個過程當中很關鍵的步驟是泡茶,泡茶須要跑什麼茶呢?泡多久?(留給子類本身去實現)。spring

img

APIjson

寫過API接口的碼友們都知道,寫API通常有四個步驟:設計模式

參數解析、參數校驗、處理業務、組織返回參數。api

把請求參數解析成該業務的請求參數json解析成實體類;參數校驗,您可使用通用的方式就是判斷參數是否爲空,也能夠本身定義特殊的校驗方式;處理業務通常每一個接口都是不同的,基本上都是本身去實現;至於返回參數,可能您得根據該API接口業務來返回。緩存

支付訂單springboot

作過支付相關的系統的人都清楚,支付訂單大體分這三個步驟:

組織請求銀行或者第三方支付公司的請求參數、發起支付、處理返回結果。

以上三個場景中的步驟就是算法骨架,至於每一個步驟可能每一個人喝茶偏好不同,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等框架中有很好實現案例。相對來講模板方法模式是算比較簡單的哈,在面試中也能和麪試官扯一下子了。

「爲了將來好一點 ,如今苦一點算什麼」

相關文章
相關標籤/搜索