模板設計模式
認識模板方法
在閻巨集博士的《JAVA與模式》一書中開頭是這樣描述模板方法(Template Method)模式的:
模板方法模式是類的行為模式。準備一個抽象類,將部分邏輯以具體方法以及具體建構函式的形式實現,然後宣告一些抽象方法來迫使子類實現剩餘的邏輯。不同的子類可以以不同的方式實現這些抽象方法,從而對剩餘的邏輯有不同的實現。這就是模板方法模式的用意。
寫程式碼的一個很重要的思考點就是“變與不變”,程式中哪些功能是可變的,哪些功能是不變的。
我們可以把不變的部分抽象出來,進行公共的實現,把變化的部分分離出來,用介面來封裝隔離,或用抽象類約束子類行為。模板方法就很好的體現了這一點。
模板方法定義了一個演算法的步驟,並允許子類為一個或多個步驟提供實現。
模板模式
模板,顧名思義,它是一個固定化、標準化的東西。它是基於繼承的程式碼複用的基本技術
模板方法模式是一種行為設計模式, 它在父類(超類)中定義了一個演算法的框架, 允許子類在不修改結構的情況下重寫演算法的特定步驟。
優點
- 封裝不變部分,擴充套件可變部分
- 提取公共部分程式碼,便於維護
- 行為由父類控制,子類實現
缺點
- 每一個不同的實現都需要一個子類來實現,導致類的個數增加,使得系統更加龐大。
場景問題
模板方法模式就是用來建立一個演算法的模板,這個模板就是方法,該方法將演算法定義成一組步驟,其中的任意步驟都可能是抽象的,由子類負責實現。這樣可以確保演算法的結構保持不變,同時由子類提供部分實現。
假設我們是一家飲品店的師傅,起碼需要以下兩個手藝:
這兩個步驟大同小異,第一反應就是寫個業務介面,不同的飲品實現其中的方法就行,像這樣
第一步和第三步沒什麼差別,而且做飲品是個流程式的工作。我希望使用時,直接呼叫一個方法,就去執行對應的製作步驟。
我們可以使用一個抽象父類,把步驟方法放在一個大的流程方法 makingDrinks()
中,且第一步和第三步,完全一樣,沒必要在子類實現,改進如下:
現在用同一個 makingDrinks()
方法來處理咖啡和茶的製作,而且我們不希望子類覆蓋這個方法,所以可以申明為 final;
不同的製作步驟,我們希望子類來提供,必須在父類申明為抽象方法,而第一步和第三步我們不希望子類重寫,所以我們宣告為非抽象方法
public abstract class Drinks {
public final void makingDrinks() {
//熱水
boilWater();
//沖泡
brew();
//倒進杯子
pourInCup();
//加料
addCondiments();
}
void boilWater() {
System.out.println("將水煮沸");
}
abstract void brew();
void pourInCup() {
System.out.println("倒入杯子");
}
abstract void addCondiments();
}
接著,我們分別處理咖啡和茶,這兩個類只需要繼承父類,重寫其中的抽象方法即可(實現各自的沖泡和新增調料)
Tea:
public class Tea extends Drinks {
@Override
void brew() {
System.out.println("沖茶葉");
}
@Override
void addCondiments() {
System.out.println("加檸檬片");
}
}
Coffee:
public class Coffee extends Drinks {
@Override
void brew() {
System.out.println("衝咖啡粉");
}
@Override
void addCondiments() {
System.out.println("加奶加糖");
}
}
現在可以製作下咖啡和茶吧
public static void main(String[] args) {
Drinks coffee = new Coffee();
coffee.makingDrinks();
System.out.println();
Drinks tea = new Tea();
tea.makingDrinks();
}
這就是模板方法模式,我們的 makingDrinks()
就是模板方法。我們可以看到相同的步驟 boilWater()
和 pourInCup()
只在父類中進行即可,不同的步驟放在子類實現。
鉤子方法
鉤子:在模板方法的父類中,我們可以定義一個方法,它預設不做任何事,子類可以視情況要不要覆蓋它,該方法稱為“鉤子”。
再回顧下我們製作咖啡和茶的例子,有些顧客要不希望咖啡加糖或者不希望茶里加檸檬,我們要改造下模板方法,在加相應的調料之前,問下顧客
public abstract class Drinks {
void boilWater() {
System.out.println("將水煮沸");
}
abstract void brew();
void pourInCup() {
System.out.println("倒入杯子");
}
abstract void addCondiments();
public final void makingDrinks() {
boilWater();
brew();
pourInCup();
//如果顧客需要,才加料
if (customerLike()) {
addCondiments();
}
}
//定義一個空的預設方法,只返回 true
boolean customerLike() {
return true;
}
}
如上,我們加了一個邏輯判斷,邏輯判斷的方法時一個只返回 true 的方法,這個方法我們叫做 鉤子方法。
鉤子方法一般是空的或者有預設實現。鉤子的存在,可以讓子類有能力對演算法的不同點進行掛鉤。而要不要掛鉤,又由子類去決定。
是不是很有用呢,我們再看下咖啡的製作
public class Coffee extends Drinks {
@Override
void brew() {
System.out.println("衝咖啡粉");
}
@Override
void addCondiments() {
System.out.println("加奶加糖");
}
//覆蓋了鉤子,提供了自己的詢問功能,讓使用者輸入是否需要加料
boolean customerLike() {
String answer = getUserInput();
if (answer.toLowerCase().startsWith("y")) {
return true;
} else {
return false;
}
}
//處理使用者的輸入
private String getUserInput() {
String answer = null;
System.out.println("您想要加奶加糖嗎?輸入 YES 或 NO");
BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
try {
answer = reader.readLine();
} catch (IOException e) {
e.printStackTrace();
}
if (answer == null) {
return "no";
}
return answer;
}
}
接著再去測試下程式碼,看看結果吧。
鉤子條件控制,影響抽象類中的演算法流程,當然也可以什麼都不做。
模板方法有很多種實現,有時看起來可能不是我們所謂的“中規中矩”的設計。接下來我們看下 JDK 和 Spring 中是怎麼使用模板方法的。
JDK 中的模板方法
我們寫程式碼經常會用到 comparable 比較器來對陣列物件進行排序,我們都會實現它的 compareTo()
方法,之後就可以通過 Collections.sort()
或者 Arrays.sort()
方法進行排序了。
@Override
public int compareTo(Object o) {
Coffee coffee = (Coffee) o;
if(this.price < (coffee.price)){
return -1;
}else if(this.price == coffee.price){
return 0;
}else{
return 1;
}
}
public static void main(String[] args) {
Coffee[] coffees = {new Coffee("星冰樂",38),
new Coffee("拿鐵",32),
new Coffee("摩卡",35)};
Arrays.sort(coffees);
for (Coffee coffee1 : coffees) {
System.out.println(coffee1);
}
}
你可能會說,這個看著不像我們常規的模板方法,是的。我們看下比較器實現的步驟
- 構建物件陣列
- 通過 Arrays.sort 方法對陣列排序,傳參為
Comparable
介面的例項 - 比較時候會呼叫我們的實現類的
compareTo()
方法 - 將排好序的陣列設定進原陣列中,排序完成
一臉懵逼,這個實現竟然也是模板方法。
這個模式的重點在於提供了一個固定演算法框架,並讓子類實現某些步驟,雖然使用繼承是標準的實現方式,但通過回撥來實現,也不能說這就不是模板方法。
其實併發程式設計中最常見,也是面試必問的 AQS 就是一個典型的模板方法。
Spring 中的模板方法
Spring 中的設計模式太多了,而且大部分擴充套件功能都可以看到模板方式模式的影子。
我們看下 IOC 容器初始化時中的模板方法,不管是 XML 還是註解的方式,對於核心容器啟動流程都是一致的。
AbstractApplicationContext
的 refresh
方法實現了 IOC 容器啟動的主要邏輯。
一個 refresh()
方法包含了好多其他步驟方法,像不像我們說的 模板方法,getBeanFactory()
、refreshBeanFactory()
是子類必須實現的抽象方法,postProcessBeanFactory()
是鉤子方法。
public abstract class AbstractApplicationContext extends DefaultResourceLoader
implements ConfigurableApplicationContext {
@Override
public void refresh() throws BeansException, IllegalStateException {
synchronized (this.startupShutdownMonitor) {
prepareRefresh();
ConfigurableListableBeanFactory beanFactory = obtainFreshBeanFactory();
prepareBeanFactory(beanFactory);
postProcessBeanFactory(beanFactory);
invokeBeanFactoryPostProcessors(beanFactory);
registerBeanPostProcessors(beanFactory);
initMessageSource();
initApplicationEventMulticaster();
onRefresh();
registerListeners();
finishBeanFactoryInitialization(beanFactory);
finishRefresh();
}
}
// 兩個抽象方法
@Override
public abstract ConfigurableListableBeanFactory getBeanFactory() throws IllegalStateException;
protected abstract void refreshBeanFactory() throws BeansException, IllegalStateException;
//鉤子方法
protected void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) {
}
}
開啟你的 IDEA,我們會發現常用的 ClassPathXmlApplicationContext 和 AnnotationConfigApplicationContext 啟動入口,都是它的實現類(子類的子類的子類的...)。
AbstractApplicationContext的一個子類 AbstractRefreshableWebApplicationContext 中有鉤子方法 onRefresh()的實現:
public abstract class AbstractRefreshableWebApplicationContext extends …… {
/**
* Initialize the theme capability.
*/
@Override
protected void onRefresh() {
this.themeSource = UiApplicationContextUtils.initThemeSource(this);
}
}
看下大概的類圖:
小總結
優點:
- 封裝不變的部分,擴充套件可變的部分
- 提取公共程式碼,便於維護
- 行為由父類控制,子類實現
缺點:每一個不同的實現都需要一個子類來實現,導致類的個數增加,使得系統更加龐大。
使用場景:
- 有多個子類共有的方法,且邏輯相同
- 重要的、複雜的方法,可以考慮作為模板方法
注意事項:為防止惡意操作,一般模板方法都加上 final 關鍵詞
【文章轉載自《Java面試攻略》公眾號?】Java面試攻略-模板方法模式
相關文章
- 設計模式-模板模式設計模式
- 設計模式——模板模式設計模式
- Java設計模式——模板設計模式Java設計模式
- 設計模式-模板方法模式設計模式
- 設計模式 ——— 模板方法模式設計模式
- 模板方法設計模式設計模式
- java模板設計模式Java設計模式
- 【設計模式】--模板方法設計模式
- Java設計模式-模板方法模式Java設計模式
- 設計模式之【模板方法模式】設計模式
- js設計模式--模板方法模式JS設計模式
- 設計模式之模板方法模式設計模式
- 設計模式之----Java模板模式設計模式Java
- 設計模式-模板方法模式.md設計模式
- 設計模式(五)——模板方法模式設計模式
- 23種設計模式(四)- 模板方法設計模式設計模式
- 設計模式之模板方法設計模式
- PHP設計模式之模板方法模式PHP設計模式
- 簡說設計模式——模板方法模式設計模式
- python設計模式-模板方法模式Python設計模式
- 設計模式實戰-模板方法模式設計模式
- 極簡設計模式-模板方法模式設計模式
- 設計模式學習-物件模板模式設計模式物件
- 【大話設計模式】—— 模板方法模式設計模式
- 我學設計模式 之 模板模式設計模式
- Java設計模式之模板模式(Template )Java設計模式
- Java描述設計模式(19):模板方法模式Java設計模式
- 設計模式快速學習(六)模板模式設計模式
- C#設計模式(14)——模板方法模式C#設計模式
- 13.java設計模式之模板模式Java設計模式
- 《Head First 設計模式》:模板方法模式設計模式
- Java設計模式之(十三)——模板方法模式Java設計模式
- javascript設計模式 之 8 模板方法模式JavaScript設計模式
- 設計模式—模板模式(DesignPattern_TemplateMethod)設計模式
- Android原始碼設計模式-模板模式Android原始碼設計模式
- 我的Java設計模式-模板方法模式Java設計模式
- 23種設計模式之--模板方法模式設計模式
- JAVA設計模式之模板方法Java設計模式