Java設(shè)計(jì)模式之java模板方法模式詳解
模板方法模式
在程序開(kāi)發(fā)中,經(jīng)常會(huì)遇到這種情況:某個(gè)方法要實(shí)現(xiàn)的算法需要多個(gè)步驟,但其中有一些步驟是固定不變的,而另一些步驟則是不固定的。為了提高代碼的可擴(kuò)展性和可維護(hù)性,模板方法模式在這種場(chǎng)景下就派上了用場(chǎng)。
譬如制作一節(jié)網(wǎng)課的步驟可以簡(jiǎn)化為4個(gè)步驟:
1.制作PPT
2.錄制視頻
3.編寫(xiě)筆記
4.提供課程資料
其中1、2、3的動(dòng)作在所有課程中的固定不變的,步驟3可有可無(wú),步驟4在每個(gè)課程都不同(有些課程需要提供源代碼,有些需要提供圖片文件等)
我們可以在父類(lèi)中確定整個(gè)流程的循序,并實(shí)現(xiàn)固定不變的步驟,而把不固定的步驟留給子類(lèi)實(shí)現(xiàn)。甚至可以通過(guò)一個(gè)鉤子方法,讓子類(lèi)來(lái)決定流程中某個(gè)方法的執(zhí)行與否
介紹
模板方法模式:定義一個(gè)操作中算法的框架,而將一些步驟延遲到子類(lèi)中。模板方法模式使得子類(lèi)可以不改變一個(gè)算法的結(jié)構(gòu)即可重定義該算法的某些特定步驟。模板方法模式是一種基于繼承的代碼復(fù)用技術(shù),它是一種類(lèi)行為型模式。
角色
AbstractClass(抽象類(lèi)):在抽象類(lèi)中定義了一系列基本操作(PrimitiveOperations),這些基本操作可以是具體的,也可以是抽象的,每一個(gè)基本操作對(duì)應(yīng)算法的一個(gè)步驟,在其子類(lèi)中可以重定義或?qū)崿F(xiàn)這些步驟。同時(shí),在抽象類(lèi)中實(shí)現(xiàn)了一個(gè)模板方法(Template Method),用于定義一個(gè)算法的框架,模板方法不僅可以調(diào)用在抽象類(lèi)中實(shí)現(xiàn)的基本方法,也可以調(diào)用在抽象類(lèi)的子類(lèi)中實(shí)現(xiàn)的基本方法,還可以調(diào)用其他對(duì)象中的方法。
ConcreteClass(具體子類(lèi)):它是抽象類(lèi)的子類(lèi),用于實(shí)現(xiàn)在父類(lèi)中聲明的抽象基本操作以完成子類(lèi)特定算法的步驟,也可以覆蓋在父類(lèi)中已經(jīng)實(shí)現(xiàn)的具體基本操作。
一個(gè)模板方法是定義在抽象類(lèi)中的、把基本操作方法組合在一起形成一個(gè)總算法或一個(gè)總行為的方法。這個(gè)模板方法定義在抽象類(lèi)中,并由子類(lèi)不加以修改地完全繼承下來(lái)。模板方法是一個(gè)具體方法,它給出了一個(gè)頂層邏輯框架,而邏輯的組成步驟在抽象類(lèi)中可以是具體方法,也可以是抽象方法。
基本方法是實(shí)現(xiàn)算法各個(gè)步驟的方法,是模板方法的組成部分?;痉椒ㄓ挚梢苑譃槿N:抽象方法(Abstract Method)、具體方法(Concrete Method)和鉤子方法(Hook Method)。
- 抽象方法:一個(gè)抽象方法由抽象類(lèi)聲明、由其具體子類(lèi)實(shí)現(xiàn)。
- 具體方法:一個(gè)具體方法由一個(gè)抽象類(lèi)或具體類(lèi)聲明并實(shí)現(xiàn),其子類(lèi)可以進(jìn)行覆蓋也可以直接繼承。
- 鉤子方法:可以與一些具體步驟 “掛鉤” ,以實(shí)現(xiàn)在不同條件下執(zhí)行模板方法中的不同步驟
代碼演示
所以我們通過(guò)模板方法模式,在抽象類(lèi)中把整個(gè)流程固定下來(lái),其中1、2、3的實(shí)現(xiàn)在抽象類(lèi)中完成,3的執(zhí)行與否則由子類(lèi)通過(guò)鉤子方法來(lái)控制,4則由子類(lèi)來(lái)實(shí)現(xiàn)
抽象類(lèi)定義如下:
//抽象課程類(lèi)
public abstract class Course
{
protected final void makeCourse() {
this.makePPT();
this.makeVideo();
if (needWriteArticle()) {
this.writeArticle();
}
this.packageCourse();
}
//final表示子類(lèi)繼承后,無(wú)法修改父類(lèi)該方法
final void makePPT() {
System.out.println("1. 制作PPT");
}
final void makeVideo() {
System.out.println("2. 制作視頻");
}
final void writeArticle() {
System.out.println("3. 編寫(xiě)課程筆記");
}
//鉤子方法
protected boolean needWriteArticle() {
return false;
}
//抽象方法,留給子類(lèi)去實(shí)現(xiàn)
abstract void packageCourse();
}
其中的 makeCourse 方法是模板方法,它定義了制作網(wǎng)課的基本流程,makePPT、makeVideo、writeArticle 這三個(gè)步驟在所有課程中都是固定的,所以用 final 關(guān)鍵字修飾;packageCourse 方法在所有課程中都可能不一樣,所以聲明為抽象方法,由子類(lèi)自行實(shí)現(xiàn);鉤子方法 needWriteArticle 返回一個(gè) boolean 類(lèi)型的值,控制是否編寫(xiě)課程筆記
子類(lèi) JavaCourse,實(shí)現(xiàn)了抽象方法 packageCourse,重寫(xiě)了鉤子方法 needWriteArticle
public class JavaCourse extends Course {
@Override
void packageCourse() {
System.out.println("4. 提供Java課程源代碼");
}
@Override
protected boolean needWriteArticle() {
return true;
}
}
子類(lèi) FECourse,實(shí)現(xiàn)了抽象方法 packageCourse,重寫(xiě)了鉤子方法 needWriteArticle,其中把鉤子方法的結(jié)果交給客戶(hù)端確定
public class JavaCourse extends Course {
@Override
void packageCourse() {
System.out.println("4. 提供Java課程源代碼");
}
@Override
protected boolean needWriteArticle() {
return true;
}
}
客戶(hù)端測(cè)試
public class TEST
{
public static void main(String[] args) {
System.out.println("Java課程start---");
Course javaCourse = new JavaCourse();
javaCourse.makeCourse();
System.out.println("Java課程end---\n");
System.out.println("前端課程start---");
Course feCourse = new FECourse(false);
feCourse.makeCourse();
System.out.println("前端課程end---");
}
}
輸出結(jié)果
Java課程start---
1. 制作PPT
2. 制作視頻
3. 編寫(xiě)筆記
4. 提供Java課程源代碼
Java課程end---前端課程start---
1. 制作PPT
2. 制作視頻
4.1 提供課程的前端代碼
4.2 提供課程的圖片等多媒體素材
前端課程end---
模板方法模式總結(jié)
優(yōu)點(diǎn)
- 在父類(lèi)中形式化地定義一個(gè)算法,而由它的子類(lèi)來(lái)實(shí)現(xiàn)細(xì)節(jié)的處理,在子類(lèi)實(shí)現(xiàn)詳細(xì)的處理算法時(shí)并不會(huì)改變算法中步驟的執(zhí)行次序。
- 模板方法模式是一種代碼復(fù)用技術(shù),它在類(lèi)庫(kù)設(shè)計(jì)中尤為重要,它提取了類(lèi)庫(kù)中的公共行為,將公共行為放在父類(lèi)中,而通過(guò)其子類(lèi)來(lái)實(shí)現(xiàn)不同的行為,它鼓勵(lì)我們恰當(dāng)使用繼承來(lái)實(shí)現(xiàn)代碼復(fù)用
- 可實(shí)現(xiàn)一種反向控制結(jié)構(gòu),通過(guò)子類(lèi)覆蓋父類(lèi)的鉤子方法來(lái)決定某一特定步驟是否需要執(zhí)行。
- 在模板方法模式中可以通過(guò)子類(lèi)來(lái)覆蓋父類(lèi)的基本方法,不同的子類(lèi)可以提供基本方法的不同實(shí)現(xiàn),更換和增加新的子類(lèi)很方便,符合單一職責(zé)原則和開(kāi)閉原則
缺點(diǎn)
需要為每一個(gè)基本方法的不同實(shí)現(xiàn)提供一個(gè)子類(lèi),如果父類(lèi)中可變的基本方法太多,將會(huì)導(dǎo)致類(lèi)的個(gè)數(shù)增加,系統(tǒng)更加龐大,設(shè)計(jì)也更加抽象,此時(shí),可結(jié)合橋接模式來(lái)進(jìn)行設(shè)計(jì)。
適用場(chǎng)景
- 對(duì)一些復(fù)雜的算法進(jìn)行分割,將其算法中固定不變的部分設(shè)計(jì)為模板方法和父類(lèi)具體方法,而一些可以改變的細(xì)節(jié)由其子類(lèi)來(lái)實(shí)現(xiàn)。即:一次性實(shí)現(xiàn)一個(gè)算法的不變部分,并將可變的行為留給子類(lèi)來(lái)實(shí)現(xiàn)。
- 各子類(lèi)中公共的行為應(yīng)被提取出來(lái)并集中到一個(gè)公共父類(lèi)中以避免代碼重復(fù)。
- 需要通過(guò)子類(lèi)來(lái)決定父類(lèi)算法中某個(gè)步驟是否執(zhí)行,實(shí)現(xiàn)子類(lèi)對(duì)父類(lèi)的反向控制。
- 一般模板方法都會(huì)加上final關(guān)鍵字,防止子類(lèi)重寫(xiě)該方法
源碼分析模板方法模式的典型應(yīng)用
Servlet 中的模板方法模式
Servlet(Server Applet)是Java Servlet的簡(jiǎn)稱(chēng),用Java編寫(xiě)的服務(wù)器端程序,主要功能在于交互式地瀏覽和修改數(shù)據(jù),生成動(dòng)態(tài)Web內(nèi)容。在每一個(gè) Servlet 都必須要實(shí)現(xiàn) Servlet 接口,GenericServlet 是個(gè)通用的、不特定于任何協(xié)議的Servlet,它實(shí)現(xiàn)了 Servlet 接口,而 HttpServlet 繼承于 GenericServlet,實(shí)現(xiàn)了 Servlet 接口,為 Servlet 接口提供了處理HTTP協(xié)議的通用實(shí)現(xiàn),所以我們定義的 Servlet 只需要繼承 HttpServlet 即可。
HttpServlet 的簡(jiǎn)要代碼如下所示
public abstract class HttpServlet extends GenericServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// ...
}
protected void doPost(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// ...
}
protected void doHead(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// ...
}
protected void doPut(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// ...
}
protected void doDelete(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// ...
}
protected void doOptions(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// ...
}
protected void doTrace(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
// ...
}
protected void service(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
String method = req.getMethod();
if (method.equals(METHOD_GET)) {
long lastModified = getLastModified(req);
if (lastModified == -1) {
// servlet doesn't support if-modified-since, no reason
// to go through further expensive logic
doGet(req, resp);
} else {
long ifModifiedSince = req.getDateHeader(HEADER_IFMODSINCE);
if (ifModifiedSince < lastModified) {
// If the servlet mod time is later, call doGet()
// Round down to the nearest second for a proper compare
// A ifModifiedSince of -1 will always be less
maybeSetLastModified(resp, lastModified);
doGet(req, resp);
} else {
resp.setStatus(HttpServletResponse.SC_NOT_MODIFIED);
}
}
} else if (method.equals(METHOD_HEAD)) {
long lastModified = getLastModified(req);
maybeSetLastModified(resp, lastModified);
doHead(req, resp);
} else if (method.equals(METHOD_POST)) {
doPost(req, resp);
} else if (method.equals(METHOD_PUT)) {
doPut(req, resp);
} else if (method.equals(METHOD_DELETE)) {
doDelete(req, resp);
} else if (method.equals(METHOD_OPTIONS)) {
doOptions(req,resp);
} else if (method.equals(METHOD_TRACE)) {
doTrace(req,resp);
} else {
//
// Note that this means NO servlet supports whatever
// method was requested, anywhere on this server.
//
String errMsg = lStrings.getString("http.method_not_implemented");
Object[] errArgs = new Object[1];
errArgs[0] = method;
errMsg = MessageFormat.format(errMsg, errArgs);
resp.sendError(HttpServletResponse.SC_NOT_IMPLEMENTED, errMsg);
}
}
// ...省略...
}
在 HttpServlet 的 service 方法中,首先獲得到請(qǐng)求的方法名,然后根據(jù)方法名調(diào)用對(duì)應(yīng)的 doXXX 方法,比如說(shuō)請(qǐng)求方法為GET,那么就去調(diào)用 doGet 方法;請(qǐng)求方法為POST,那么就去調(diào)用 doPost 方法
HttpServlet 相當(dāng)于定義了一套處理 HTTP 請(qǐng)求的模板;service 方法為模板方法,定義了處理HTTP請(qǐng)求的基本流程;doXXX 等方法為基本方法,根據(jù)請(qǐng)求方法做相應(yīng)的處理,子類(lèi)可重寫(xiě)這些方法;HttpServletRequest 中的Method則起到鉤子方法的作用.
在開(kāi)發(fā)javaWeb應(yīng)用時(shí),自定義的Servlet類(lèi)一般都擴(kuò)展 HttpServlet 類(lèi),譬如我們實(shí)現(xiàn)一個(gè)輸出 Hello World! 的 Servlet 如下
// 擴(kuò)展 HttpServlet 類(lèi)
public class HelloWorld extends HttpServlet {
public void init() throws ServletException {
// ...
}
public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
response.setContentType("text/html");
PrintWriter out = response.getWriter();
out.println("<h1>Hello World!</h1>");
}
public void destroy() {
// ...
}
}
該自定義的 Servlet 重寫(xiě)了 doGet 方法,當(dāng)客戶(hù)端發(fā)起 GET 請(qǐng)求時(shí)將得到
Hello World!
Spring中的IOC容器啟動(dòng)—refresh()方法
Spring IOC容器初始化時(shí)運(yùn)用到的模板方法模式
1、首先定義一個(gè)接口ConfigurableApplicationContext,聲明模板方法refresh
public interface ConfigurableApplicationContext extends ApplicationContext, Lifecycle, Closeable {
/**聲明了一個(gè)模板方法*/
void refresh() throws BeansException, IllegalStateException;
}
2、抽象類(lèi)AbstractApplicationContext實(shí)現(xiàn)了接口,主要實(shí)現(xiàn)了模板方法refresh(這個(gè)方法很重要,是各種IOC容器初始化的入口)的邏輯
public abstract class AbstractApplicationContext extends DefaultResourceLoader
implements ConfigurableApplicationContext, DisposableBean {
/**模板方法的具體實(shí)現(xiàn)*/
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
// Prepare this context for refreshing.
prepareRefresh();
//注意這個(gè)方法是,里面調(diào)用了兩個(gè)抽象方法refreshBeanFactory、getBeanFactory
// Tell the subclass to refresh the internal bean factory.
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
// Prepare the bean factory for use in this context.
prepareBeanFactory(beanFactory);
try {
//注意這個(gè)方法是鉤子方法
// Allows post-processing of the bean factory in context subclasses.
postProcessBeanFactory(beanFactory);
// Invoke factory processors registered as beans in the context.
invokeBeanFactoryPostProcessors(beanFactory);
// Register bean processors that intercept bean creation.
registerBeanPostProcessors(beanFactory);
// Initialize message source for this context.
initMessageSource();
// Initialize event multicaster for this context.
initApplicationEventMulticaster();
//注意這個(gè)方法是鉤子方法
// Initialize other special beans in specific context subclasses.
onRefresh();
// Check for listener beans and register them.
registerListeners();
// Instantiate all remaining (non-lazy-init) singletons.
finishBeanFactoryInitialization(beanFactory);
// Last step: publish corresponding event.
finishRefresh();
}
catch (BeansException ex) {
// Destroy already created singletons to avoid dangling resources.
destroyBeans();
// Reset 'active' flag.
cancelRefresh(ex);
// Propagate exception to caller.
throw ex;
}
}
}
這里最主要有一個(gè)抽象方法obtainFreshBeanFactory、兩個(gè)鉤子方法postProcessBeanFactory和onRefresh,看看他們?cè)陬?lèi)中的定義
兩個(gè)鉤子方法:
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
}
protected void onRefresh() throws BeansException {
// For subclasses: do nothing by default.
}
再看看獲取Spring容器的抽象方法:
/**其實(shí)他內(nèi)部只調(diào)用了兩個(gè)抽象方法**/
protected ConfigurableListableBeanFactory obtainFreshBeanFactory() {
refreshBeanFactory();
ConfigurableListableBeanFactory beanFactory = getBeanFactory();
if (logger.isDebugEnabled()) {
logger.debug("Bean factory for " + getDisplayName() + ": " + beanFactory);
}
return beanFactory;
}
protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException;
public abstract ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;
具體要取那種BeanFactory容器的決定權(quán)交給了子類(lèi)!
3、具體實(shí)現(xiàn)的子類(lèi),實(shí)現(xiàn)了抽象方法getBeanFactory的子類(lèi)有: AbstractRefreshableApplicationContext
public abstract class AbstractRefreshableApplicationContext extends AbstractApplicationContext {
@Override
public final ConfigurableListableBeanFactory getBeanFactory() {
synchronized (this.beanFactoryMonitor) {
if (this.beanFactory == null) {
throw new IllegalStateException("BeanFactory not initialized or already closed - " +
"call 'refresh' before accessing beans via the ApplicationContext");
}
//這里的this.beanFactory在另一個(gè)抽象方法refreshBeanFactory的設(shè)置的
return this.beanFactory;
}
}
}
public class GenericApplicationContext extends AbstractApplicationContext implements BeanDefinitionRegistry {
@Override
public final ConfigurableListableBeanFactory getBeanFactory() {
//同樣這里的this.beanFactory在另一個(gè)抽象方法中設(shè)置
return this.beanFactory;
}
}
其實(shí)這里的差別還不是很大,我們可以看看另一個(gè)抽象方法refreshBeanFactory的實(shí)現(xiàn),兩個(gè)抽象方法的配合使用。

參考文章
設(shè)計(jì)模式 | 模板方法模式及典型應(yīng)用
設(shè)計(jì)模式——Spring IoC中用到的模板方法模式
總結(jié)
本篇文章就到這里了,希望能夠給你帶來(lái)幫助,也希望您能夠多多關(guān)注腳本之家的更多內(nèi)容!
相關(guān)文章
關(guān)于maven項(xiàng)目中使用BCrypt加密方式
BCrypt是一種基于Blowfish加密算法的密碼散列函數(shù),用于安全存儲(chǔ)和驗(yàn)證用戶(hù)密碼,它通過(guò)引入鹽和工作因子增加計(jì)算復(fù)雜度,有效防止彩虹表攻擊和破解,BCrypt具備適應(yīng)性工作因子、成本參數(shù)調(diào)整、迭代哈希和密鑰擴(kuò)展等特點(diǎn),被廣泛應(yīng)用于Web應(yīng)用程序的安全性設(shè)計(jì)中2024-10-10
java調(diào)用淘寶api聯(lián)網(wǎng)查詢(xún)ip歸屬地
java聯(lián)網(wǎng)查詢(xún)IP歸屬地,原理是根據(jù)淘寶提供的service查詢(xún)IP的歸屬地并且解析http請(qǐng)求返回的json串2014-03-03
Spring中@Controller和@RestController的區(qū)別詳解
這篇文章主要介紹了Spring中@Controller和@RestController的區(qū)別詳解,@RestController?是?@Controller?和?@ResponseBody?的結(jié)合體,單獨(dú)使用?@RestController?的效果與?@Controller?和?@ResponseBody?二者同時(shí)使用的效果相同,需要的朋友可以參考下2023-10-10
Spring?Boot?4.0對(duì)于Java開(kāi)發(fā)的影響和前景
探索Spring?Boot?4.0如何徹底革新Java開(kāi)發(fā),提升效率并開(kāi)拓未來(lái)可能性!別錯(cuò)過(guò)這篇緊湊的指南,它帶你領(lǐng)略Spring?Boot的強(qiáng)大魅力和潛力,準(zhǔn)備好了嗎?2024-02-02
JAVA解決在@autowired,@Resource注入為null的情況
這篇文章主要介紹了JAVA解決在@autowired,@Resource注入為null的情況,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-10-10
手把手教你SpringBoot快速集成Swagger的配置過(guò)程
這篇文章主要介紹了手把手教你SpringBoot快速集成Swagger的配置過(guò)程,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-02-02
LocalDateTime日期時(shí)間格式中間多了一個(gè)T的問(wèn)題及解決
這篇文章主要介紹了LocalDateTime日期時(shí)間格式中間多了一個(gè)T的問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-03-03
Java利用PDFBox實(shí)現(xiàn)PDF文檔基本操作
這篇文章主要為大家詳細(xì)介紹了java如何利用PDFBox實(shí)現(xiàn)PDF文檔基本操作,例如創(chuàng)建PDF文檔、加載PDF文檔、獲取總頁(yè)數(shù)等,需要的小伙伴可以參考下2023-11-11

