版權申明】非商業目的可自由轉載
博文地址:
出自:shusheng007
設計模式系列文章:
秒懂Java代理與動態代理模式
秒懂設計模式之建造者模式(Builder Pattern)
秒懂設計模式之工廠方法模式(Factory Method Pattern)
秒懂設計模式之抽象工廠模式(Abstract Factory Pattern)
秒懂設計模式之策略模式(Strategy Pattern)
秒懂設計模式之橋接模式(Bridge Pattern)
前言
人在IT江湖飄,不懂設計模式咋裝X?
今天我要要談的模板方法模式非常好理解,因爲它特別貼近於我們的日常生活,顧名思義就是給出一個模板,大家按照模板各自去發揮。常常聽一些“牛人”吹噓:我的成功可以被複制!然後就告訴你第一步怎麼幹,第二步怎麼幹,第三步怎麼幹。。。但是10個人去按照他說的模板去做,卻會有10種不同的結果。
定義
Defines the skeleton of an algorithm in a method, deferring some steps to subclasses. Template Method lets subclasses redefine certain steps of an algorithm without changing the algorithms structure.
在一個方法中定義一個算法的骨架,而將一些步驟的實現延遲到子類中,使得子類可以在不改變一個算法的結構前提下即可重定義該算法的某些特定步驟
即使是這麼簡單的概念,新手單純的看它的定義還是不知道在說啥?這又一次印證了理論需要結合實踐的那個真理。那接下來我們就讓我們在實踐中去理解理論。
使用場景
這個設計模式一般在最初寫代碼的時候基本上是不會預先想到的,都是在後期不斷重構的過程中被提煉出來的。簡單來說就是:當你發現你寫了兩個或者多個幾乎一毛一樣的類,只是有些方法的實現方式不一致的時候,你就要停下來想想了,如果你又發現在使用這些類的地方調用方法順序都一樣,不要猶豫請使用模板方法模式重構吧。
具體業務場景
直播行業是2017年互聯網行業的風口,什麼有熊貓直播、映客直播、陌陌直播、虎牙直播。。。說是全民直播也不爲過。王二狗他們老闆也想當風口的豬,就是說它想借着風上天,於是也加入了直播行業大軍。小公司不可能自己開發一套直播的基礎架構系統,實力不允許,於是就想到了接入第三方的直播SDK,剛開始接入了騰訊的直播SDK,後來由於不穩定又要同時接入金山的SDK,而且要做到可以互相切換。
當王二狗帶着小弟林蛋大日夜趕工,一個月後把這個功能實現後發現,你媽這重複代碼太多了,的重構啊,不然老闆哪天又要接其他家的SDK可咋辦,總不能把幾乎一毛一樣的代碼再寫一遍吧。
因爲用戶看直播是有一套固定的模板流程的:登錄—進入房間—獲取音視頻流—觀看—停止音視頻流—退出房間 雖然兩套SDK每一個步驟的實現方式不同,但是基本都是遵循同一套流程。王二狗猛然發現,這不就是給模板方法模式準備的場景嘛,於是乎擼起袖子就幹了!
模板方法模式UML圖
如上圖所示,模板方法模式關鍵點如下:一個模板類LivePlay
,裏面有一個定義爲final
的模板方法seeLivePlay()
. 多個模板實現類。
具體實現
第一步:先定義一個模板類LivePlay
,如下代碼片段所示,其中的seeLivePlay()
就是所謂的模板方法,爲了不被子類overwrite
,它被設置爲final
的,其定義了一個算法骨架。
其中的login()
是一個實體方法,裏面是通用邏輯,所有的子類都是一樣的。四個被abstract
修飾的是抽象方法,這些方法是需要子類去根據自己的實際算法實現的,而pushVideoStream()
方法有一個默認的空實現,這個一般稱爲鉤子方法,設計用來被其中部分需要的子類overwrite
. 例如騰訊直播SDK提供了旁路推流的功能,而金山的沒有提供,那麼騰訊直播類就可以選擇overwrite這個鉤子方法,提供自己的實現。
public abstract class LivePlay {
//模板方法
public final void seeLivePlay() {
login();
openRoom();
startAudioAndVideoStream();
pushVideoStream();
stopAudioAndVideoStream();
closeRoom();
}
//實體方法,這個方法實現通用的業務邏輯
private void login() {
System.out.println("用戶登錄");
}
/*抽象方法*/
//打開房間
public abstract void openRoom();
//打開音視頻流
public abstract void startAudioAndVideoStream();
//關閉音視頻流
public abstract void stopAudioAndVideoStream();
//關閉房間
public abstract void closeRoom();
/*鉤子方法,可以被需要的子類overwrite*/
//旁路推流,可以通過視頻鏈接在瀏覽器中查看視頻
public void pushVideoStream() {
}
}
第二步:定義具體的實體類,根據情況overwrite
相應的抽象方法和鉤子方法。
//騰訊直播類
public class TencentLivePlay extends LivePlay {
@Override
public void openRoom() {
System.out.println("騰訊打開房間");
}
@Override
public void startAudioAndVideoStream() {
System.out.println("騰訊打開音視頻流");
}
@Override
public void stopAudioAndVideoStream() {
System.out.println("騰訊關閉音視頻流");
}
@Override
public void closeRoom() {
System.out.println("騰訊關閉房間");
}
//覆寫鉤子方法,提供旁路推流功能
@Override
public void pushVideoStream() {
super.pushVideoStream();
System.out.println("騰訊進行旁路推流");
}
}
值得注意的是,由於騰訊SDK提供了旁路推流的功能,所以它overwrite
了pushVideoStream()
這個鉤子方法.
//金山直播類
public class JinShanLivePlay extends LivePlay {
@Override
public void openRoom() {
System.out.println("金山打開房間");
}
@Override
public void startAudioAndVideoStream() {
System.out.println("金山打開音視頻流");
}
@Override
public void stopAudioAndVideoStream() {
System.out.println("金山關閉音視頻流");
}
@Override
public void closeRoom() {
System.out.println("金山關閉房間");
}
}
由於金山SDK沒有提供了旁路推流的功能,所以它不用覆寫pushVideoStream()
這個鉤子方法,而只需要overwrite
抽象方法即可。
第三步:客戶端調用
我們根據後端返回的結果來決定使用哪家的SDK
public static void main(String[] args) {
//此處省略若干代碼
...
LivePlay tencentLive=new TencentLivePlay();
tencentLive.seeLivePlay();
System.out.println("");
LivePlay jinShanLive=new JinShanLivePlay();
jinShanLive.seeLivePlay();
}
輸出爲:
用戶登錄
騰訊打開房間
騰訊打開音視頻流
騰訊進行旁路推流
騰訊關閉音視頻流
騰訊關閉房間
用戶登錄
金山打開房間
金山打開音視頻流
金山關閉音視頻流
金山關閉房間
從輸出可以清楚的看到,要使用哪家的服務就實例化哪家的對象,然後調用開始觀看直播即可。
優缺點
優點
主要是提高了代碼的複用度,而且很好的符合的“開閉原則”。
缺點
- 設計模式的通病:類增多了
- 調用控制反轉:一般情況下,程序的執行流是子類調用父類的方法,模板方法模式使得程序流程變成了父類調用子類方法,這個使得程序比較難以理解和跟蹤。
總結
模板方法模式很好的提現了好萊塢原則(Hollywood Principle): Don’t call us, we’ll call you。使得父類來控制程序流程,父類根據程序執行需要將子類的功能勾到父類中,程序流程倒置這個點需要花一點時間適應。
最後還是要悲傷的提一下結局:王二狗猜對了開頭卻沒有猜對結尾,在其帶來手下一幫兄弟如火如荼的接入另一家服務商ucloud的直播SDK期間,老闆破產跑路了,帶沒帶小姨子真不清楚,但是財務王姐跟着跑了。。。
設計模式值得你刻意練習!