天天看點

實戰!聊聊工作中使用了哪些設計模式

平時我們寫代碼呢,多數情況都是流水線式寫代碼,基本就可以實作業務邏輯了。如何在寫代碼中找到樂趣呢,我覺得,最好的方式就是:使用設計模式優化自己的業務代碼。今天跟大家聊聊日常工作中,我都使用過哪些設計模式。

實戰!聊聊工作中使用了哪些設計模式

假設有這樣的業務場景,大資料系統把檔案推送過來,根據不同類型采取不同的解析方式。多數的小夥伴就會寫出以下的代碼:

這個代碼可能會存在哪些問題呢?

如果分支變多,這裡的代碼就會變得臃腫,難以維護,可讀性低。

如果你需要接入一種新的解析類型,那隻能在原有代碼上修改。

說得專業一點的話,就是以上代碼,違背了面向對象程式設計的開閉原則以及單一原則。

開閉原則(對于擴充是開放的,但是對于修改是封閉的):增加或者删除某個邏輯,都需要修改到原來代碼

單一原則(規定一個類應該隻有一個發生變化的原因):修改任何類型的分支邏輯代碼,都需要改動目前類的代碼。

如果你的代碼就是醬紫:有多個​<code>​if...else​</code>​等條件分支,并且每個條件分支,可以封裝起來替換的,我們就可以使用政策模式來優化。

政策模式定義了算法族,分别封裝起來,讓它們之間可以互相替換,此模式讓算法的變化獨立于使用算法的的客戶。這個政策模式的定義是不是有點抽象呢?那我們來看點通俗易懂的比喻:

假設你跟不同性格類型的小姐姐約會,要用不同的政策,有的請電影比較好,有的則去吃小吃效果不錯,有的去逛街買買買最合适。當然,目的都是為了得到小姐姐的芳心,請看電影、吃小吃、逛街就是不同的政策。

政策模式針對一組算法,将每一個算法封裝到具有共同接口的獨立的類中,進而使得它們可以互相替換。

政策模式怎麼使用呢?醬紫實作的:

一個接口或者抽象類,裡面兩個方法(一個方法比對類型,一個可替換的邏輯實作方法)

不同政策的差異化實作(就是說,不同政策的實作類)

使用政策模式

A 類型政策具體實作

B 類型政策具體實作

預設類型政策具體實作

如何使用呢?我們借助​<code>​spring​</code>​的生命周期,使用​<code>​ApplicationContextAware​</code>​接口,把對用的政策,初始化到​<code>​map​</code>​裡面。然後對外提供​<code>​resolveFile​</code>​方法即可。

我們來看一個常見的業務場景,下訂單。下訂單接口,基本的邏輯,一般有參數非空校驗、安全校驗、黑名單校驗、規則攔截等等。很多夥伴會使用異常來實作:

這段代碼使用了異常來做邏輯條件判斷,如果後續邏輯越來越複雜的話,會出現一些問題:如異常隻能傳回異常資訊,不能傳回更多的字段,這時候需要自定義異常類。

并且,阿裡開發手冊規定:禁止用異常做邏輯判斷。

【強制】異常不要用來做流程控制,條件控制。

說明:異常設計的初衷是解決程式運作中的各種意外情況,且異常的處理效率比條件判斷方式要低很多。

如何優化這段代碼呢?可以考慮責任鍊模式

當你想要讓一個以上的對象有機會能夠處理某個請求的時候,就使用責任鍊模式。

責任鍊模式為請求建立了一個接收者對象的鍊。執行鍊上有多個對象節點,每個對象節點都有機會(條件比對)處理請求事務,如果某個對象節點處理完了,就可以根據實際業務需求傳遞給下一個節點繼續處理或者傳回處理完畢。這種模式給予請求的類型,對請求的發送者和接收者進行解耦。

責任鍊模式實際上是一種處理請求的模式,它讓多個處理器(對象節點)都有機會處理該請求,直到其中某個處理成功為止。責任鍊模式把多個處理器串成鍊,然後讓請求在鍊上傳遞:

實戰!聊聊工作中使用了哪些設計模式

打個比喻:

假設你晚上去上選修課,為了可以走點走,坐到了最後一排。來到教室,發現前面坐了好幾個漂亮的小姐姐,于是你找張紙條,寫上:“你好, 可以做我的女朋友嗎?如果不願意請向前傳”。紙條就一個接一個的傳上去了,後來傳到第一排的那個妹子手上,她把紙條交給老師,聽說老師40多歲未婚...

責任鍊模式怎麼使用呢?

一個接口或者抽象類

每個對象差異化處理

對象鍊(數組)初始化(連起來)

這個接口或者抽象類,需要:

有一個指向責任下一個對象的屬性

一個設定下一個對象的set方法

給子類對象差異化實作的方法(如以下代碼的doFilter方法)

責任鍊上,每個對象的差異化處理,如本小節的業務場景,就有參數校驗對象、安全校驗對象、黑名單校驗對象、規則攔截對象

運作結果如下:

假設我們有這麼一個業務場景:内部系統不同商戶,調用我們系統接口,去跟外部第三方系統互動(http方式)。走類似這麼一個流程,如下:

實戰!聊聊工作中使用了哪些設計模式

一個請求都會經曆這幾個流程:

查詢商戶資訊

對請求封包加簽

發送http請求出去

對傳回的封包驗簽

這裡,有的商戶可能是走代理出去的,有的是走直連。假設目前有A,B商戶接入,不少夥伴可能這麼實作,僞代碼如下:

假設新加一個C商戶接入,你需要再實作一套這樣的代碼。顯然,這樣代碼就重複了,一些通用的方法,卻在每一個子類都重新寫了這一方法。

如何優化呢?可以使用模闆方法模式。

定義一個操作中的算法的骨架流程,而将一些步驟延遲到子類中,使得子類可以不改變一個算法的結構即可重定義該算法的某些特定步驟。它的核心思想就是:定義一個操作的一系列步驟,對于某些暫時确定不下來的步驟,就留給子類去實作,這樣不同的子類就可以定義出不同的步驟。

打個通俗的比喻:

模式舉例:追女朋友要先“牽手”,再“擁抱”,再“接吻”, 再“拍拍..額..手”。至于具體你用左手還是右手牽,無所謂,但是整個過程,定了一個流程模闆,按照模闆來就行。

一個抽象類,定義骨架流程(抽象方法放一起)

确定的共同方法步驟,放到抽象類(去除抽象方法标記)

不确定的步驟,給子類去差異化實作

我們繼續那以上的舉例的業務流程例子,來一起用 模闆方法優化一下哈:

因為一個個請求經過的流程為一下步驟:

是以我們就可以定義一個抽象類,包含請求流程的幾個方法,方法首先都定義為抽象方法哈:

因為是否走代理流程是不确定的,是以給子類去實作。

商戶A的請求實作:

商戶B的請求實作:

登陸注冊應該是最常見的業務場景了。就拿注冊來說事,我們經常會遇到類似的場景,就是使用者注冊成功後,我們給使用者發一條消息,又或者發個郵件等等,是以經常有如下的代碼:

這塊代碼會有什麼問題呢? 如果産品又加需求:現在注冊成功的使用者,再給使用者發一條短信通知。于是你又得改register方法的代碼了。。。這是不是違反了開閉原則啦。

并且,如果調發短信的接口失敗了,是不是又影響到使用者注冊了?!這時候,是不是得加個異步方法給通知消息才好。。。

實際上,我們可以使用觀察者模式優化。

觀察者模式定義對象間的一種一對多的依賴關系,當一個對象的狀态發生改變時,所有依賴于它的對象都得到通知并被完成業務的更新。

觀察者模式屬于行為模式,一個對象(被觀察者)的狀态發生改變,所有的依賴對象(觀察者對象)都将得到通知,進行廣播通知。它的主要成員就是觀察者和被觀察者。

被觀察者(Observerable):目标對象,狀态發生變化時,将通知所有的觀察者。

觀察者(observer):接受被觀察者的狀态變化通知,執行預先定義的業務。

使用場景: 完成某件事情後,異步通知場景。如,登陸成功,發個IM消息等等。

觀察者模式實作的話,還是比較簡單的。

一個被觀察者的類Observerable ;

多個觀察者Observer ;

觀察者的差異化實作

經典觀察者模式封裝:EventBus實戰

自己搞一套觀察者模式的代碼,還是有點小麻煩。實際上,​<code>​Guava EventBus ​</code>​就封裝好了,它提供一套基于注解的事件總線,api可以靈活的使用,爽歪歪。

我們來看下​<code>​EventBus​</code>​的實戰代碼哈,首先可以聲明一個EventBusCenter類,它類似于以上被觀察者那種角色​<code>​Observerable​</code>​。

然後再聲明觀察者​<code>​EventListener​</code>​

使用demo測試:

運作結果:

工廠模式一般配合政策模式一起使用。用來去優化大量的​<code>​if...else...​</code>​或​<code>​switch...case...​</code>​條件語句。

我們就取第一小節中政策模式那個例子吧。根據不同的檔案解析類型,建立不同的解析對象

其實這就是工廠模式,定義一個建立對象的接口,讓其子類自己決定執行個體化哪一個工廠類,工廠模式使其建立過程延遲到子類進行。

政策模式的例子,沒有使用上一段代碼,而是借助spring的特性,搞了一個工廠模式,哈哈,小夥伴們可以回去那個例子細品一下,我把代碼再搬下來,小夥伴們再品一下吧:

定義工廠模式也是比較簡單的:

一個工廠接口,提供一個建立不同對象的方法。

其子類實作工廠接口,構造不同對象

使用工廠模式

一般情況下,對于工廠模式,你不會看到以上的代碼。工廠模式會跟配合其他設計模式如政策模式一起出現的。

單例模式,保證一個類僅有一個執行個體,并提供一個通路它的全局通路點。 I/O與資料庫的連接配接,一般就用單例模式實作de的。Windows裡面的Task Manager(任務管理器)也是很典型的單例模式。

來看一個單例模式的例子

以上的例子,就是懶漢式的單例實作。執行個體在需要用到的時候,才去建立,就比較懶。如果有則傳回,沒有則建立,需要加下 ​<code>​synchronized ​</code>​關鍵字,要不然可能存線上性安全問題。

其實單例模式還有有好幾種實作方式,如餓漢模式,雙重校驗鎖,靜态内部類,枚舉等實作方式。

餓漢模式,它比較饑餓、比較勤奮,執行個體在初始化的時候就已經建好了,不管你後面有沒有用到,都先建立好執行個體再說。這個就沒有線程安全的問題,但是呢,浪費記憶體空間呀。

雙重校驗鎖實作的單例模式,綜合了懶漢式和餓漢式兩者的優缺點。以上代碼例子中,在synchronized關鍵字内外都加了一層 ​<code>​if ​</code>​條件判斷,這樣既保證了線程安全,又比直接上鎖提高了執行效率,還節省了記憶體空間。

靜态内部類的實作方式,效果有點類似雙重校驗鎖。但這種方式隻适用于靜态域場景,雙重校驗鎖方式可在執行個體域需要延遲初始化時使用。

枚舉實作的單例,代碼簡潔清晰。并且它還自動支援序列化機制,絕對防止多次執行個體化。