Java設(shè)計(jì)模式之模板方法模式Template Method Pattern詳解
概述
模板方法
模板方法定義了一個(gè)算法的步驟,并允許子類為一個(gè)或多個(gè)步驟提供實(shí)現(xiàn)。那么什么是模板方法呢?我們看下模板方法的定義。
- 一個(gè)具體方法而非抽象方法,其用作一個(gè)算法的模板;
- 在模板方法中,算法內(nèi)的大多數(shù)步驟都被某個(gè)方法代表;
- 模板方法中某些方法是子類處理
- 需要由子類提供的方法,必須在超類中聲明為抽象方法;
- 模板方法通常不能被覆蓋,也就是使用final修飾;
模板方法模式(Template Method Pattern),又叫模板模式(Template Pattern),指在一個(gè)抽象類公開(kāi)定義了執(zhí)行它的方法的模板。它的子類可以按需要重寫方法實(shí)現(xiàn),但調(diào)用將以抽象類中定義的方式進(jìn)行簡(jiǎn)單說(shuō),模板方法模式定義一個(gè)操作中的算法的骨架,而將一些步驟延遲到子類中,使得子類可以不改變一個(gè)算法的結(jié)構(gòu),就可以重定義該算法的某些特定步驟。
這種類型的設(shè)計(jì)模式屬于行為型模式。
如下實(shí)例,prepareRecipe就是一個(gè)模板方法。
public abstract class CaffeineBeverage {
final void prepareRecipe(){
boilWater();
brew();
pourInCup();
addCondiments();
}
protected abstract void addCondiments();
protected abstract void pourInCup();
protected abstract void brew();
protected abstract void boilWater();
}
模板方法模式
在一個(gè)方法中定義一個(gè)算法的骨架,而將一些步驟延遲到子類中。模板方法使得子類可以在不改變算法結(jié)構(gòu)的情況下,重新定義算法中的某些步驟。
這個(gè)模式是用來(lái)創(chuàng)建一個(gè)算法的模板。什么是模板?如你所見(jiàn)的,模板就是一個(gè)方法。更具體地說(shuō),這個(gè)方法將算法定義成一組步驟,其中的任何步驟都可以是抽象的,由子類負(fù)責(zé)實(shí)現(xiàn)。這可以確保算法的結(jié)構(gòu)保持不變,同時(shí)由子類提供部分實(shí)現(xiàn)。
對(duì)上面實(shí)例進(jìn)一步擴(kuò)展,我們看下抽象類內(nèi)可以有哪些類型的方法。
public abstract class CaffeineBeverage {
final void prepareRecipe(){
boilWater();
brew();
pourInCup();
addCondiments();
concreteOperation();
hook();
}
protected abstract void addCondiments();
protected abstract void pourInCup();
protected abstract void brew();
protected abstract void boilWater();
final void concreteOperation(){
// 這里是實(shí)現(xiàn)
}
//空方法
void hook(){};
}可以看到有一個(gè)具體的concreteOperation方法,final表示其不可以被子類覆蓋。我們也可以由“默認(rèn)不做事的方法”,我們稱這種方法為“hook”(鉤子)。子類可以視情況決定要不要覆蓋他們。
鉤子是一種被聲明在 抽象類中的方法,但只有空的或者默認(rèn)的實(shí)現(xiàn)。鉤子的存在,可以讓子類有能力對(duì)算法的不同點(diǎn)進(jìn)行掛鉤。要不要掛鉤,由子類決定。每一個(gè)具體的子類都必須定義所有的抽象方法,并為模板方法算法中未定義步驟提供完整的實(shí)現(xiàn)。
那么什么時(shí)候使用抽象方法什么時(shí)候使用鉤子呢?
答,當(dāng)你的子類必須提供算法中某個(gè)算法或步驟的實(shí)現(xiàn)時(shí),就是用抽象方法。如果算法的這個(gè)部分是可選的,就用鉤子。如果是鉤子的話,子類可以選擇實(shí)現(xiàn)這個(gè)鉤子,但并不強(qiáng)制這么做。
使用鉤子的真正目的是什么?
鉤子有幾種用法。如我們之前所說(shuō)的,鉤子可以讓子類實(shí)現(xiàn)算法中可選的部分,或者在鉤子對(duì)于子類的實(shí)現(xiàn)并不重要的時(shí)候,子類可以對(duì)此鉤子置之不理。鉤子的另一個(gè)用法,是讓子類能夠有機(jī)會(huì)對(duì)模板方法中某些即將發(fā)生的(或剛剛發(fā)生的)步驟作出反應(yīng)。比方說(shuō),名為justReOrderedList()的鉤子方法允許子類在內(nèi)部列表重新組織后執(zhí)行某些動(dòng)作(例如在屏幕上重新顯示數(shù)據(jù))。正如前面提到的,鉤子也可以讓子類有能力為其抽象類作一些決定。
好萊塢原則
好萊塢原則簡(jiǎn)單來(lái)講就是:別調(diào)用我們,我們會(huì)調(diào)用你。
好萊塢原則可以給我們一種防止“依賴腐敗”的方法。當(dāng)高層組件依賴低層組件,而低層組件又依賴高層組件,而高層組件又依賴邊側(cè)組件,邊側(cè)組件又依賴低層組件時(shí),依賴腐敗就發(fā)生了。在這種情況下,沒(méi)有人可以輕易地搞懂系統(tǒng)是如何設(shè)計(jì)的。
在好萊塢原則之下,我們?cè)试S低層組件將自己掛鉤到系統(tǒng)上,但是高層組件會(huì)決定什么時(shí)候和怎樣使用這些低層組件。換句話說(shuō),高層組件對(duì)待低層組件的方式就是“別調(diào)用我們,我們會(huì)調(diào)用你”。
模板方法模式就契合該原則。當(dāng)我們?cè)O(shè)計(jì)模板方法模式時(shí),我們告訴子類,“不要調(diào)用我們,我們會(huì)調(diào)用你”。
那么低層組件完全不可以調(diào)用高層組件的方法嗎?并不盡然!
事實(shí)上,低層組件在結(jié)束時(shí),常常調(diào)用從超類中繼承來(lái)的方法。我們所要做的是,避免讓高層和低層組件之間有明顯的環(huán)狀依賴。
好萊塢原則與依賴倒置原則
依賴倒置原則教我們盡量避免使用具體類,而多使用抽象類。而好萊塢原則是用在創(chuàng)建框架或組件上的一種技巧,好讓低層組件能夠被掛鉤進(jìn)計(jì)算中,而且又不會(huì)讓高層組件依賴低層組件。兩者的目標(biāo)都是在于解耦,但是依賴倒置原則更加注重如何在設(shè)計(jì)中避免依賴。
好萊塢原則教我們一個(gè)技巧,創(chuàng)建一個(gè)有彈性的設(shè)計(jì),允許低層結(jié)構(gòu)能夠互相操作,而又防止其他類太過(guò)依賴它們。
真實(shí)案例
數(shù)組類Arrays的mergeSort方法就是一個(gè)模板方法。
private static void mergeSort(Object[] src,
Object[] dest,
int low,
int high,
int off) {
int length = high - low;
// Insertion sort on smallest arrays
if (length < INSERTIONSORT_THRESHOLD) {
for (int i=low; i<high; i++)
for (int j=i; j>low &&
((Comparable) dest[j-1]).compareTo(dest[j])>0; j--)
swap(dest, j, j-1);
return;
}
// Recursively sort halves of dest into src
int destLow = low;
int destHigh = high;
low += off;
high += off;
int mid = (low + high) >>> 1;
mergeSort(dest, src, low, mid, -off);
mergeSort(dest, src, mid, high, -off);
// If list is already sorted, just copy from src to dest. This is an
// optimization that results in faster sorts for nearly ordered lists.
if (((Comparable)src[mid-1]).compareTo(src[mid]) <= 0) {
System.arraycopy(src, low, dest, destLow, length);
return;
}
// Merge sorted halves (now in src) into dest
for(int i = destLow, p = low, q = mid; i < destHigh; i++) {
if (q >= high || p < mid && ((Comparable)src[p]).compareTo(src[q])<=0)
dest[i] = src[p++];
else
dest[i] = src[q++];
}
}這里方法中,swap方法是一個(gè)具體方法。每一個(gè)元素需要實(shí)現(xiàn)compareTo方法,“填補(bǔ)”模板方法的缺憾。
可能會(huì)有疑問(wèn),上面這個(gè)案例真的是一個(gè)模板方法嗎?
答,其符合模板方法的精神。這個(gè)模式的重點(diǎn)在于提供一個(gè)算法,并讓子類實(shí)現(xiàn)某些步驟。而數(shù)組的排序做法很明顯地并非如此!但是,我們都知道。Java api中的模式并非總是如同教科書例子一般地中規(guī)中矩,為了符合當(dāng)前的環(huán)境和實(shí)現(xiàn)的約束,它們總是要被適當(dāng)?shù)匦薷?。這個(gè)Arrays類sort方法的設(shè)計(jì)者受到一些約束。通常我們無(wú)法設(shè)計(jì)一個(gè)類繼承Java數(shù)組,而sort()方法希望能夠適用于所有的數(shù)組(每個(gè)數(shù)組都是不同的類)。所以它們定義了一個(gè)靜態(tài)方法,而由被排序的對(duì)象內(nèi)的每個(gè)元素自行提供比較大小的算法部分。所以,這雖然不是教科書上的模板方法,但它的實(shí)現(xiàn)仍然符合模板方法模式的精神。再者,由于不需要基礎(chǔ)數(shù)組就可以使用這個(gè)方法,這樣使得排序變得更有彈性、更有用。
另外一個(gè)案例是java.io的InputStream類有一個(gè)read()方法,是由子類實(shí)現(xiàn)的,而這個(gè)方法又會(huì)被read(byte b[], int off, int len)模板方法使用。
模板方法模式的注意事項(xiàng)和細(xì)節(jié)
基本思想是:算法只存在于一個(gè)地方,也就是在父類中,容易修改。需要修改算法時(shí),只要修改父類的模板方法或者已經(jīng)實(shí)現(xiàn)的某些步驟,子類就會(huì)繼承這些修改。
實(shí)現(xiàn)了最大化代碼復(fù)用。父類的模板方法和已實(shí)現(xiàn)的某些步驟會(huì)被子類繼承而直接使用。 既統(tǒng)一了算法,也提供了很大的靈活性。父類的模板方法確保了算法的結(jié)構(gòu)保持不變,同時(shí)由子類提供部分步驟的實(shí)現(xiàn)。
該模式的不足之處:每一個(gè)不同的實(shí)現(xiàn)都需要一個(gè)子類實(shí)現(xiàn),導(dǎo)致類的個(gè)數(shù)增加,使得系統(tǒng)更加龐大。一般模板方法都加上final 關(guān)鍵字, 防止子類重寫模板方法。
模板方法模式使用場(chǎng)景:當(dāng)要完成在某個(gè)過(guò)程,該過(guò)程要執(zhí)行一系列步驟,這一系列的步驟基本相同,但其個(gè)別步驟在實(shí)現(xiàn)時(shí)可能不同,通??紤]用模板方法模式來(lái)處理。
到此這篇關(guān)于Java設(shè)計(jì)模式之模板方法模式Template Method Pattern詳解的文章就介紹到這了,更多相關(guān)Java模板方法模式內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
mybatis實(shí)體類字段大小寫及字段獲取不到值問(wèn)題
這篇文章主要介紹了mybatis實(shí)體類字段大小寫及字段獲取不到值問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-12-12
Java 敏感詞檢測(cè)工具的實(shí)現(xiàn)
本文介紹了Java敏感詞檢測(cè)工具的使用方法,包括依賴引入、核心方法使用實(shí)例、常規(guī)用法、自定義替換檢測(cè)策略等內(nèi)容,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2024-11-11
解析和解決org.springframework.beans.factory.NoSuchBeanDefinitionE
這篇文章主要介紹了解析和解決org.springframework.beans.factory.NoSuchBeanDefinitionException異常問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2024-04-04
Mybatis-Plus中updateById方法不能更新空值問(wèn)題解決
本文主要介紹了Mybatis-Plus中updateById方法不能更新空值問(wèn)題解決,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-08-08

