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