概述模板模式就是定義一個(gè)操作中的算法骨架,然后將一些步驟延遲到子類中。模板方法使得子類在不改變算法的結(jié)構(gòu)即可重定義該算法的某些步驟。 使用場(chǎng)景泡茶我們都知道泡茶基本步驟有: 燒水、選茶葉、泡茶、喝茶水。 這里指的是一般步驟。至于你燒水時(shí)用什么燒?熱得快、熱水壺、還是其他工具,反正目的是把水燒開就好;你選的茶葉是什么茶葉?鐵觀音、菊花茶、龍井茶等,您想選什么茶喜歡就好;泡茶都是一樣的,就是把茶葉放開水里泡,甚至您也可以選擇自己泡茶時(shí)間的長(zhǎng)久或者選擇水的溫度;至于喝茶是大杯或者還是小杯自己喜歡就好。 API寫過API 接口的碼友們都知道,寫API 一般有四個(gè)步驟: 參數(shù)解析、參數(shù)校驗(yàn)、處理業(yè)務(wù)、組織返回參數(shù)。 把請(qǐng)求參數(shù)解析成該業(yè)務(wù)的請(qǐng)求參數(shù)json 解析成實(shí)體類;參數(shù)校驗(yàn),您可以使用通用的方式就是判斷參數(shù)是否為空,也可以自己定義特殊的校驗(yàn)方式;處理業(yè)務(wù)一般每個(gè)接口都是不一樣的,基本上都是自己去實(shí)現(xiàn);至于返回參數(shù),可能您得根據(jù)該API 接口業(yè)務(wù)來返回。 支付訂單做過支付相關(guān)的系統(tǒng)的人都清楚,支付訂單大致分這三個(gè)步驟: 組織請(qǐng)求銀行或者第三方支付公司的請(qǐng)求參數(shù)、發(fā)起支付、處理返回結(jié)果。 以上三個(gè)場(chǎng)景中的步驟就是算法骨架,至于每個(gè)步驟可能每個(gè)人喝茶偏好不一樣,API接口業(yè)務(wù)不一樣、銀行或者第三方支付的支付處理不一樣,可能需要自己做特殊的處理。 場(chǎng)景現(xiàn)實(shí)實(shí)現(xiàn)一個(gè)API 接口 算法類package com.tian.springbootdemo.controller; import com.tian.springbootdemo.rep.Result; /** * @auther: 老田 * @Description: 模板類 */ public abstract class AbstractTemplate {
/** * 算法骨架 */ public Result execute() { //第一步:解析參數(shù) parseRequestParameters(); //第二步:校驗(yàn)參數(shù) checkRequestParameters(); //第三步:業(yè)務(wù)處理 Object data= doBusiness(); //第四步:組織返回參數(shù) return assembleResponseParameters(data); }
/** * 解析參數(shù) */ public abstract void parseRequestParameters();
/** * 校驗(yàn)參數(shù) */ public abstract void checkRequestParameters();
/** * 業(yè)務(wù)處理 */ public abstract Object doBusiness();
/** * 組織返回參數(shù) */ public abstract Result assembleResponseParameters(Object object); }
實(shí)現(xiàn)類一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("*****解析參數(shù)*****"); }
@Override public void checkRequestParameters() { System.out.println("*****校驗(yàn)參數(shù)*****"); }
@Override public Object doBusiness() { System.out.println("*****處理業(yè)務(wù)*****"); // TODO: 2018/11/17 調(diào)用service處理業(yè)務(wù) 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("*****返回參數(shù)*****"); Result result = new Result("200", "處理成功"); result.setData(object); return result; } }
實(shí)現(xiàn)類二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("解析登錄參數(shù)"); }
@Override public void checkRequestParameters() { System.out.println("校驗(yàn)登錄用戶名是否為空,密碼是否為空"); }
@Override public Object doBusiness() { System.out.println("通過用戶名查詢是否存在此用戶"); System.out.println("校驗(yàn)用戶密碼是否正確"); 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("*****返回參數(shù)*****"); Result result = new Result("200", "登錄成功"); result.setData(object); return result; } }
相關(guān)類/** * @auther: 老田 * @Description: 返回信息 */ public class Result { //返回碼 private String responseCode; //描述 private String message; //數(shù)據(jù) 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: 數(shù)據(jù) */ 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; } }
測(cè)試這里使用的是idea 的Tools 下面的REST Client 進(jìn)行接口測(cè)試: enter image description hereenter image description here再看看控制臺(tái)Console 打印出來的信息: enter image description here enter image description here這樣我們就把模板設(shè)計(jì)模式應(yīng)用到我們的具體代碼里了,同樣的我們也可以實(shí)現(xiàn)其他API 的實(shí)現(xiàn)類。 另外,參數(shù)校驗(yàn)也可以在 AbstractTemplate 中實(shí)現(xiàn)一個(gè) default 的方式,比如說:校驗(yàn)參數(shù)是否為空,但是子類也可以重寫這個(gè)方法,自己做一個(gè)特殊的校驗(yàn);比如說:如果參數(shù)中有手機(jī)號(hào)碼,那么我們不僅要校驗(yàn)手機(jī)號(hào)是否為空,還可以校驗(yàn)這個(gè)手機(jī)號(hào)碼是不是11位,是否合法的校驗(yàn)等等。 模板模式優(yōu)缺點(diǎn)優(yōu)點(diǎn)提高代碼的復(fù)用性,將相同部分的代碼放到抽象類里; 提高拓展性,將不同的放到不同的實(shí)現(xiàn)類里,通過實(shí)現(xiàn)類的擴(kuò)展增加一些自己需要的行為; 實(shí)現(xiàn)反向控制,通過一個(gè)父類調(diào)用實(shí)現(xiàn)類的操作,通過對(duì)實(shí)現(xiàn)類的擴(kuò)展增加新行為,實(shí)現(xiàn)反向控制。
缺點(diǎn)大佬們?cè)诳蚣芾锸窃趺词褂玫模?/span>Spring中AbstractApplicationContext 中的refreash 方法就是模板方法,源碼為:
@Override public void refresh() throws BeansException, IllegalStateException { synchronized (this.startupShutdownMonitor) { //調(diào)用容器準(zhǔn)備刷新的方法,獲取容器的當(dāng)時(shí)時(shí)間, //同時(shí)給容器設(shè)置同步標(biāo)識(shí) prepareRefresh(); //告訴子類啟動(dòng)refreshBeanFactory()方法, //Bean定義資源文件的載入從 //子類的refreshBeanFactory()方法啟動(dòng) ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory(); //為BeanFactory配置容器特性,例如類加載器、事件處理器等 prepareBeanFactory(beanFactory); try { //為容器的某些子類指定特殊的BeanPost事件處理器 //-----子類實(shí)現(xiàn) postProcessBeanFactory(beanFactory); //調(diào)用所有注冊(cè)的BeanFactoryPostProcessor的Bean invokeBeanFactoryPostProcessors(beanFactory); //為BeanFactory注冊(cè)BeanPost事件處理器. //BeanPostProcessor是Bean后置處理器, //用于監(jiān)聽容器觸發(fā)的事件 registerBeanPostProcessors(beanFactory); //初始化信息源,和國(guó)際化相關(guān). initMessageSource(); //初始化容器事件傳播器. initApplicationEventMulticaster(); //調(diào)用子類的某些特殊Bean初始化方法 //-----子類實(shí)現(xiàn) onRefresh(); //為事件傳播器注冊(cè)事件監(jiān)聽器. registerListeners(); //初始化所有剩余的單例Bean finishBeanFactoryInitialization(beanFactory); //初始化容器的生命周期事件處理器, //并發(fā)布容器的生命周期事件 finishRefresh(); //.....
該方法就是上下文啟動(dòng)模板方法。這就是模板模式在Spring中應(yīng)用場(chǎng)景之一。 Mybatis中BaseExecutor 中的update 方法就是一個(gè)模板方法
/** * SqlSession.update/insert/delete會(huì)調(diào)用此方法 * 模板方法 */ @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(); //由子類實(shí)現(xiàn)(鉤子方法) return doUpdate(ms, parameter); }
在BaseExecutor 里只是定義了方法,但是實(shí)現(xiàn)是在子類里 //更新 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開頭的方法都是交給具體子類自己去實(shí)現(xiàn)
BaseExecutor 的實(shí)現(xiàn)類如下: enter image description here 實(shí)現(xiàn)類SimpleExecutor 中的doUpdate 方法的實(shí)現(xiàn)
@Override public int doUpdate(MappedStatement ms, Object parameter) throws SQLException { Statement stmt = null; try { Configuration configuration = ms.getConfiguration(); //新建一個(gè)StatementHandler //這里看到ResultHandler傳入的是null StatementHandler handler = configuration.newStatementHandler( this, ms, parameter, RowBounds.DEFAULT, null, null); //準(zhǔn)備語句 stmt = prepareStatement(handler, ms.getStatementLog()); //StatementHandler.update return handler.update(stmt); } finally { closeStatement(stmt); } }
實(shí)現(xiàn)類ReuseExecutor 中的doUpdate 方法的實(shí)現(xiàn) @Override public int doUpdate(MappedStatement ms, Object parameter) throws SQLException { Configuration configuration = ms.getConfiguration(); //和SimpleExecutor一樣, //新建一個(gè)StatementHandler //這里看到ResultHandler傳入的是null StatementHandler handler = configuration.newStatementHandler( this, ms, parameter, RowBounds.DEFAULT, null, null); //準(zhǔn)備語句 Statement stmt = prepareStatement(handler, ms.getStatementLog()); return handler.update(stmt); }
這就是Mybatis 中的模板方法模式的應(yīng)用。 另外相關(guān)實(shí)現(xiàn)類代碼由于篇幅過了,此處做省略,有興趣的朋友可以聯(lián)系我,咱們一起看源碼。
|