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