Java設(shè)計(jì)模式之淺談模板方法模式
一. 什么是模板方法設(shè)計(jì)模式
從字面意義上理解, 模板方法就是定義出來(lái)一套方法, 作為模板, 也就是基礎(chǔ)。 在這個(gè)基礎(chǔ)上, 我們可以進(jìn)行加工,實(shí)現(xiàn)個(gè)性化的實(shí)現(xiàn)。比如:一日餐三. 早餐, 中餐, 晚餐. 每個(gè)人都要吃三餐, 但每個(gè)人的三餐吃的可能都不一樣. 一日三餐定義了模板--早中晚, 每個(gè)人的三餐就是模板的具體實(shí)現(xiàn).
1.1 模板方法的用途
將不變的行為從子類(lèi)搬到超類(lèi),去除了子類(lèi)中的重復(fù)代碼。規(guī)范子類(lèi)的結(jié)構(gòu)
1.2 模板方法的定義
定義一個(gè)操作中的算法骨架,而將算法的一些步驟延遲到子類(lèi)中,使得子類(lèi)可以不改變?cè)撍惴ńY(jié)構(gòu)的情況下重定義該算法的某些特定步驟。它是一種類(lèi)行為型模式。
二. 定義模板方法的步驟
第一步: 定義模板類(lèi)
第二步: 定義具體子類(lèi)
第三步: 客戶(hù)端調(diào)用
下面來(lái)了解每一個(gè)步驟:
2.1 定義模板類(lèi)
通常模板類(lèi)是抽象類(lèi),負(fù)責(zé)給出算法的輪廓或者框架。他是有若干個(gè)模板方法和若干個(gè)基本方法構(gòu)成。
模板方法
定義了算法的骨架, 定義了方法調(diào)用的順序, 其中包含一個(gè)或者多個(gè)基本方法
基本方法
基本算法有三種類(lèi)型:
a) 抽象方法:子類(lèi)必須重寫(xiě)的方法。沒(méi)有默認(rèn)實(shí)現(xiàn)。
b)具體方法:父類(lèi)定義的默認(rèn)實(shí)現(xiàn),有實(shí)現(xiàn)邏輯,可以被具體的子類(lèi)繼承或重寫(xiě)
c)鉤子方法:判斷的邏輯方法和需要子類(lèi)重寫(xiě)的空方法兩種。
2.2 定義具體子類(lèi)
具體子類(lèi),也就是具體的實(shí)現(xiàn)類(lèi), 實(shí)現(xiàn)抽象類(lèi)中的抽象方法。他們是抽象的模板方法中一個(gè)組成部分。
2.3 定義客戶(hù)端調(diào)用
客戶(hù)端調(diào)用抽象類(lèi), 實(shí)例化的時(shí)候?qū)嵗唧w類(lèi), 只需要調(diào)用抽象類(lèi)的模板方法就可以了。
2.4 下面來(lái)看一下抽象類(lèi)和子類(lèi)之間的UML圖和源碼實(shí)現(xiàn)
1.UML圖
從圖中可以看出抽象類(lèi)的結(jié)構(gòu)可以定義三類(lèi)方法。 可以有一個(gè)也可以有多個(gè)。子類(lèi)必須需要實(shí)現(xiàn)抽象類(lèi)中的抽象方法,可以選擇性重寫(xiě)父類(lèi)的具體方法。子類(lèi)實(shí)現(xiàn)接口的時(shí)候,要多思考設(shè)計(jì)模式的六大原則。
2.源碼
先定義抽象類(lèi), 也就是框架。
package com.lxl.www.designPatterns.templatePattern.template; /** * 抽象類(lèi), 定義模板 */ public abstract class AbstractClass { /** * 定義模板方法 * 規(guī)范了流程的框架 */ public void templateMethod() { // 先調(diào)用具體方法 specificMethod(); // 在調(diào)用抽象方法 abstractMethod(); } /** * 具體方法 */ public void specificMethod() { // 具體的公共邏輯, 父子類(lèi)通用 System.out.println("具體方法---父子類(lèi)通用邏輯"); } /** * 抽象方法 * * 抽象方法, 子類(lèi)必須重寫(xiě) */ public abstract void abstractMethod(); }
在定義具體的實(shí)現(xiàn)類(lèi), 實(shí)現(xiàn)父類(lèi)的抽象方法
package com.lxl.www.designPatterns.templatePattern.template; /** * 具體實(shí)現(xiàn)類(lèi) */ public class ConcreteClass extends AbstractClass{ /** * 重寫(xiě)父類(lèi)的抽象方法 */ @Override public void abstractMethod() { System.out.println("具體實(shí)現(xiàn)類(lèi)--重寫(xiě)父類(lèi)的抽象方法"); } }
最后定義客戶(hù)端調(diào)用
package com.lxl.www.designPatterns.templatePattern.template; /** * 模板方法客戶(hù)端 */ public class TemplateClient { public static void main(String[] args) { AbstractClass abstractClass = new ConcreteClass(); abstractClass.templateMethod(); } }
運(yùn)行結(jié)果:
具體方法---父子類(lèi)通用邏輯
具體實(shí)現(xiàn)類(lèi)--重寫(xiě)父類(lèi)的抽象方法
對(duì)照模板方法設(shè)計(jì)模式,我們來(lái)看一個(gè)具體的案例。
三、案例
1. 案例1: 一日規(guī)劃
每個(gè)人的一日安排都有三餐, 早餐, 中餐,晚參。 但每個(gè)人的三餐食物不盡相同,我們來(lái)看看每個(gè)人的三餐變化, 以及三餐前后要做的事情。
package com.lxl.www.designPatterns.templatePattern.oneDayArrangement; /** * 一日三餐抽象類(lèi) */ public abstract class ArrangementAbstract { /** * 模板方法 * 規(guī)定了一天的框架 */ public void templateMethod() { System.out.println("一日安排如下: "); getUp(); breakfast(); lunch(); dinner(); getDown(); } public void getUp() { System.out.println("起床"); } public void getDown() { System.out.println("睡覺(jué)"); } /** * 早餐抽象類(lèi) */ public abstract void breakfast() ; /** * 午餐抽象類(lèi) */ public abstract void lunch(); /** * 晚餐抽象類(lèi) */ public abstract void dinner(); }
定義一日三餐抽象類(lèi)。每個(gè)人的日程安排都是,起床,早餐,中餐,晚餐,睡覺(jué)。 其中起床和睡覺(jué)是每個(gè)人都要做的事情,三餐也是,但三餐的食物不同,于是我們將三餐定義為抽象
一日安排實(shí)現(xiàn)類(lèi)
package com.lxl.www.designPatterns.templatePattern.oneDayArrangement; /** * 張三的一日三餐安排 */ public class PersonArrangement extends ArrangementAbstract{ private String name; public PersonArrangement(String name) { this.name = name; } /** * 早餐抽象類(lèi) */ public void breakfast(){ System.out.println(name + "--早餐吃牛奶面包"); } /** * 午餐抽象類(lèi) */ public void lunch() { System.out.println(name + "--中餐吃食堂"); } /** * 晚餐抽象類(lèi) */ public void dinner() { System.out.println(name + "--晚餐吃水果"); } }
客戶(hù)端調(diào)用
public class Client { public static void main(String[] args) { ArrangementAbstract zhangsan = new PersonArrangement("張三"); zhangsan.templateMethod(); } }
運(yùn)行結(jié)果:
一日安排如下:
起床
張三--早餐吃牛奶面包
張三--中餐吃食堂
張三--晚餐吃水果
睡覺(jué)
可以看出, 完全按照模板方法的步驟實(shí)現(xiàn)。
2. 案例2: 鉤子方法
我們上面說(shuō)了, 模板方法設(shè)計(jì)模式中, 基本方法包括抽象方法,具體方法和鉤子方法.
如果能夠使用好鉤子方法, 可以在程序中完美實(shí)現(xiàn)子類(lèi)控制父類(lèi)的行為. 我們來(lái)看下面的案例:
我們?cè)诔橄蠓椒ㄖ卸x一個(gè)鉤子方法hookMethod(), 在模板方法templateMethod()中,鉤子方法控制了代碼的流程.
UML圖:
源代碼:
package com.lxl.www.designPatterns.templatePattern.hookMethod; /** * 抽象類(lèi), 定義模板 */ public abstract class AbstractClass { /** * 定義模板方法 * 規(guī)范了流程的框架 */ public void templateMethod() { // 調(diào)用具體方法 specificMethod(); // 鉤子方法控制下一步驟 if (hookMethod()) { // 調(diào)用抽象方法 abstractMethod(); } } /** * 具體方法 */ public void specificMethod() { // 具體的公共邏輯, 父子類(lèi)通用 System.out.println("具體方法---父子類(lèi)通用邏輯"); } /** * 鉤子方法 * 鉤子方法是有具體實(shí)現(xiàn)的, */ public boolean hookMethod() { return true; } /** * 抽象方法 * * 抽象方法, 子類(lèi)必須重寫(xiě) */ public abstract void abstractMethod(); }
定義具體實(shí)現(xiàn)
/** * 具體實(shí)現(xiàn)類(lèi) */ public class ConcreteClass extends AbstractClass { /** * 重寫(xiě)父類(lèi)的抽象方法 */ @Override public void abstractMethod() { System.out.println("具體實(shí)現(xiàn)類(lèi)--重寫(xiě)父類(lèi)的抽象方法"); } /** * 鉤子方法 * @return */ @Override public boolean hookMethod() { System.out.println("重寫(xiě)了父類(lèi)的鉤子方法, 反向控制父類(lèi)的行為"); return false; } }
重寫(xiě)了鉤子方法, 反向控制父類(lèi)的行為
public class TemplateClient { public static void main(String[] args) { AbstractClass abstractClass = new ConcreteClass(); abstractClass.templateMethod(); } }
運(yùn)行結(jié)果
具體方法---父子類(lèi)通用邏輯
重寫(xiě)了父類(lèi)的鉤子方法, 反向控制父類(lèi)的行為
如果子類(lèi)鉤子方法 HookMethod() 的代碼改變,則程序的運(yùn)行結(jié)果也會(huì)發(fā)生改變。
四. 模板方法的優(yōu)缺點(diǎn)
4.1 優(yōu)點(diǎn)
1.規(guī)范了框架, 封裝了不變的部分, 擴(kuò)展了可變的部分. 父類(lèi)定義框架, 并抽象了公共不變的部分, 子類(lèi)通過(guò)重寫(xiě)擴(kuò)展完善了框架的實(shí)現(xiàn).
2.使用了"開(kāi)閉原則", 對(duì)擴(kuò)展開(kāi)放, 對(duì)修改關(guān)閉. 子類(lèi)可以通過(guò)重寫(xiě)父類(lèi)的抽象方法來(lái)擴(kuò)展父類(lèi)的實(shí)現(xiàn).
3.行為集中有父類(lèi)控制, 規(guī)范流程
4.2 缺點(diǎn)
1.每一種實(shí)現(xiàn)都需要定義一個(gè)具體實(shí)現(xiàn)類(lèi), 增加類(lèi)的數(shù)量, 系統(tǒng)更加復(fù)雜
2.繼承的缺點(diǎn), 一旦父類(lèi)增加一個(gè)抽象方法, 所有子類(lèi)都需要增加. 這一點(diǎn)違背"開(kāi)閉原則".
3.父類(lèi)中的抽象方法由子類(lèi)實(shí)現(xiàn), 子類(lèi)的執(zhí)行結(jié)果影響父類(lèi), 這種"反向控制"結(jié)構(gòu), 會(huì)增加代碼的復(fù)雜性。
五. 使用場(chǎng)景
1.算法的整體步驟是固定的,但個(gè)別部分容易發(fā)生變化時(shí),可以考慮使用模板方法設(shè)計(jì)模式,將容易發(fā)生變化的部分抽象出來(lái),提供給子類(lèi)去實(shí)現(xiàn)。
2.當(dāng)多個(gè)子類(lèi)存在公共的行為時(shí),可以將其提取出來(lái)并集中到一個(gè)公共父類(lèi)中以避免代碼重復(fù)。首先,要識(shí)別現(xiàn)有代碼中的不同之處,并且將不同之處分離為新的操作。最后,用一個(gè)調(diào)用這些新的操作的模板方法來(lái)替換這些不同的代碼。
3.當(dāng)需要控制子類(lèi)的擴(kuò)展時(shí),模板方法只在特定點(diǎn)調(diào)用鉤子操作,這樣就只允許在這些點(diǎn)進(jìn)行擴(kuò)展。
4.重構(gòu)時(shí),模板方法模式是一個(gè)經(jīng)常使用到的模式,把相同的代碼抽取到父類(lèi)中,通過(guò)鉤子函數(shù)約束其行為
六. 對(duì)設(shè)計(jì)模式六大原則的應(yīng)用思考
1.單一職責(zé)原則: 一個(gè)方法只有一個(gè)引起變化的原因, 這個(gè)不太好看出, 要開(kāi)子類(lèi)代碼的具體實(shí)現(xiàn)
2.里式替換原則: 父類(lèi)出現(xiàn)的地方都可以使用子類(lèi)替換,并且結(jié)果保持一致. 子類(lèi)重寫(xiě)了父類(lèi)的方法。 模板方法設(shè)計(jì)模式可能違背里式替換原則, 不過(guò),這正是能夠“反向控制”的原理
3.接口隔離原則: 依賴(lài)于最小的單一接口, 而不是胖接口. 符合
4.依賴(lài)倒置原則: 依賴(lài)于抽象, 而不是依賴(lài)于具體. 符合
5.迪米特法則: 最少知識(shí)原則. 之和朋友溝通, 減少和朋友的溝通. 這個(gè)需要看子類(lèi)具體實(shí)現(xiàn)是否符合
6.開(kāi)閉原則: 違背開(kāi)閉原則, 一旦父類(lèi)增加一個(gè)抽象方法, 所有子類(lèi)都需要對(duì)應(yīng)增加
到此這篇關(guān)于Java設(shè)計(jì)模式之淺談模板方法模式的文章就介紹到這了,更多相關(guān)Java模板方法內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
java基于Des對(duì)稱(chēng)加密算法實(shí)現(xiàn)的加密與解密功能詳解
這篇文章主要介紹了java基于Des對(duì)稱(chēng)加密算法實(shí)現(xiàn)的加密與解密功能,結(jié)合實(shí)例形式詳細(xì)分析了Des加密算法的功能、原理、使用方法與相關(guān)注意事項(xiàng),需要的朋友可以參考下2017-01-01詳解Java設(shè)計(jì)模式編程中的訪問(wèn)者模式
這篇文章主要介紹了Java設(shè)計(jì)模式編程中的訪問(wèn)者模式,訪問(wèn)者模式的合理利用可以避免項(xiàng)目中出現(xiàn)大量重復(fù)的代碼,需要的朋友可以參考下2016-02-02在IDEA中實(shí)現(xiàn)同時(shí)運(yùn)行2個(gè)相同的java程序
這篇文章主要介紹了在IDEA中實(shí)現(xiàn)同時(shí)運(yùn)行2個(gè)相同的java程序,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2021-02-02SpringBoot選擇自有bean優(yōu)先加載實(shí)現(xiàn)方法
在一些需求中,可能存在某些場(chǎng)景,比如先加載自己的bean,然后自己的bean做一些DB操作,初始化配置問(wèn)題,然后后面的bean基于這個(gè)配置文件,繼續(xù)做其他的業(yè)務(wù)邏輯。因此有了本文的這個(gè)題目2023-03-03利用spring的攔截器自定義緩存的實(shí)現(xiàn)實(shí)例代碼
這篇文章主要介紹了利用spring的攔截器自定義緩存的實(shí)現(xiàn)實(shí)例代碼,分享了相關(guān)代碼示例,小編覺(jué)得還是挺不錯(cuò)的,具有一定借鑒價(jià)值,需要的朋友可以參考下2018-02-02spring bean.xml文件p標(biāo)簽使用報(bào)錯(cuò)的解決
這篇文章主要介紹了spring bean.xml文件p標(biāo)簽使用報(bào)錯(cuò)的解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-08-08詳解Spring Boot工程集成全局唯一ID生成器 UidGenerator的操作步驟
本文就在項(xiàng)目中來(lái)集成 UidGenerator這一工程來(lái)作為項(xiàng)目的全局唯一 ID生成器。接下來(lái)通過(guò)實(shí)例代碼給大家詳解詳解Spring Boot工程集成全局唯一ID生成器 UidGenerator的操作步驟,感興趣的朋友一起看看吧2018-10-10java實(shí)現(xiàn)適用于安卓的文件下載線程類(lèi)
本文給大家分享的是java實(shí)現(xiàn)適用于安卓的文件下載線程類(lèi)的代碼,有需要的小伙伴可以參考下2015-07-07SpringBoot?JavaMailSender發(fā)送郵件功能(實(shí)例詳解)
JavaMailSender是Spring提供的,非常好用的,實(shí)現(xiàn)郵件發(fā)送的接口 ,這篇文章主要介紹了SpringBoot?JavaMailSender發(fā)送郵件功能,需要的朋友可以參考下2024-03-03