Go設計模式實戰--用職責鏈實現購物車與商品優惠的解耦
上一節「Go項目實戰-購物車功能的核心接口開發」中我們完成了購物車模塊的基本功能的開發,所有購物車功能中存在變數的就是購物車的賬單結算功能,也是未來經常可能會遇到需求改動的功能?各種促銷活動相關的需求都會要求涵蓋在這個功能中。
我們理解的購物車結算功能,一開始可能是是下面這個清純版的
圖片
但實際上公司產品要求的購物車結算功能是下面這張圖這樣,不光能算出商品總價,還要能綜合考慮用戶是不是VIP,有沒有優惠券、夠不夠參加滿減活動。
圖片
而且促銷活動的玩法可能遠不止這么多未來還有可能增加新的玩法。每次新增一個玩法我們的結算模塊代碼大概率又需要增加一個代碼分支,做相應的查詢和判斷等等操作來滿足新的促銷玩法。
那么有沒有什么設計模式能讓我們稍微緩解一下代碼不停添加條件分支來適應新需求呢?我這么說了,當然是有了,這就是職責鏈模式,也有的資料叫責任鏈模式。本節我們把購物車的賬單結算功能使用職責鏈進行改造,讓它支持優惠券、滿減活動、VIP折扣能促銷功能的應用。
不過首先我們需要聊一下什么是責任鏈模式。
職責鏈模式
職責鏈在很多流行框架里都有被用到,像中間件、攔截器等框架組件都是應用的這種設計模式,這兩個組價大家應該用的比較多。但其實在一些核心的業務中,應用職責鏈模式也能夠讓我們“相對無痛地”擴展業務流程的步驟。
注意我上面的用詞“相對無痛”,意思是我們不用不停地在原有步驟中增加if else 判斷,不必修改原有已經開發好,經過測試的流程。但還是有些代碼開發成本的,也會增加代碼的復雜度,真正做到“無痛”,那你的轉個行當甲方,最好是當老板才行。。。
職責鏈的實現步驟分析
我們通過上面流程擴展的痛點可以想到,流程中每個步驟都應由一個處理對象來完成邏輯抽象、所有處理對象都應該提供統一的處理自身邏輯的方法,其次還應該維護指向下一個處理對象的引用,當前步驟自己邏輯處理完后,就調用下一個對象的處理方法,把請求交給后面的對象進行處理,依次遞進直到流程結束。
如果抽象成 UML 類圖表示的話,差不多就是下面這個樣子。
圖片
圖片
了解完職責鏈模式從接口和類型設計上應該怎么實現后,我們進入代碼實現環節。
用職責鏈模式改造購物車結算功能
以我們項目的購物車結算功能在加入促銷相關的需求后,其流程如下:
查購物信息--> 查看的可用優惠券--> 查滿減活動-->查VIP資格和折扣-->生成賬單信息。
開頭和結尾的步驟固定,不管什么類型的用戶都會有這個流程,中間的促銷流程則是變數,我們的目標是利用職責鏈模式,實現這個流程中的每個步驟,且相互間不耦合,還支持向流程中增加步驟。
改造結算功能的第一步,是先確定新的結算功能返回的響應,我們把購物車功能結算的響應對象改造為以下結構,增加了促銷相關的信息,這樣客戶端拿到后也能展示給用戶,讓用戶知道自己用了哪些優惠。
新的購物車結算功能的響應對象如下。
type CheckedCartItemBillV2 struct {
Items []*CartItem `json:"items"`
BillDetail struct {
Coupon struct { // 可用的優惠券
CouponId int64`json:"coupon_id"`
CouponName string`json:"coupon_name"`
DiscountMoney int `json:"discount_money"`
} `json:"coupon"`
Discount struct { // 可用的滿減活動券
DiscountId int64`json:"discount_id"`
DiscountName string`json:"discount_name"`
DiscountMoney int `json:"discount_money"`
} `json:"discount"`
VipDiscountMoney int`json:"vip_discount_money"` // VIP減免的金額
OriginalTotalPrice int`json:"original_total_price"`// 減免、優惠前的總金額
TotalPrice int`json:"total_price"` // 實際要支付的總金額
} `json:"bill_detail"`
}
我們服務層使用的領域對象也需要做調整。
type CartBillInfo struct {
Coupon struct { // 可用的優惠券
CouponId int64
CouponName string
DiscountMoney int
Threshold int// 使用門檻, 比如滿1000 可用
}
Discount struct { // 可用的滿減活動券
DiscountId int64
DiscountName string
DiscountMoney int
Threshold int// 使用門檻, 比如滿1000 可用
}
VipDiscountMoney int// VIP減免的金額
OriginalTotalPrice int// 減免、優惠前的總金額
TotalPrice int// 實際要支付的總金額
}
接下來我們先來實現職責鏈模式里的公共部分—即模式的接口和抽象類,在logic/domainservice中新建cart_bill_checker.go 新增以下Interface。
type cartBillCheckHandler interface {
RunChecker(*CartBillChecker) error
SetNext(cartBillCheckHandler) cartBillCheckHandler
Check(*CartBillChecker) error
}
接下來定義 cartCommonChecker ,它充當抽象類型,實現公共方法。
type cartCommonChecker struct {
nextHandler cartBillCheckHandler
}
func (n *cartCommonChecker) SetNext(handler cartBillCheckHandler) cartBillCheckHandler {
n.nextHandler = handler
return handler
}
func (n *cartCommonChecker) RunChecker(billChecker *CartBillChecker) (err error) {
if n.nextHandler != nil {
if err = n.nextHandler.Check(billChecker); err != nil {
err = errcode.Wrap("CartBillCheckerError", err)
return
}
return n.nextHandler.RunChecker(billChecker)
}
return
}
抽象方法 Check 則留給像下面優惠券處理類 couponChecker 這樣的匿名嵌套了 cartCommonChecker 的具體處理類去實現。
我們來實現couponChecker、discountChecker、vipChecker 三個具體的流程步驟的處理類,他們各自要處理的邏輯都封裝在自己實現的Check方法中。