一. 什麼是模板方法設計模式
從字面意義上理解, 模板方法就是定義出來一套方法, 作為模板, 也就是基礎。 在這個基礎上, 我們可以進行加工,實現個性化的實現。比如:一日餐三. 早餐, 中餐, 晚餐. 每個人都要吃三餐, 但每個人的三餐吃的可能都不一樣. 一日三餐定義了模板--早中晚, 每個人的三餐就是模板的具體實現.
1.1 模板方法的用途:
- 將不變的行為從子類搬到超類,去除了子類中的重複程式碼。
- 規範子類的結構
1.2 模板方法的定義
定義一個操作中的演算法骨架,而將演算法的一些步驟延遲到子類中,使得子類可以不改變該演算法結構的情況下重定義該演算法的某些特定步驟。它是一種類行為型模式。
二. 定義模板方法的步驟
第一步: 定義模板類
第二步: 定義具體子類
第三步: 客戶端呼叫
下面來了解每一個步驟:
2.1 定義模板類
通常模板類是抽象類,負責給出演算法的輪廓或者框架。他是有若干個模板方法和若干個基本方法構成。
-
模板方法
定義了演算法的骨架, 定義了方法呼叫的順序, 其中包含一個或者多個基本方法
-
基本方法
基本演算法有三種型別:
a) 抽象方法:子類必須重寫的方法。沒有預設實現。
b)具體方法:父類定義的預設實現,有實現邏輯,可以被具體的子類繼承或重寫
c)鉤子方法:判斷的邏輯方法和需要子類重寫的空方法兩種。
2.2 定義具體子類
具體子類,也就是具體的實現類, 實現抽象類中的抽象方法。他們是抽象的模板方法中一個組成部分。
2.3 定義客戶端呼叫
客戶端呼叫抽象類, 例項化的時候例項化具體類, 只需要呼叫抽象類的模板方法就可以了。
2.4 下面來看一下抽象類和子類之間的UML圖和原始碼實現
- UML圖
從圖中可以看出抽象類的結構可以定義三類方法。 可以有一個也可以有多個。子類必須需要實現抽象類中的抽象方法,可以選擇性重寫父類的具體方法。子類實現介面的時候,要多思考設計模式的六大原則。
-
原始碼
先定義抽象類, 也就是框架。
package com.lxl.www.designPatterns.templatePattern.template;
/**
* 抽象類, 定義模板
*/
public abstract class AbstractClass {
/**
* 定義模板方法
* 規範了流程的框架
*/
public void templateMethod() {
// 先呼叫具體方法
specificMethod();
// 在呼叫抽象方法
abstractMethod();
}
/**
* 具體方法
*/
public void specificMethod() {
// 具體的公共邏輯, 父子類通用
System.out.println("具體方法---父子類通用邏輯");
}
/**
* 抽象方法
*
* 抽象方法, 子類必須重寫
*/
public abstract void abstractMethod();
}
在定義具體的實現類, 實現父類的抽象方法
package com.lxl.www.designPatterns.templatePattern.template;
/**
* 具體實現類
*/
public class ConcreteClass extends AbstractClass{
/**
* 重寫父類的抽象方法
*/
@Override
public void abstractMethod() {
System.out.println("具體實現類--重寫父類的抽象方法");
}
}
最後定義客戶端呼叫
package com.lxl.www.designPatterns.templatePattern.template;
/**
* 模板方法客戶端
*/
public class TemplateClient {
public static void main(String[] args) {
AbstractClass abstractClass = new ConcreteClass();
abstractClass.templateMethod();
}
}
執行結果:
具體方法---父子類通用邏輯
具體實現類--重寫父類的抽象方法
對照模板方法設計模式,我們來看一個具體的案例。
三、案例
1. 案例1: 一日規劃
每個人的一日安排都有三餐, 早餐, 中餐,晚參。 但每個人的三餐食物不盡相同,我們來看看每個人的三餐變化, 以及三餐前後要做的事情。
package com.lxl.www.designPatterns.templatePattern.oneDayArrangement;
/**
* 一日三餐抽象類
*/
public abstract class ArrangementAbstract {
/**
* 模板方法
* 規定了一天的框架
*/
public void templateMethod() {
System.out.println("一日安排如下: ");
getUp();
breakfast();
lunch();
dinner();
getDown();
}
public void getUp() {
System.out.println("起床");
}
public void getDown() {
System.out.println("睡覺");
}
/**
* 早餐抽象類
*/
public abstract void breakfast() ;
/**
* 午餐抽象類
*/
public abstract void lunch();
/**
* 晚餐抽象類
*/
public abstract void dinner();
}
定義一日三餐抽象類。每個人的日程安排都是,起床,早餐,中餐,晚餐,睡覺。 其中起床和睡覺是每個人都要做的事情,三餐也是,但三餐的食物不同,於是我們將三餐定義為抽象
一日安排實現類
package com.lxl.www.designPatterns.templatePattern.oneDayArrangement;
/**
* 張三的一日三餐安排
*/
public class PersonArrangement extends ArrangementAbstract{
private String name;
public PersonArrangement(String name) {
this.name = name;
}
/**
* 早餐抽象類
*/
public void breakfast(){
System.out.println(name + "--早餐吃牛奶麵包");
}
/**
* 午餐抽象類
*/
public void lunch() {
System.out.println(name + "--中餐吃食堂");
}
/**
* 晚餐抽象類
*/
public void dinner() {
System.out.println(name + "--晚餐吃水果");
}
}
客戶端呼叫
public class Client {
public static void main(String[] args) {
ArrangementAbstract zhangsan = new PersonArrangement("張三");
zhangsan.templateMethod();
}
}
執行結果:
一日安排如下:
起床
張三--早餐吃牛奶麵包
張三--中餐吃食堂
張三--晚餐吃水果
睡覺
可以看出, 完全按照模板方法的步驟實現。
2. 案例2: 鉤子方法
我們上面說了, 模板方法設計模式中, 基本方法包括抽象方法,具體方法和鉤子方法.
如果能夠使用好鉤子方法, 可以在程式中完美實現子類控制父類的行為. 我們來看下面的案例:
我們在抽象方法中定義一個鉤子方法hookMethod(), 在模板方法templateMethod()中,鉤子方法控制了程式碼的流程.
UML圖:
原始碼:
package com.lxl.www.designPatterns.templatePattern.hookMethod;
/**
* 抽象類, 定義模板
*/
public abstract class AbstractClass {
/**
* 定義模板方法
* 規範了流程的框架
*/
public void templateMethod() {
// 呼叫具體方法
specificMethod();
// 鉤子方法控制下一步驟
if (hookMethod()) {
// 呼叫抽象方法
abstractMethod();
}
}
/**
* 具體方法
*/
public void specificMethod() {
// 具體的公共邏輯, 父子類通用
System.out.println("具體方法---父子類通用邏輯");
}
/**
* 鉤子方法
* 鉤子方法是有具體實現的,
*/
public boolean hookMethod() {
return true;
}
/**
* 抽象方法
*
* 抽象方法, 子類必須重寫
*/
public abstract void abstractMethod();
}
定義具體實現
/**
* 具體實現類
*/
public class ConcreteClass extends AbstractClass {
/**
* 重寫父類的抽象方法
*/
@Override
public void abstractMethod() {
System.out.println("具體實現類--重寫父類的抽象方法");
}
/**
* 鉤子方法
* @return
*/
@Override
public boolean hookMethod() {
System.out.println("重寫了父類的鉤子方法, 反向控制父類的行為");
return false;
}
}
重寫了鉤子方法, 反向控制父類的行為
public class TemplateClient {
public static void main(String[] args) {
AbstractClass abstractClass = new ConcreteClass();
abstractClass.templateMethod();
}
}
執行結果
具體方法---父子類通用邏輯
重寫了父類的鉤子方法, 反向控制父類的行為
如果子類鉤子方法 HookMethod() 的程式碼改變,則程式的執行結果也會發生改變。
四. 模板方法的優缺點
4.1 優點
- 規範了框架, 封裝了不變的部分, 擴充套件了可變的部分. 父類定義框架, 並抽象了公共不變的部分, 子類通過重寫擴充套件完善了框架的實現.
- 使用了"開閉原則", 對擴充套件開放, 對修改關閉. 子類可以通過重寫父類的抽象方法來擴充套件父類的實現.
- 行為集中有父類控制, 規範流程
4.2 缺點
- 每一種實現都需要定義一個具體實現類, 增加類的數量, 系統更加複雜
- 繼承的缺點, 一旦父類增加一個抽象方法, 所有子類都需要增加. 這一點違背"開閉原則".
- 父類中的抽象方法由子類實現, 子類的執行結果影響父類, 這種"反向控制"結構, 會增加程式碼的複雜性。
五. 使用場景
- 演算法的整體步驟是固定的,但個別部分容易發生變化時,可以考慮使用模板方法設計模式,將容易發生變化的部分抽象出來,提供給子類去實現。
- 當多個子類存在公共的行為時,可以將其提取出來並集中到一個公共父類中以避免程式碼重複。首先,要識別現有程式碼中的不同之處,並且將不同之處分離為新的操作。最後,用一個呼叫這些新的操作的模板方法來替換這些不同的程式碼。
- 當需要控制子類的擴充套件時,模板方法只在特定點呼叫鉤子操作,這樣就只允許在這些點進行擴充套件。
- 重構時,模板方法模式是一個經常使用到的模式,把相同的程式碼抽取到父類中,通過鉤子函式約束其行為
六. 對設計模式六大原則的應用思考
- 單一職責原則: 一個方法只有一個引起變化的原因, 這個不太好看出, 要開子類程式碼的具體實現
- 裡式替換原則: 父類出現的地方都可以使用子類替換,並且結果保持一致. 子類重寫了父類的方法。 模板方法設計模式可能違背裡式替換原則, 不過,這正是能夠“反向控制”的原理
- 介面隔離原則: 依賴於最小的單一介面, 而不是胖介面. 符合
- 依賴倒置原則: 依賴於抽象, 而不是依賴於具體. 符合
- 迪米特法則: 最少知識原則. 之和朋友溝通, 減少和朋友的溝通. 這個需要看子類具體實現是否符合
- 開閉原則: 違背開閉原則, 一旦父類增加一個抽象方法, 所有子類都需要對應增加