一文搞懂設計模式—模板方法模式
模板方法模式(Template Method Pattern),又叫模板模式(Template Pattern),是一種行為設計模式,它定義了一個操作中的算法框架,將某些步驟的具體實現(xiàn)留給子類。通過模板方法模式,我們可以在不改變算法結(jié)構的情況下,允許子類重新定義某些步驟,從而實現(xiàn)代碼復用和擴展。
在軟件開發(fā)中,我們經(jīng)常會遇到需要定義一組相似操作的場景。這些操作可能在整體上有著相同的結(jié)構,但在細節(jié)上有所差異。如果每次都重復編寫這些操作的通用結(jié)構,會導致代碼的冗余性,同時也增加了后期維護的難度。為了解決這個問題,模板方法模式應運而生。
使用場景
模板方法模式適用于以下場景:
- 當存在一組相似的操作,它們具有相同的算法結(jié)構,但實現(xiàn)細節(jié)各不相同時。
- 當希望在不改變算法的整體結(jié)構的情況下,允許子類自由擴展或修改某些步驟時。
- 當希望將算法的實現(xiàn)細節(jié)封裝起來,只暴露出高層接口供調(diào)用者使用時。
JUC 下的 AQS 就使用到了模板方法模式,其中 acquire() 是模板方法。tryAcquire() 方法的具體實現(xiàn)去交給子類完成。
/**
* Acquires in exclusive mode, ignoring interrupts. Implemented
* by invoking at least once {@link #tryAcquire},
* returning on success. Otherwise the thread is queued, possibly
* repeatedly blocking and unblocking, invoking {@link
* #tryAcquire} until success. This method can be used
* to implement method {@link Lock#lock}.
*
* @param arg the acquire argument. This value is conveyed to
* {@link #tryAcquire} but is otherwise uninterpreted and
* can represent anything you like.
*/
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
實現(xiàn)方式
結(jié)構說明
模板方法模式由抽象類和具體子類組成。抽象類定義了算法的框架,其中包含了一個或多個抽象方法,用于由具體子類實現(xiàn)。具體子類繼承抽象類,并根據(jù)需要重寫其中的抽象方法,從而實現(xiàn)具體的細節(jié)。
在模板方法模式中,通常涉及以下幾個角色:
- 抽象類(Abstract Class):抽象類定義了算法的框架,包括一個或多個抽象方法和具體方法。其中的抽象方法由子類實現(xiàn),具體方法可以被子類直接繼承或重寫。
- 具體子類(Concrete Subclass):具體子類繼承抽象類,并根據(jù)需要實現(xiàn)其中的抽象方法。具體子類提供了算法的具體實現(xiàn)細節(jié)。
示例代碼
以下是一個簡單的代碼示例:
// 抽象類,定義模板方法和抽象步驟方法
public abstract class AbstractClass {
// 模板方法,定義算法的整體結(jié)構
public final void templateMethod() {
step1();
step2();
step3();
}
// 模板公共方法
protected final void step1(){
System.out.println("ConcreteClass: Step 1");
}
// 抽象步驟方法,由子類實現(xiàn)具體的步驟邏輯
protected abstract void step2();
// 抽象步驟方法,由子類實現(xiàn)具體的步驟邏輯
protected abstract void step3();
}
// 具體子類,實現(xiàn)抽象步驟方法
public class ConcreteClass extends AbstractClass {
protected void step2() {
System.out.println("ConcreteClass: Step 2");
}
protected void step3() {
System.out.println("ConcreteClass: Step 3");
}
}
// 客戶端代碼
public class Client {
public static void main(String[] args) {
AbstractClass abstractClass = new ConcreteClass();
abstractClass.templateMethod();
}
}
在上述代碼中,我們首先定義了一個抽象類 AbstractClass,其中包含了模板方法和抽象方法。然后,我們創(chuàng)建了具體子類 ConcreteClass,根據(jù)需要實現(xiàn)了抽象方法。
在客戶端代碼 Client 中,我們創(chuàng)建了具體子類的對象,并調(diào)用了模板方法 templateMethod(),從而執(zhí)行了定義好的算法。
運行該代碼將輸出以下結(jié)果:
ConcreteClass: Step 1
ConcreteClass: Step 2
ConcreteClass: Step 3
注意:
- 一般模板方法都加上 final 關鍵字, 防止子類重寫模板方法。
- 抽象模板中的基本方法盡量設計為 protected 類型,符合迪米特法則,不需要暴露的屬性或方法盡量不要設置為 protected 類型。實現(xiàn)類若非必要,盡量不要擴大父類中的訪問權限。
鉤子方法
鉤子方法(Hook Method)是模板方法模式中的一種特殊方法,用于在抽象類中提供一個默認的實現(xiàn),但允許具體子類選擇性地進行重寫或擴展。鉤子方法允許子類在不改變算法骨架的情況下,對算法的某些步驟進行定制。
以下是一個包含鉤子方法的 Java 示例代碼:
// 抽象類,定義模板方法和鉤子方法
public abstract class AbstractClass {
// 模板方法,定義算法的整體結(jié)構
public final void templateMethod() {
step1();
step2();
// 鉤子方法的調(diào)用
if (hookMethod()) {
step3();
}
}
protected abstract void step1();
protected abstract void step2();
// 鉤子方法,默認返回true,子類可以選擇性地重寫
protected boolean hookMethod() {
return true;
}
protected abstract void step3();
}
// 具體子類1
public class ConcreteClass1 extends AbstractClass {
protected void step1() {
System.out.println("ConcreteClass1: Step 1");
}
protected void step2() {
System.out.println("ConcreteClass1: Step 2");
}
protected void step3() {
System.out.println("ConcreteClass1: Step 3");
}
}
// 具體子類2
public class ConcreteClass2 extends AbstractClass {
protected void step1() {
System.out.println("ConcreteClass2: Step 1");
}
protected void step2() {
System.out.println("ConcreteClass2: Step 2");
}
protected boolean hookMethod() {
return false; // 重寫鉤子方法,返回false
}
protected void step3() {
System.out.println("ConcreteClass2: Step 3");
}
}
// 客戶端代碼
public class Client {
public static void main(String[] args) {
AbstractClass class1 = new ConcreteClass1();
class1.templateMethod();
System.out.println("------------------");
AbstractClass class2 = new ConcreteClass2();
class2.templateMethod();
}
}
在上述代碼中,我們定義了一個抽象類 AbstractClass,其中包含模板方法 templateMethod() 和鉤子方法 hookMethod()。在模板方法中,我們先執(zhí)行了step1() 和 step2() 兩個基本操作方法,然后通過調(diào)用鉤子方法決定是否執(zhí)行 step3()。
具體子類 ConcreteClass1 和 ConcreteClass2 繼承了抽象類,并實現(xiàn)了基本操作方法 step1()、step2() 和鉤子方法 hookMethod()、step3()。
在客戶端代碼 Client 中,我們分別創(chuàng)建了具體子類的對象,并調(diào)用其模板方法,從而執(zhí)行了定義好的算法。
運行該示例代碼將輸出以下結(jié)果:
ConcreteClass1: Step 1
ConcreteClass1: Step 2
ConcreteClass1: Step 3
------------------
ConcreteClass2: Step 1
ConcreteClass2: Step 2
通過重寫鉤子方法,具體子類可以選擇性地對算法進行定制化。這就展示了鉤子方法在模板方法模式中的應用。
優(yōu)缺點
優(yōu)點
- 代碼復用:模板方法模式通過將算法的通用結(jié)構定義在抽象類中,可以使子類直接繼承這些通用部分,從而達到代碼復用的目的。
- 擴展性:模板方法模式允許子類根據(jù)需要重寫父類的某些步驟,從而實現(xiàn)對算法的自由擴展和修改,同時保持整體結(jié)構的穩(wěn)定性。
- 封裝性:模板方法模式將算法的實現(xiàn)細節(jié)封裝在抽象類中,對調(diào)用者屏蔽了具體的實現(xiàn)細節(jié),只暴露出高層接口。
缺點
- 模板方法模式將算法的執(zhí)行流程固定在抽象類中,可能會導致代碼的可讀性降低,增加理解和維護的難度。
- 模板方法中的步驟越多, 其維護工作就可能會越困難。
- 通過子類抑制默認步驟實現(xiàn)可能會導致違反里氏替換原則。
總結(jié)
模板方法是一種簡單但非常實用的設計模式,它通過定義一個算法的框架,并將具體實現(xiàn)延遲到子類中,實現(xiàn)了代碼復用和擴展的目的。在具體實現(xiàn)步驟相對固定、但又存在差異性的情況下,模板方法模式能夠很好地解決代碼重復和維護難度的問題。