Java 設計模式——模板模式

引言

喜愛車的朋友們一定知道,今年tesla發佈了一款車叫model 3,這輛車支持兩種解鎖方式,一種是刷卡進入,還有一種使用手機解鎖,也就是說開走一輛model 3 的步驟如下:解鎖、馳騁、上鎖。如果你身邊有一群開的model 3的朋友,你們相約一起不停的解鎖、馳騁、上鎖,那場面一定很酷。

為什麼要使用模板模式

現在以model 3的開鎖方式來分,一般是以刷卡進入車內和手機解鎖兩種,正常的邏輯思維是抽象成一個父類,子類繼承父類,並實現抽象方法:

public abstract class ITesla{
//開鎖
public abstract void unlock();
//馳騁
public abstract void drive();
//上鎖
public abstract void lock();
//使用
public abstract void use();
}

使用刷卡進入model 3的方式如下:

public class Model3Card extends ITesla{
@Override
public void unlock(){
System.out.println("刷卡進入model 3 ...");
}

@Override

public void drive(){
System.out.println("正在行駛中 ...");
}

@Override
public void lock(){
System.out.println("上鎖 ...");
}

@Override
public void use(){
unlock();
drive();
lock();
}
}

使用手機進入model3的方式如下:

public class Model3Phone extends ITesla{
@Override
public void unlock(){
System.out.println("使用手機進入model 3 ...");
}

@Override
public void drive(){
System.out.println("正在行駛中 ...");
}

@Override
public void lock(){
System.out.println("上鎖 ...");
}

@Override
public void use(){
unlock();
drive();
lock();
}
}

OK, 看客戶端的調用:

public class Client{
public static void main(String[] args){
ITesla card = new Model3Card();
card.use();
System.out.println("========================");
ITesla phone = new Model3Phone();
phone.use();
}
}

相信都已經看出代碼的問題,use方法的實現是一樣的,也就是代碼重複了,這是病必須得治,藥方就是模板方式模式。

什麼是模板模式

模板模式的定義如下:

模板方法模式:定義一個操作中算法的框架,而將一些步驟延遲到子類中。模板方法模式使得子類可以不改變一個算法的結構即可重定義該算法的某些特定步驟。 Template Method Pattern: Define the skeleton of an algorithm in an operation, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithm's structure.

模板方法模式是一種基於繼承的代碼複用技術,它是一種類行為型模式。 模板方法模式是結構最簡單的行為型設計模式,在其結構中只存在父類與子類之間的繼承關係。通過使用模板方法模式,可以將一些複雜流程的實現步驟封裝在一系列基本方法中,在抽象父類中提供一個稱之為模板方法的方法來定義這些基本方法的執行次序,而通過其子類來覆蓋某些步驟,從而使得相同的算法框架可以有不同的執行結果。模板方法模式提供了一個模板方法來定義算法框架,而某些具體步驟的實現可以在其子類中完成。

在軟件開發中,有時會遇到類似的情況,某個方法的實現需要多個步驟(類似“請客”),其中有些步驟是固定的(類似“點單”和“買單”),而有些步驟並不固定,存在可變性(類似“吃東西”)。為了提高代碼的複用性和系統的靈活性,可以使用一種稱之為模板方法模式的設計模式來對這類情況進行設計,在模板方法模式中,將實現功能的每一個步驟所對應的方法稱為基本方法(例如“點單”、“吃東西”和“買單”),而調用這些基本方法同時定義基本方法的執行次序的方法稱為模板方法(例如“請客”)。在模板方法模式中,可以將相同的代碼放在父類中,例如將模板方法“請客”以及基本方法“點單”和“買單”的實現放在父類中,而對於基本方法“吃東西”,在父類中只做一個聲明,將其具體實現放在不同的子類中,在一個子類中提供“吃麵條”的實現,而另一個子類提供“吃滿漢全席”的實現。通過使用模板方法模式,一方面提高了代碼的複用性,另一方面還可以利用面向對象的多態性,在運行時選擇一種具體子類,實現完整的“請客”方法,提高系統的靈活性和可擴展性。

模板方法模式結構與實現

模式結構

模板方法模式結構比較簡單,其核心是抽象類和其中的模板方法的設計,其結構如圖所示:

Java 設計模式——模板模式

模板方法模式包含如下兩個角色:

  1. AbstractClass(抽象類)
  2. 在抽象類中定義了一系列基本操作(PrimitiveOperations),這些基本操作可以是具體的,也可以是抽象的,每一個基本操作對應算法的一個步驟,在其子類中可以重定義或實現這些步驟。同時,在抽象類中實現了一個模板方法(Template Method),用於定義一個算法的框架,模板方法不僅可以調用在抽象類中實現的基本方法,也可以調用在抽象類的子類中實現的基本方法,還可以調用其他對象中的方法。
  3. ConcreteClass(具體子類)

它是抽象類的子類,用於實現在父類中聲明的抽象基本操作以完成子類特定算法的步驟,也可以覆蓋在父類中已經實現的具體基本操作。

模式實現

在實現模板方法模式時,開發抽象類的軟件設計師和開發具體子類的軟件設計師之間可以進行協作。一個設計師負責給出一個算法的輪廓和框架,另一些設計師則負責給出這個算法的各個邏輯步驟。實現這些具體邏輯步驟的方法即為基本方法,而將這些基本方法彙總起來的方法即為模板方法,模板方法模式的名字也因此而來。下面將詳細介紹模板方法和基本方法:

  1. 模板方法

一個模板方法是定義在抽象類中的、把基本操作方法組合在一起形成一個總算法或一個總行為的方法。這個模板方法定義在抽象類中,並由子類不加以修改地完全繼承下來。模板方法是一個具體方法,它給出了一個頂層邏輯框架,而邏輯的組成步驟在抽象類中可以是具體方法,也可以是抽象方法。由於模板方法是具體方法,因此模板方法模式中的抽象層只能是抽象類,而不是接口。

  1. 基本方法

基本方法是實現算法各個步驟的方法,是模板方法的組成部分。基本方法又可以分為三種:抽象方法(Abstract Method)、具體方法(Concrete Method)和鉤子方法(Hook Method)。

(1) 抽象方法:一個抽象方法由抽象類聲明、由其具體子類實現。抽象方法以abstract關鍵字標識。

(2) 具體方法:一個具體方法由一個抽象類或具體類聲明並實現,其子類可以進行覆蓋也可以直接繼承。

(3) 鉤子方法:一個鉤子方法由一個抽象類或具體類聲明並實現,而其子類可能會加以擴展。通常在父類中給出的實現是一個空實現,並以該空實現作為方法的默認實現,當然鉤子方法也可以提供一個非空的默認實現。

在模板方法模式中,鉤子方法有兩類:第一類鉤子方法可以與一些具體步驟“掛鉤”,以實現在不同條件下執行模板方法中的不同步驟,這類鉤子方法的返回類型通常是bool類型的,這類方法名一般為IsXXX(),用於對某個條件進行判斷,如果條件滿足則執行某一步驟,否則將不執行,如下代碼片段所示:

//模板方法
public void templateMethod() {
open();
display();
//通過鉤子方法來確定某步驟是否執行
if (isPrint()) {
print();
}
//鉤子方法
public boolean isPrint(){
return true;
}

在代碼中isPrint()方法即是鉤子方法,它可以決定print()方法是否執行,一般情況下,鉤子方法的返回值為true,如果不希望某方法執行,可以在其子類中覆蓋鉤子方法,將其返回值改為false即可,這種類型的鉤子方法可以控制方法的執行,對一個算法進行約束。

還有一類鉤子方法就是實現體為空的具體方法,子類可以根據需要覆蓋或者繼承這些鉤子方法,與抽象方法相比,這類鉤子方法的好處在於子類如果沒有覆蓋父類中定義的鉤子方法,編譯可以正常通過,但是如果沒有覆蓋父類中聲明的抽象方法,編譯將報錯。

案例

OK,學習了模板模式後,我們將前面的model 3的例子使用模板模式改寫一下.

第一步還是定義我們的父類,一些共同的方法,我們直接在父類實現,如下:

public abstract ITesla{
//開鎖
public abstract void unlock();
//馳騁
public void drive(){
System.out.println("正在行駛中 ...");
}
//上鎖
public void lock(){
System.out.println("上鎖 ...");
}
//使用
public void use(){
unlock();
drive();
lock();
}
}

使用刷卡進入model 3的方式如下:

public class Model3Card extends ITesla{
@Override
public void unlock(){
System.out.println("刷卡進入model 3 ...");
}
}

使用手機進入model 3的方式如下:

public class Model3Phone extends ITesla{
@Override
public void unlock(){
System.out.println("使用手機進入model 3 ...");
}
}

客戶端的調用不變,我們發現在子類中只是實現瞭解鎖的方式不一樣,其他步驟和父類保持不變,消除了重複代碼。

總結

在模板方法模式中,由於面向對象的多態性,子類對象在運行時將覆蓋父類對象,子類中定義的方法也將覆蓋父類中定義的方法,因此程序在運行時,具體子類的基本方法將覆蓋父類中定義的基本方法,子類的鉤子方法也將覆蓋父類的鉤子方法,從而可以通過在子類中實現的鉤子方法對父類方法的執行進行約束,實現子類對父類行為的反向控制。


分享到:


相關文章: