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




























