模式定義與設計解讀
設計模式晦澀的定義總是難懂,但同時這些定義又有着獨特的涵義。本文想通過最直觀的例子,把這些晦澀的定義反應在代碼層面上。代碼是設計模式最直觀的表達,當你看不懂定義時,代碼會說話。希望這篇解讀可以幫助到你。預計閱讀10分鐘。
定義
責任鏈模式 :使多個對象有機會處理請求,從而避免了請求的發送者和接收者之間的耦合關係。將這些對象連成一條鏈,並沿着這條鏈傳遞該請求,直到有對象處理它爲止。
注意一下加粗的關鍵字,解讀會重新定義這些難懂的定義。
解讀
在生活經常存在一種場景,完成一件事情,需要串行執行幾個步驟。比如把大象裝冰箱,需要三個步驟。
把冰箱門打開
把大象裝冰箱
把冰箱門關上
現在把這件事情抽象成代碼,並與上文中的定義對應。
請求指把大象裝冰箱這件事情,對應一個數據結構
RequestContext
。
/** * 請求上下文 */public class RequestContext { public Elephant mElephant;//大象 public Refrigerator mRefrigerator;//冰箱 public RequestContext(Elephant elephant, Refrigerator refrigerator) { mElephant = elephant; mRefrigerator = refrigerator; } public static class Elephant{ } public static class Refrigerator{ } }
一條鏈比較好理解,但是比較難抽象。既然做這件事需要串行好幾個步驟,鏈條上自然也是圍繞請求展開,既需要有獲取請求的方法
RequestContext getContext()
,也需要有執行請求的方法procceed(RequestContext context)
。這個方法有開始執行的意思,也有請求串行傳遞後繼續執行的意思。
proceed 英 [prə'siːd]
vi. 開始;繼續進行;發生;
/** * 請求鏈 */public interface Chain { /** * 獲取請求實體 */ RequestContext getContext(); /** * 開始或繼續執行請求。 */ void proceed(RequestContext context); }
對象指1.2.3操作大象和冰箱的三個步驟,可以把每次步抽象成一個處理請求的對象,萬物皆對象。現在我們已經把請求封裝在了鏈條裏,那對於每個操作步驟,都直接來處理鏈條上的請求即可。
/** * 處理者 */public interface Handler { /** * 處理鏈條上的請求 */ void handle(Chain chain); }
發送者和接收者其實是指操作步驟的動作。每個操作步驟都會有以下流程
先接收請求(接收)
處理請求 (處理)
通知Chain,繼續執行請求。(發送)
每個操作步驟只需要關注自己需要如何操作請求,而不需要關心操作完成後下個步驟是什麼。只需要通知Chain,我執行完畢了,可以繼續執行下一步了。從而避免了發送者和接收者之間的耦合關係。那麼如何解耦呢,自然是鏈條來解決。鏈條上持有一系列操作步驟,存儲成List<Handler>
,並且記錄當前執行到第幾步int index
,再結合之前定義的Chain
接口,Chain
實現類如下
/** * 鏈實現類 */public class ProcessChain implements Chain { public List<Handler> mProcessors; public RequestContext mChainContext; public int mIndex; public ProcessChain(List<Handler> processors, int index, RequestContext chainContext) { mProcessors = processors; mIndex = index; mChainContext = chainContext; } @Override public RequestContext getContext() { return mChainContext; } @Override public void proceed(RequestContext processContext) { if (mProcessors.size() > mIndex) { //獲取當前處理者 Handler processor = mProcessors.get(mIndex); //更新index 與 Context ProcessChain nextChain = new ProcessChain(mProcessors, mIndex + 1, processContext); //處理者執行處理步驟 processor.handle(nextChain); } } }
proceed方法可能會覺得有些繞,其實道理很簡單。着重理解一下
nextChain
即可,這裏是重新構造了一個Chain
,其實也可以理解爲更新了一下Chain的數據,將index+1
,以及更新RequestContext
。接下來講到Processor
的實現類,二者結合起來理解會更容易nextChain
。
Handler
實現類。接收的概念好理解,就是接口方法handle(Chain chain)
中的參數。那麼什麼是發送呢,我們回到上文nextChain
,實際handle(Chain chain)
方法接收的參數就是下個節點的Chain
,我們只需要在handle()
方法中處理完畢後,繼續調用chain.proceed()
方法,通知鏈條繼續執行就可以了。請求就這樣被髮送出去了。
/** * 打開冰箱 */public class HandlerOpenRefrigerator implements Handler { @Override public void handle(Chain chain) { RequestContext requestContext = chain.getContext(); //處理具體操作 if(!requestContext.mRefrigerator.isOpen()) { requestContext.mRefrigerator.open(); } //處理完畢 繼續執行下個操作 chain.proceed(requestContext); } }
攔截器 當我們執行到第二步時,需要裝大象,如果這時候門沒打開怎麼辦?任務執行不下去,我們可以選擇中斷鏈條,退出本次串行任務。這種鏈條上的任務傳遞和適當時機終止結合起來就是我們經常說的
攔截器
。攔截器就是基於責任鏈模式,每個節點有自己的職責,同時可以選擇是否把任務傳遞給下一個環節
/** * 移動大象 */public class MoveElephantHandler implements Handler { @Override public void handle(Chain chain){ RequestContext requestContext = chain.getContext(); //處理具體操作 if(requestContext.mRefrigerator.isOpen()) { requestContext.mElephant.move(); //處理完畢 繼續執行下個操作 chain.proceed(requestContext); }else{ //發生異常 中斷鏈條 chain.abort(); } } }
推薦閱讀:阿里騰訊Android開發十年,到中年危機就只剩下這套移動架構體系了!
講到這裏再回頭讀一下最開始定義的那句話。你會有不一樣的理解。如果沒有,自己敲一遍代碼再試試。
運行 依次構造請求,責任鏈,然後開始任務。
public class MyClass { public static void main(String[] args) { //構造請求 RequestContext requestContext = new RequestContext(new RequestContext.Elephant(),new RequestContext.Refrigerator()); //構造處理步驟 List<Handler> list = new ArrayList<>(); //你也可以不加打開冰箱試試結果 list.add(new OpenRefrigeratorHandler()); list.add(new MoveElephantHandler()); list.add(new CloseRefrigeratorHandler()); //執行任務 ProcessChain processChain = new ProcessChain(list,0,requestContext); processChain.proceed(requestContext); } }
思想理解與實踐運用
理解
責任鏈重點是在解耦,如何將一個任務的不同步驟之間沒有耦合關係,每個步驟專注負責自身,而不需要關心其他步驟,以及其他步驟的變化,這些變化可以交給更高層次來控制,而不是在任務節點上控制。比如上述例子中,如果裝大象上線後發現很容易失敗,需要先把大象切開(一個殘忍的舉例),那麼只需要在控制層添加list.add(new CutElephantHandler)
即可。這種修改對於既有的代碼侵略性極低,因爲步驟之間解耦很徹底,並且這種可插拔式的代碼,組合性極強。使得代碼的擴展性和可維護性極高。
運用
框架中的運用 :
OkHttp
中有使用Interceptor
就是一個經典的例子。來看看源碼中的定義
/** * Observes, modifies, and potentially short-circuits requests going out and the corresponding * responses coming back in. Typically interceptors add, remove, or transform headers on the request * or response. */public interface Interceptor { Response intercept(Chain chain) throws IOException; interface Chain { Request request(); Response proceed(Request request) throws IOException; } }
是不是很熟悉。Okhttp
中攔截器就是責任鏈模式運用。我們在進行應用開發的時候都會在請求中增加一些通用信息,比如在 header
中增加用戶的token信息等等以及在請求的過程中修改請求的 request
和 response
。那麼我們就可以定義不同的Interceptor
來處理。這些攔截器之間沒有任何關聯,只關注自己處理過程。現在回頭再去看看一些OKhttp的源碼解讀應該就很容易理解其精髓了。
實際開發中的運用:除了在一些既有的框架中使用,我們在Andorid實際開發場景中如何運用呢。再舉個在項目中使用過的場景。我們的App,在HomeActivity啓動後,會依次做幾件事情
檢測升級,出現彈窗1
彈窗1關閉後,出現引導Window
引導圖關閉後,出現選擇語言彈窗。
如果需要是一步一步加上來的。比如第一個版本,我們只有檢測升級。代碼一定很直接,onCreate()
方法裏showUpgradeDialog()
。當來了第二個需求,代碼直接寫就會變成這樣。在升級彈窗的Dialog onDismissListener
裏showGuideWindow()
,如果第一步未檢測到升級,還需要判斷if(!needUpgrade)
直接showGuideWindow()
。代碼寫到這裏,這兩個步驟已經耦合在一起了。引導window是否出現,跟檢測彈窗幾乎綁定在一起。如果有一天產品腦洞大開,我們先出引導再升級吧!想想你的代碼會怎麼改。
在window的 onDismissListener 裏showUpgradeDialog()
?如果使用責任鏈模式呢?在控制層換一下順序就好了。
大家可以嘗試找一下項目裏類似的需求,一些任務需要串行執行的地方,如果耦合嚴重,試着重構一下。
總結
設計模式在開發實踐中,對代碼的可讀,可擴展,可維護性起到重要作用。在面試中,經常會和麪試者聊聊設計模式,大部分面試者都停留在設計概念本身,如果能闡述更多對設計模式的理解與在實踐中的運用,相信會對你的面試有幫助。本文也給大家舉一個設計模式學習的例子,從模式學習,思想理解到實踐運用。回頭再讀一下本文重點:
責任鏈模式 :使多個對象有機會處理請求,從而避免了請求的發送者和接收者之間的耦合關係。將這些對象連成一條鏈,並沿着這條鏈傳遞該請求,直到有對象處理它爲止。
Android設計模式等
關於Android進階的全部學習內容,我們這邊都有系統的知識體系以及進階視頻資料,有需要的朋友可以加羣免費領取安卓進階視頻教程,源碼,面試資料,羣內有大牛一起交流討論技術;818520403(包括自定義控件、NDK、架構設計、混合式開發工程師(React native,Weex)、性能優化、完整商業項目開發等)
Android高級進階視頻