設(shè)計(jì)模式-模版方法模式
定義
模版方法模式(Template Method Pattern)又叫模版模式,是指定義一個(gè)操作中的算法的框架,而將一些步驟延遲到子類中.使得子類可以不改變一個(gè)算法的結(jié)構(gòu)即可重定義該算法的某些特定步驟,屬于行為型設(shè)計(jì)模式.
模版方法模式實(shí)際上是封裝了一個(gè)固定流程,該流程由幾個(gè)步驟組成,具體步驟可以由子類進(jìn)行不同實(shí)現(xiàn),從而讓固定的流程產(chǎn)生不同的結(jié)果.它非常簡(jiǎn)單,其實(shí)就是類的繼承機(jī)制,但它卻是一個(gè)應(yīng)用非常廣泛的模式.模版模式的本質(zhì)是抽象封裝流程,具體進(jìn)行實(shí)現(xiàn).
模版方法模式的應(yīng)用場(chǎng)景
當(dāng)一個(gè)操作具有固定的流程時(shí),由抽象固定流程步驟,具體步驟交給子類進(jìn)行具體實(shí)現(xiàn)(固定的流程,不同的實(shí)現(xiàn)).
在我們的日常生活當(dāng)中,模版方法非常的常見.比如以下流程:
再比如趙本山老師的經(jīng)典小品中的一個(gè)段子,把大象放進(jìn)冰箱需要幾步?
那么,同學(xué)們,我們接著研究一下:把長頸鹿放進(jìn)冰箱需要幾步?還是3步嗎?
-
-
-
-
-
從以上的案例中,我們不難總結(jié)出模版方法模式的適用場(chǎng)景:
1萄涯、一次性實(shí)現(xiàn)一個(gè)算法的不變的部分,并將可變的行為留給子類來實(shí)現(xiàn).
2、各子類中公公的行為被提取出來并集中到一個(gè)公共的父類中,從而避免代碼重復(fù).
首先,我們看一下模版方法的通用UML靜態(tài)類圖:
在UML類圖中,我們不難看到,模版方法模式主要包含兩種角色:
抽象模版(AbstractClass):抽象模版類,定義了一套算法框架/流程;
具體實(shí)現(xiàn)(ConcreteClass):具體實(shí)現(xiàn)類,對(duì)算法框架/流程中的某些步驟進(jìn)行了實(shí)現(xiàn).
模版方法模式中的鉤子方法
我們繼續(xù)以趙本山老師的冰箱裝大象事件為例,通過代碼來實(shí)現(xiàn):
抽象模板類中有個(gè)鉤子方法,這里解釋一下.設(shè)計(jì)鉤子方法的主要目的是用來干預(yù)執(zhí)行流程,使得我們能夠控制執(zhí)行流程,是其更符合實(shí)際業(yè)務(wù)需求.鉤子方法的返回值一般為適合條件分支語句的返回值(如:boolean充边、int等).小伙伴們可以根據(jù)自己的實(shí)際業(yè)務(wù)場(chǎng)景決定是否使用鉤子方法.
通過這個(gè)案例,相信小伙伴們一定對(duì)模版方法及鉤子方法有了一個(gè)初步的認(rèn)識(shí).為了加深理解,我們一起去探查另一個(gè)應(yīng)用案例.
利用模版方法模式重構(gòu)JDBC操作業(yè)務(wù)場(chǎng)景
創(chuàng)建一個(gè)模版累JdbcTemplate,封裝所有的JDBC操作.以查詢?yōu)槔?每次查詢的表不同,返回的數(shù)據(jù)結(jié)構(gòu)也不一樣.我們針對(duì)不同的數(shù)據(jù),都要封裝成不同的實(shí)體對(duì)象.而每個(gè)實(shí)體封裝的邏輯都是不一樣的,但封裝前和封裝后的處理流程是不變的,因此,我們可以使用模版方法模式來設(shè)計(jì)這樣的業(yè)務(wù)場(chǎng)景.先創(chuàng)建約束ORM邏輯的接口RowMapper:
再創(chuàng)建封裝了所有處理流程的抽象類JdbcTemplate:
希望通過以上兩個(gè)案例的業(yè)務(wù)場(chǎng)景分析,能夠幫助小伙伴們對(duì)模版方法模式有比較深入的理解.
模版方法模式在源碼中的應(yīng)用
先來看JDK中的AbstractList,來看代碼:
我們看到get()是一個(gè)抽象方法,那么它的邏輯就是交給子類來實(shí)現(xiàn),我們大家熟悉的ArrayList就是AbstractList的子類.同理,有AbstractList就有AbstractSet和AbstractMap,感興趣的小伙伴們可以去看看這些的源碼實(shí)現(xiàn).還有一個(gè)每天都在用的HttpServlet,有3個(gè)方法:service()方法恤煞、doGet()方法和doPost()方法,都是模版方法的抽象實(shí)現(xiàn).
在Mybatis框架也有一些經(jīng)典的應(yīng)用,我們來看一下BaseExecutor類,它是一個(gè)基礎(chǔ)的SQL執(zhí)行類,實(shí)現(xiàn)了大部分的SQL執(zhí)行邏輯,然后把幾個(gè)方法教給子類定制化完成,源碼如下:
如:doUpdate()椅寺、doFlushStatements()梯影、doQuery()、doQueryCursor()這幾個(gè)方法就是由子類來實(shí)現(xiàn)的,那么BaseExecutor有哪些子類呢?我們來看一下它的類圖:
我們?cè)賮砜匆幌耂impleExecutor的doUpdate實(shí)現(xiàn):
再來對(duì)比一下BatchExecutor的doUpdate實(shí)現(xiàn):
通過對(duì)比,我們不難發(fā)現(xiàn)兩者的差異.封裝抽象流程,具體進(jìn)行實(shí)現(xiàn).感興趣的小伙伴可以繼續(xù)深入研究一波MyBatis源碼,我們這里就不繼續(xù)深挖啦.
模版方法模式的優(yōu)點(diǎn)
1诅挑、利用模版方法將相同處理邏輯的代碼放到抽象父類中,可以提高代碼的復(fù)用性.
2四敞、在不同的子類中,通過對(duì)子類的擴(kuò)展增加新的行為,提高代碼的擴(kuò)展性.
3、把不變的行為寫在父類中,去除子類的重復(fù)代碼,提供了一個(gè)很好的代碼復(fù)用平臺(tái),符合開閉原則.
模版方法模式的缺點(diǎn)
1拔妥、類數(shù)目增加,每個(gè)抽象類都需要至少一個(gè)子類來實(shí)現(xiàn),這樣導(dǎo)致類的數(shù)量增加.
2忿危、類數(shù)量的增加,間接地增加了系統(tǒng)實(shí)現(xiàn)的復(fù)雜度.
3、繼承關(guān)系自身缺點(diǎn),如果父類添加新的抽象方法,所有子類都要改一遍.