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































