【設計模式】漢堡中的設計模式——策略模式

碼農Amg發表於2021-12-03

【設計模式】漢堡中的設計模式——策略模式

每章一句

Yesterday home runs don't win today games

前言

哈嘍,大家好,今天要分享的知識點是關於策略模式的使用,觀看本文章可能需要耗費【8】分鐘,通過本文,你可以認識到以下幾個知識點

  • 什麼是策略模式
  • 針對策略模式的侷限,又有哪些解決辦法
  • 列舉策略瞭解一下?

情景帶入

話說昨天,麥當勞搞活動,板燒只要5塊大洋!!!下班了之後我就騎著心愛的小摩托飛奔過去,在等待了一段(long)時(long)間(time)...... 終於如願以償的握著這簡單的快樂

看著手裡的板燒,心裡突然就有了一些想法,現在搞活動,部分商品低價就可以拿到,但是搞活動不能一直搞吧,那不然肯定虧大本了,活動形式也總不能一成不變吧,需要保持新穎,多元化吸引顧客消費

那麼變化點就出來了,這個形式是可以不斷變化的,例如會有這麼幾種

  • 打折活動的時候,只需要5塊大洋就能拿下一個漢堡
  • 買一送一活動的時候,原價可以拿到兩個
  • 優惠券活動,有的時候是有一些優惠券的,達到門檻減多少
  • 沒有活動時,需要原價購買

我們常說要透過現象看本質,儘管形式很多,但是萬變不離其中,最終是要付錢產生價值的,那就來分析一下這個流程吧!

開始分析

我們再來模擬一下,顧客在各種形式下是怎麼點餐的

  • 沒有活動時,我把漢堡新增到購物車,建立訂單,支付,等待出餐
  • 搞打折活動時,漢堡只需要5塊大洋,通過指定連結,把漢堡加到購物車,建立訂單,支付,等待出餐
  • 搞買一送一活動時,把漢堡新增到購物車,建立訂單,支付,等待出餐
  • 搞滿減活動時,把漢堡和中薯、那麼大雞排新增到購物車,湊足滿減金額,建立訂單,使用優惠券,支付出餐

通過畫圖的形式展示一下上述的邏輯

image-20211202103437081

上面是完整的步驟,簡化一下,我們只關注產生價值的部分,也即客戶支付了多少錢,取到了哪些食物

image-20211202103937268

進一步說就是,商戶可以隨意切換形式,比如昨天有5元的板燒,今天點進去看發現沒有了,但是多出來了其他的優惠,例如【麥樂雞20粒】優惠券,後天進去發現優惠券都沒了,只能原價購買等情況;但是無論形式是這樣的,最終產出時的步驟都是一致的,例如這裡就是支付和取餐

繞了這麼久,其實就是要引出今天的主角————策略模式

策略模式

標準定義以及類圖

定義一組演算法,並封裝每個演算法,讓它們彼此可以交換使用。策略模式讓這些演算法在客戶端中使用起來更加獨立

image-20211202142525934

如果以上述例子來看,所有形式都是一種特定的演算法

  • 原價購買是一種演算法
  • 打折也是一種演算法
  • 優惠券滿減也是演算法
  • 買一送一也是演算法
  • ....

演算法具體的如何實現的,客戶端不管,客戶端只知道,我可以任意切換形式,並且達成想要的效果

就好比顧客知道有這個活動,但不用知道這個活動的其他細節,我只需要按照步驟操作即可有優惠

嘗試編碼

既然上述幾種情況最終都需要支付和取餐,那麼我們直接就定義一個頂層介面管理這些演算法(相當於是AbstractStrategy),介面中有兩個方法

  • 一個是返回實際需要付多少錢
  • 一個是返回實際取到的食物列表

具體如何實現,就是每個演算法內部的事情了,別人管不了,相當於是每一個ConcentrateStrategy

從類圖上看到,標準的策略模式還要有一個元件Context,他就類似是策略模式的客戶端,要呼叫哪一個策略,跟Context溝通,不跟具體實現溝通,這樣做的好處就是實現客戶端(真正的呼叫方)與具體實現間的解耦,如下圖所示

image-20211202145103765

所以,根據設計,我們把程式碼給敲一下

  • 首先是頂層介面程式碼

  • 然後是各個具體演算法的實現

  • Context程式碼

  • 客戶端呼叫情況

    輸出結果

    image-20211202150316134

如果我要新新增一種形式呢?

麥當勞“壕無人性”地說,今天薯條免費贈送,那麼針對這種情況,很明顯現有的演算法家族是不支援的,那麼我們只需要再新增一種演算法,叫做【FreePrice】,然後交給Client呼叫即可

我們完全不需要動其他已經寫好的演算法,這很符合OCP原則,並且演算法的具體實現也被完美的隱藏在各個實現類中,實在是很nice

策略模式的優點

其實剛剛也講了,這裡再總結一下

  • 演算法的具體實現封裝在各個實現類中,客戶端不需要知道
  • 客戶端可以根據場合隨意切換到底要使用哪一種策略
  • 將客戶端與具體實現通過Context解耦,即符合OCP原則,又可以讓具體演算法獨立發展而不會影響其他類修改

策略模式的侷限

那麼,可能有小夥伴就像提問了,策略模式這麼牛逼,他就沒有一點侷限性嗎?這裡引用我在看《Head First 設計模式》中看到的一段話,他的意思是

設計模式的定義告訴我們,問題包含了一個目標和一組約束;光明的方向就是你的目標,黑暗的方向就是這些約束

光明與黑暗總是相伴而生,所以策略模式的約束是什麼?不妨從我們實際呼叫的方向入手,思考一下?

商戶在實際設定新行為的時候,肯定是會有一個UI介面,會有下拉選擇本次要推出的形式,針對不同的形式,所需要的引數也不盡相同,例如形式是優惠卷滿減的時候,需要有滿減閾值,滿減多少,優惠券時效等等引數

那麼傳送到後端建立的時候,url請求也許是這樣子的(簡單給個例子):newActivity?type=1

其中type就是標識,我們具體選擇的策略,比如

  • type=1代表是原價
  • type=2代表是打折
  • type=3代表是滿減

然後回看一下剛剛寫的程式碼,在Client端呼叫,簡直就是太過於理想化了,真正呼叫的時候,不可能這麼寫的

image-20211202160139151

實際上,對應處理的Controller(客戶端)在接收到方法的時候,最基礎時要這麼來判斷

//虛擬碼,暫不校驗欄位有效性問題

if (1 == type)
    then xxxx
else if (2 == type)
    then xxxx
else if (3 == type)
    then xxxx
else if (4 == type)
    then xxxx
    
    ....

你說這個,跟我瞭解策略模式的侷限性到底有什麼關係啊?其實仔細品一下,就會發現,儘管我們把各個演算法的實現細節都給隱藏了,當時我們依然需要知道有多少種策略,換言之就是,我們在選擇策略的時候不免要進行判斷,這就是策略模式一個侷限

第二個就是,每次都需要新建一個類單獨做一個演算法策略,如果有很多很多的演算法策略,就會導致類結構非常的膨脹,此時,就不易於維護和管理

解決侷限性問題

針對客戶端存在一大堆的if-else,我們使用簡單工廠的形式配合把他們都封裝起來

簡單工廠+策略模式解決客戶端大量if-else情況

原來的設計不變,把Context給替換成HandlerFactory,通過靜態方法返回資訊,這裡為了更加貼合實際,定義了兩個VO物件

簡單工廠

最終客戶端呼叫

使用postman進行測試,為了方便起見,引數就不改變了,就改變type,實際上是不同的type,引數也會不相同

  1. type為1,原價方式

  2. type為2,優惠券滿減策略image-20211202183040457

  3. type為3,折扣策略

    image-20211202183131883

  4. type為4,買一送一策略

    image-20211202183158039

  5. type為5,免費送策略

    image-20211202183250444

通過簡單工廠+策略模式,我們把原本存在於客戶端中的判斷給挪到工廠裡面,把所有的執行邏輯都隱藏起來了;每次有新的策略,只需要新建一個類,修改一下HandlerFactory中的selectStrategy方法即可

不過這也導致了HandlerFactory這個類違背了OCP原則,但是相對於一大段的if-else直接暴露再客戶端,這種方法無疑是值得考慮的

image-20211202183415178

列舉策略方式

或許在看完【簡單工廠+策略模式】之後小夥伴會有所疑問,這不就是把客戶端的判斷邏輯給轉移到工廠中而已,雖然對於客戶端來說,會更加的清爽,可是似乎沒有根本性的解決問題,工廠中把if-else換成了switch-case,新的策略該建立類還是要建立類,所以該膨脹還是得膨脹啊。。。

那沒辦法了,只能出絕招了,

使用列舉策略模式,相當於策略來說,列舉策略更像是他的改良升級版本,使用起來也更加的靈活,不需要建立大量的類來充當各個具體實現,也不需要滿螢幕的if-else或者switch-case,看起來就相當的誘人

列舉大家都使用過,常用來定義一些常量資訊,而列舉策略就是在列舉類裡邊加上抽象方法,讓每個常量都實現這些方法

例如,我在列舉裡邊定義這兩個方法

image-20211202233138877

那麼我的每一個列舉成員都必須實現這兩個方法

image-20211202233213003

給出完整的程式碼

客戶端呼叫情況

可以發現,原本各個實現類都不需要了,只需要在列舉中定義成員,即可達成原來的效果,而且在匹配對應的策略時,直接使用迴圈的方式,看起來非常的清爽

如果要新增新的策略,直接在列舉裡邊新增成員,實現對應的方法即可,而且將所有的策略統一管理起來,方便維護

唯一的缺點,可能就是策略越來越多的時候,這個列舉類也會越來越長,但是通過成員來管理,註釋寫好,也不會顯示很混亂

總結

所以,策略模式到底是什麼?

就是提供很多很多的演算法,讓客戶端可以動態的選擇使用哪一個,其實在MVC中,View通過url把請求給到Controller處理,就是一種典型的策略選擇,View可以隨時更換url,繫結給另外一個Controller處理

事務都有兩面性,所以針對策略模式的侷限,我們需要做額外的工作,把不好的影響降到我們能接受的度

好啦,本期文章就到這裡了,限於本人水平的問題,如果有說得不對的地方,歡迎指出!我們下期再見!

相關文章