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è)抽象類公開定義了執(zhí)行它的方法的模板。它的子類可以按需要重寫方法實(shí)現(xiàn),但調(diào)用將以抽象類中定義的方式進(jìn)行簡(jiǎn)單說,模板方法模式定義一個(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è)模式是用來創(chuàng)建一個(gè)算法的模板。什么是模板?如你所見的,模板就是一個(gè)方法。更具體地說,這個(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)制這么做。
使用鉤子的真正目的是什么?
鉤子有幾種用法。如我們之前所說的,鉤子可以讓子類實(shí)現(xiàn)算法中可選的部分,或者在鉤子對(duì)于子類的實(shí)現(xiàn)并不重要的時(shí)候,子類可以對(duì)此鉤子置之不理。鉤子的另一個(gè)用法,是讓子類能夠有機(jī)會(huì)對(duì)模板方法中某些即將發(fā)生的(或剛剛發(fā)生的)步驟作出反應(yīng)。比方說,名為justReOrderedList()的鉤子方法允許子類在內(nèi)部列表重新組織后執(zhí)行某些動(dòng)作(例如在屏幕上重新顯示數(shù)據(jù))。正如前面提到的,鉤子也可以讓子類有能力為其抽象類作一些決定。
好萊塢原則
好萊塢原則簡(jiǎn)單來講就是:別調(diào)用我們,我們會(huì)調(diào)用你。
好萊塢原則可以給我們一種防止“依賴腐敗”的方法。當(dāng)高層組件依賴低層組件,而低層組件又依賴高層組件,而高層組件又依賴邊側(cè)組件,邊側(cè)組件又依賴低層組件時(shí),依賴腐敗就發(fā)生了。在這種情況下,沒有人可以輕易地搞懂系統(tǒng)是如何設(shè)計(jì)的。
在好萊塢原則之下,我們?cè)试S低層組件將自己掛鉤到系統(tǒng)上,但是高層組件會(huì)決定什么時(shí)候和怎樣使用這些低層組件。換句話說,高層組件對(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)用從超類中繼承來的方法。我們所要做的是,避免讓高層和低層組件之間有明顯的環(huán)狀依賴。
好萊塢原則與依賴倒置原則
依賴倒置原則教我們盡量避免使用具體類,而多使用抽象類。而好萊塢原則是用在創(chuàng)建框架或組件上的一種技巧,好讓低層組件能夠被掛鉤進(jìn)計(jì)算中,而且又不會(huì)讓高層組件依賴低層組件。兩者的目標(biāo)都是在于解耦,但是依賴倒置原則更加注重如何在設(shè)計(jì)中避免依賴。
好萊塢原則教我們一個(gè)技巧,創(chuàng)建一個(gè)有彈性的設(shè)計(jì),允許低層結(jié)構(gòu)能夠互相操作,而又防止其他類太過依賴它們。
真實(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ì)有疑問,上面這個(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ì)者受到一些約束。通常我們無法設(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è)過程,該過程要執(zhí)行一系列步驟,這一系列的步驟基本相同,但其個(gè)別步驟在實(shí)現(xiàn)時(shí)可能不同,通常考慮用模板方法模式來處理。
到此這篇關(guān)于Java設(shè)計(jì)模式之模板方法模式Template Method Pattern詳解的文章就介紹到這了,更多相關(guān)Java模板方法模式內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringMVC之簡(jiǎn)單的增刪改查示例(SSM整合)
本篇文章主要介紹了SpringMVC之簡(jiǎn)單的增刪改查示例(SSM整合),這個(gè)例子是基于SpringMVC+Spring+Mybatis實(shí)現(xiàn)的。有興趣的可以了解一下。2017-03-03java操作mongodb實(shí)現(xiàn)CURD功能實(shí)例
mongodb支持多種語言,并且提供了多種語言的驅(qū)動(dòng),本文使用java操作mongodb實(shí)現(xiàn)CURD功能,大家參考使用吧2013-12-12詳解SpringBoot緩存的實(shí)例代碼(EhCache 2.x 篇)
這篇文章主要介紹了詳解SpringBoot緩存的實(shí)例代碼(EhCache 2.x 篇),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-07-07Springboot編寫CRUD時(shí)訪問對(duì)應(yīng)數(shù)據(jù)函數(shù)返回null的問題及解決方法
我在學(xué)習(xí)springboot,其中在編寫CRUD時(shí)發(fā)現(xiàn)訪問數(shù)據(jù)的函數(shù)執(zhí)行下去返回值是null但是其它部分正常,這篇文章主要介紹了Springboot在編寫CRUD時(shí),訪問對(duì)應(yīng)數(shù)據(jù)函數(shù)返回null,需要的朋友可以參考下2024-02-02SpringBoot整合RestTemplate用法的實(shí)現(xiàn)
本篇主要介紹了RestTemplate中的GET,POST,PUT,DELETE、文件上傳和文件下載6大常用的功能,具有一定的參考價(jià)值,感興趣的可以了解一下2023-08-08