設(shè)計(jì)模式 | 五分鐘學(xué)【模板方法】模式


概述
模板模式就是定義一個(gè)操作中的算法骨架,然后將一些步驟延遲到子類中。模板方法使得子類在不改變算法的結(jié)構(gòu)即可重定義該算法的某些步驟。
使用場景
喝茶水
我們都知道泡茶基本步驟(算法骨架)有:
燒水、泡茶、喝茶水。
整個(gè)過程中很關(guān)鍵的步驟是泡茶,泡茶需要跑什么茶呢?泡多久?(留給子類自己去實(shí)現(xiàn))。

API
寫過API接口的碼友們都知道,寫API一般有四個(gè)步驟:
參數(shù)解析、參數(shù)校驗(yàn)、處理業(yè)務(wù)、組織返回參數(shù)。
把請求參數(shù)解析成該業(yè)務(wù)的請求參數(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è)步驟:
組織請求銀行或者第三方支付公司的請求參數(shù)、發(fā)起支付、處理返回結(jié)果。
以上三個(gè)場景中的步驟就是算法骨架,至于每個(gè)步驟可能每個(gè)人喝茶偏好不一樣,API接口業(yè)務(wù)不一樣、銀行或者第三方支付的支付處理不一樣,可能需要自己做特殊的處理。
場景現(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;
????}
}
測試
這里使用的是idea的Tools下面的REST Client進(jìn)行接口測試:


再看看控制臺Console打印出來的信息:


這樣我們就把模板設(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ī)號碼,那么我們不僅要校驗(yàn)手機(jī)號是否為空,還可以校驗(yàn)這個(gè)手機(jī)號碼是不是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)類的操作,通過對實(shí)現(xiàn)類的擴(kuò)展增加新行為,實(shí)現(xiàn)反向控制。
缺點(diǎn)
因?yàn)橐肓顺橄箢?,每個(gè)不同的實(shí)現(xiàn)都需要一個(gè)子類來現(xiàn)實(shí),這樣會導(dǎo)致類的數(shù)量增多,從而導(dǎo)致系統(tǒng)實(shí)現(xiàn)的復(fù)雜度。
大佬們在框架里是怎么使用的?
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)識
????????????prepareRefresh();
????????????//告訴子類啟動refreshBeanFactory()方法,
????????????//Bean定義資源文件的載入從
????????????//子類的refreshBeanFactory()方法啟動
????????????ConfigurableListableBeanFactory?beanFactory?=?obtainFreshBeanFactory();
????????????//為BeanFactory配置容器特性,例如類加載器、事件處理器等
????????????prepareBeanFactory(beanFactory);
????????????try?{?
????????????????//為容器的某些子類指定特殊的BeanPost事件處理器
????????????????//-----子類實(shí)現(xiàn)
????????????????postProcessBeanFactory(beanFactory);
????????????????//調(diào)用所有注冊的BeanFactoryPostProcessor的Bean
????????????????invokeBeanFactoryPostProcessors(beanFactory);
????????????????//為BeanFactory注冊BeanPost事件處理器.
????????????????//BeanPostProcessor是Bean后置處理器,
????????????????//用于監(jiān)聽容器觸發(fā)的事件
????????????????registerBeanPostProcessors(beanFactory);
????????????????//初始化信息源,和國際化相關(guān).
????????????????initMessageSource();
????????????????//初始化容器事件傳播器.
????????????????initApplicationEventMulticaster();
????????????????//調(diào)用子類的某些特殊Bean初始化方法
????????????????//-----子類實(shí)現(xiàn)
????????????????onRefresh();
????????????????//為事件傳播器注冊事件監(jiān)聽器.
????????????????registerListeners();
????????????????//初始化所有剩余的單例Bean
????????????????finishBeanFactoryInitialization(beanFactory);
????????????????//初始化容器的生命周期事件處理器,
????????????????//并發(fā)布容器的生命周期事件
????????????????finishRefresh();
????????????????//.....
該方法就是上下文啟動模板方法。這就是模板模式在Spring中應(yīng)用場景之一。
Mybatis中

BaseExecutor中的update方法就是一個(gè)模板方法
?/**
?????*?SqlSession.update/insert/delete會調(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??List?doQuery(MappedStatement?ms,?Object?parameter,?RowBounds?
????????????????????rowBounds,?ResultHandler?resultHandler,?BoundSql?boundSql)
???????????????????throws?SQLException ;
?//...do開頭的方法都是交給具體子類自己去實(shí)現(xiàn)
BaseExecutor的實(shí)現(xiàn)類如下:

實(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中的模板方法模式的經(jīng)典應(yīng)用。
總結(jié)
模板方法模式就是定義了一個(gè)算法骨架,然后每個(gè)實(shí)現(xiàn)類自己去實(shí)現(xiàn)自己的業(yè)務(wù)邏輯。在Spring、Mybatis、Dubbo等框架中有很好實(shí)現(xiàn)案例。相對來說模板方法模式是算比較簡單的哈,在面試中也能和面試官扯一會兒了。
歡迎關(guān)注微信公眾號:互聯(lián)網(wǎng)全棧架構(gòu),收取更多有價(jià)值的信息。
