設(shè)計(jì)模式之模版方法模式
大家每到一家公司都會(huì)發(fā)現(xiàn),每個(gè)公司都會(huì)有一個(gè)規(guī)范,比如說請(qǐng)假流程規(guī)范,代碼規(guī)范等等。每個(gè)公司都有這個(gè)流程,只是里面的具體執(zhí)行條件不一樣而已。
在設(shè)計(jì)模式中的模版方法模式,也是可以理解為一種規(guī)范模版。主要是提升我們代碼的復(fù)用性,以及擴(kuò)展等問題。
這樣的模板方法在我們當(dāng)舔狗跟妹妹們聊天的時(shí)候也是可以用到的,比如這樣一個(gè)模板:
“寶,XXXX了,XXXX什么XX?X你的XXX”
當(dāng)我拿到這樣一個(gè)模板的時(shí)候,我就可以舉一反三直接套用了,我們直接填參數(shù)就可以了,比如:
“寶,我打疫苗了,打的什么苗 ,愛你的每一秒 ”
“寶,我做核酸了,做的什么酸,得不到你的心酸”
“寶,今天去輸液了,輸?shù)氖裁匆海肽愕囊?rdquo;
...........
好了言歸正傳,在框架中模版方法模式也是很常見的。
今天就具體來聊聊設(shè)計(jì)模式中行為型設(shè)計(jì)模式中模版方法模式。
設(shè)計(jì)模式系列往期文章:
- 單例模式
- 工廠模式
- 流程引擎
- 建造者模式
- 原型模式
- 責(zé)任鏈模式
- 觀察者模式
- 策略模式
大綱
還是老規(guī)矩從上圖五個(gè)方面來分別具體和大家聊聊模版方法模式
定義
模版方法模式的定義以及目的?
- 定義:模板方法模式在一個(gè)方法中定義一個(gè)算法骨架,并將某些步驟推遲到子類中實(shí)現(xiàn)。模板方法模式可以讓子類在不改變算法整體結(jié)構(gòu)的情況下,重新定義算法中的某些步驟
- 目的:1.使用模版方法模式的目的是避免編寫重復(fù)代碼,以便開發(fā)人員可以專注于核心業(yè)務(wù)邏輯的實(shí)現(xiàn)
- 2.解決接口與接口實(shí)現(xiàn)類之間繼承矛盾問題
- 以上定義來自《設(shè)計(jì)模式之美》
結(jié)構(gòu)圖:
- AbstractTemplate(抽象模版):定義一系列抽象方法,或者實(shí)現(xiàn)的方法,又或者是鉤子方法。即:定義流程
- ConcreteTemplate(具體模版):實(shí)現(xiàn)父類抽象方法,基于本身不同的模版業(yè)務(wù)邏輯,實(shí)現(xiàn)不同的業(yè)務(wù)邏輯代碼。即:抽象方法實(shí)現(xiàn)相同,內(nèi)部邏輯不同
整個(gè)結(jié)構(gòu)圖看起來還是很簡(jiǎn)單的,但是還是要理解設(shè)計(jì)模式解決什么問題。
代碼實(shí)現(xiàn)?還是舉例吧。
還是以上面的請(qǐng)假舉例吧,假設(shè)現(xiàn)在A公司請(qǐng)假需要直屬領(lǐng)導(dǎo)審批以及通知HR有人請(qǐng)假了就可以了,B公司需要直屬領(lǐng)導(dǎo),部門負(fù)責(zé)人審批最后通知HR,方能完成整個(gè)請(qǐng)假流程。那作為OA辦公流程怎么去處理這個(gè)問題嘛?直接看代碼實(shí)現(xiàn)吧!
- public abstract class AskForLeaveFlow {
- // 一級(jí)組長(zhǎng)直接審批
- protected abstract void firstGroupLeader(String name);
- // 二級(jí)組長(zhǎng)部門負(fù)責(zé)人審批
- protected void secondGroupLeader(String name) {
- }
- // 告知HR有人請(qǐng)假了
- private final void notifyHr(String name) {
- System.out.println("當(dāng)前有人請(qǐng)假了,請(qǐng)假人:" + name);
- }
- // 請(qǐng)假流模版
- public void askForLeave(String name) {
- firstGroupLeader(name);
- secondGroupLeader(name);
- notifyHr(name);
- }
- }
首先還是定義一個(gè)請(qǐng)假流程,其中:
- firstGroupLeader方法為abstract修飾,則作為子類都是必須要實(shí)現(xiàn)的
- secondGroupLeader 二級(jí)領(lǐng)導(dǎo)審批,在子類中可以重寫,也可不重寫
- notifyHr 方法為通知HR,已經(jīng)內(nèi)部實(shí)現(xiàn)
最后一個(gè)askForLeave請(qǐng)假流程方法,把以上模版方法串起來
- public class CompanyA extends AskForLeaveFlow {
- @Override
- protected void firstGroupLeader(String name) {
- System.out.println("CompanyA 組內(nèi)有人請(qǐng)假,請(qǐng)假人:" + name);
- }
- }
- public class CompanyB extends AskForLeaveFlow {
- @Override
- protected void firstGroupLeader(String name) {
- System.out.println("CompanyB 組內(nèi)有人請(qǐng)假,請(qǐng)假人:" + name);
- }
- @Override
- protected void secondGroupLeader(String name){
- System.out.println("CompanyB 部門有人請(qǐng)假,請(qǐng)假人:" + name);
- }
- }
在CompanyA以及CompanyB中,secondGroupLeader二級(jí)領(lǐng)導(dǎo)可以選擇重寫或者不重寫,這個(gè)類模版方法簡(jiǎn)稱為鉤子方法。
- public class testTemplate {
- public static void main(String[] args) {
- // 公司A請(qǐng)假流程模版
- AskForLeaveFlow companyA = new CompanyA();
- companyA.askForLeave("敖丙");
- // 結(jié)果:CompanyA 組內(nèi)有人請(qǐng)假,請(qǐng)假人:敖丙
- // 當(dāng)前有人請(qǐng)假了,請(qǐng)假人:敖丙
- AskForLeaveFlow companyB = new CompanyB();
- companyB.askForLeave("敖丙");
- // 結(jié)果:CompanyB 組內(nèi)有人請(qǐng)假,請(qǐng)假人:敖丙
- // CompanyB 部門有人請(qǐng)假,請(qǐng)假人:敖丙
- // 當(dāng)前有人請(qǐng)假了,請(qǐng)假人:敖丙
- }
- }
最后就是看測(cè)試dome結(jié)果了。companyA和companyB分別輸出了對(duì)應(yīng)的請(qǐng)假流程。
細(xì)心的同學(xué)可能已經(jīng)發(fā)現(xiàn)了,做為模版方法中里面除了可以有抽象方法外,還可以有具體的實(shí)現(xiàn)方法以及鉤子方法。
所以大家在應(yīng)用的過程可以多考慮考慮在內(nèi)部定義模版方法時(shí),應(yīng)該定義成抽象方法還是其它的。
框架中的應(yīng)用
模版方法模式在我們常見的Java的框架中也是非常常見的,只是可能我們平時(shí)沒有注意到這一點(diǎn)而已。
第一個(gè):首先我們學(xué)SpringMVC的時(shí)候,最開始都會(huì)寫一些Servlet來作為處理一些post或者get請(qǐng)求等。
這里直接看這個(gè)源碼大家就可以發(fā)現(xiàn)這也是直接使用模版方法模式的思想,期間在HttpServlet 繼承GenericServlet中也還是模版方法的體現(xiàn),這說明了可以多次抽象構(gòu)建模版。
第二個(gè):常見問的文件流中,Java IO 類中的InputStream、OutputStream、Reader、Writer等都能看到模版方法模式的身影。
上面是我貼出的部分InputStream的源碼,主要看這個(gè)read模版方法,也就是模版方法模式的體現(xiàn)。
當(dāng)然IO類中還有很多其他的,我就不一一貼源碼出來了,感情興趣的同學(xué),可以自己打開源碼了解了解。
業(yè)務(wù)舉例
在業(yè)務(wù)中怎么使用模版方法?
首先需要理解模版方法它是為了增加代碼的復(fù)用性,以及擴(kuò)展性而存在的,所以本著這個(gè)思想我還是給大家舉一個(gè)例子吧。
之前寫責(zé)任鏈模式最后給大家舉例商品詳情,這次還是用商品詳情,但是用模版方法模式來實(shí)現(xiàn)這個(gè)問題,理解為商詳2.0版本。
商品詳情展示我們可以是分模塊展示的,比如頭圖,商品信息,sku信息,配送地址,分期付費(fèi)等等。
那么怎么進(jìn)行組裝到商品詳情的展示呢?
流程圖:
可以看到一個(gè)請(qǐng)求過來,可以有模塊組裝器選擇組裝返回結(jié)果。
- 提一個(gè)點(diǎn),在第二步請(qǐng)求的模塊的時(shí)候?yàn)榱藴p少整個(gè)鏈路的請(qǐng)求時(shí)間可以考慮是串行,或者并行(開線程池處理)。
接下來直接看代碼吧
- public abstract class AbstractTemplateBlock<T> {
- // 組裝結(jié)果
- public T template(ModelContainer modelContainer) {
- T block = initBlock();
- try {
- this.doWork(modelContainer, block);
- } catch (Exception e) {
- // 可以選擇捕獲異常,是中斷流程,還是只打印日志,不中斷流程
- }
- return block;
- }
- // 初始化構(gòu)建返回結(jié)果模型
- protected abstract T initBlock();
- // 定義抽象模版
- protected abstract void doWork(ModelContainer modelContainer, T block) throws Exception;
- }
還是先創(chuàng)建模版Block
- @Component
- public class ItemInfoBlock extends AbstractTemplateBlock<ItemInfoBlock.ItemInfo> {
- @Override
- protected ItemInfoBlock.ItemInfo initBlock() {
- return new ItemInfoBlock.ItemInfo();
- }
- // 模擬業(yè)務(wù)邏輯,組裝返回商品信息模塊數(shù)據(jù)
- @Override
- protected void doWork(ModelContainer modelContainer, ItemInfo block) throws Exception {
- block.setItemId(123L);
- block.setItemName("測(cè)試");
- }
- @Data
- public static class ItemInfo {
- private Long itemId;
- private String itemName;
- }
- }
這里只寫了一個(gè)ItemInfoBlock,其他的模塊也是這一樣的寫法,所以就不全寫出來了。
- public static void main(String[] args) {
- // 1.模擬獲取SpringBean
- ApplicationContext applicationContext = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
- ItemInfoBlock itemInfoBlock = (ItemInfoBlock) applicationContext.getBean("itemInfoBlock");
- // 2. ModelContainer可以理解為貫穿上下文中的請(qǐng)求參數(shù),或者一些組裝數(shù)據(jù)需要的預(yù)加載數(shù)據(jù)
- ModelContainer modelContainer = new ModelContainer();
- // 3. 獲取返回結(jié)果
- ItemInfoBlock.ItemInfo itemInfo = itemInfoBlock.template(modelContainer);
- System.out.println(JSON.toJSONString(itemInfo));
- // 結(jié)果:{"itemId":123,"itemName":"測(cè)試"}
- }
最后就是看測(cè)試demo了,可以看到再每一個(gè)模塊中都是有一個(gè)AbstractTemplateBlock,內(nèi)部包含doWork抽象方法,由子類去實(shí)現(xiàn)當(dāng)前自己的業(yè)務(wù)邏輯。
同時(shí)第三步獲取返回結(jié)果時(shí),我只是單獨(dú)列出來,大家可以根據(jù)實(shí)際情況還能做改造。比如說返回map結(jié)構(gòu)等 mapKey 是模塊名稱,value是數(shù)據(jù)。
當(dāng)前這種組裝商品詳情的模式也是比較常見的一種方式。代碼的復(fù)用性高,同時(shí)擴(kuò)展性也有一定的體現(xiàn),符合模版方法模式的思想。
總結(jié)
模版方法模式的特點(diǎn)大家應(yīng)該也能體會(huì)到了,適用場(chǎng)景還是為了增加代碼的復(fù)用性,以及擴(kuò)展性。
還是那句話存在即合理,不要因設(shè)計(jì)模式而在寫代碼時(shí)強(qiáng)行嵌套。合理的學(xué)習(xí)每種設(shè)計(jì)模式適合場(chǎng)景,解決什么問題。