嗯,我的程式碼沒有
else
系列,一個設計模式業務真實使用的golang系列。
前言
本系列主要分享,如何在我們的真實業務場景中使用設計模式。
本系列文章主要採用如下結構:
- 什麼是「XX設計模式」?
- 什麼真實業務場景可以使用「XX設計模式」?
- 怎麼用「XX設計模式」?
本文主要介紹「模板模式」如何在真實業務場景中使用。
什麼是「模板模式」?
抽象類裡定義好演算法的執行步驟和具體演算法,以及可能發生變化的演算法定義為抽象方法。不同的子類繼承該抽象類,並實現父類的抽象方法。
模板模式的優勢:
- 不變的演算法被繼承複用:不變的部分高度封裝、複用。
- 變化的演算法子類繼承並具體實現:變化的部分子類只需要具體實現抽象的部分即可,方便擴充套件,且可無限擴充套件。
什麼真實業務場景可以用「模板模式」?
滿足如下要求的所有場景:
演算法執行的步驟是穩定不變的,但是具體的某些演算法可能存在變化的場景。
怎麼理解,舉個例子:比如說你煮個面,必然需要先燒水,水燒開之後再放面進去
,以上的流程我們稱之為煮麵過程
。可知:這個煮麵過程
的步驟是穩定不變的,但是在不同的環境燒水的方式可能不盡相同,也許有的人用天然氣燒水、有的人用電磁爐燒水、有的人用柴火燒水,等等。我們可以得到以下結論:
煮麵過程
的步驟是穩定不變的煮麵過程
的燒水方式是可變的
我們有哪些真實業務場景可以用「模板模式」呢?
比如抽獎系統的抽獎介面,為什麼:
- 抽獎的步驟是穩定不變的 -> 不變的演算法執行步驟
- 不同抽獎型別活動在某些邏輯處理方式可能不同 -> 變的某些演算法
怎麼用「模板模式」?
關於怎麼用,完全可以生搬硬套我總結的使用設計模式的四個步驟:
- 業務梳理
- 業務流程圖
- 程式碼建模
- 程式碼demo
業務梳理
我通過歷史上接觸過的各種抽獎場景(紅包雨、糖果雨、打地鼠、大轉盤(九宮格)、考眼力、答題闖關、遊戲闖關、支付刮刮樂、積分刮刮樂等等),按照真實業務需求梳理了以下抽獎業務抽獎介面的大致文字流程。
瞭解具體業務請點選《通用抽獎工具之需求分析 | SkrShop》
主步驟 | 主邏輯 | 抽獎型別 | 子步驟 | 子邏輯 |
---|---|---|---|---|
1 | 校驗活動編號(serial_no)是否存在、並獲取活動資訊 | - | - | - |
2 | 校驗活動、場次是否正在進行 | - | - | - |
3 | 其他引數校驗(不同活動型別實現不同) | - | - | - |
4 | 活動抽獎次數校驗(同時扣減) | - | - | - |
5 | 活動是否需要消費積分 | - | - | - |
6 | 場次抽獎次數校驗(同時扣減) | - | - | - |
7 | 獲取場次獎品資訊 | - | - | - |
8 | 獲取node獎品資訊(不同活動型別實現不同) | 按時間抽獎型別 | 1 | do nothing(抽取該場次的獎品即可,無需其他邏輯) |
8 | 按抽獎次數抽獎型別 | 1 | 判斷是該使用者第幾次抽獎 | |
8 | 2 | 獲取對應node的獎品資訊 | ||
8 | 3 | 複寫原所有獎品資訊(抽取該node節點的獎品) | ||
8 | 按數額範圍區間抽獎 | 1 | 判斷屬於哪個數額區間 | |
8 | 2 | 獲取對應node的獎品資訊 | ||
8 | 3 | 複寫原所有獎品資訊(抽取該node節點的獎品) | ||
9 | 抽獎 | - | - | - |
10 | 獎品數量判斷 | - | - | - |
11 | 組裝獎品資訊 | - | - | - |
注:流程不一定完全準確
結論:
主邏輯
是穩定不變的其他引數校驗
和獲取node獎品資訊
的演算法是可變的
業務流程圖
我們通過梳理的文字業務流程得到了如下的業務流程圖:
程式碼建模
通過上面的分析我們可以得到:
一個抽象類
- 具體共有方法`Run`,裡面定義了演算法的執行步驟
- 具體私有方法,不會發生變化的具體方法
- 抽象方法,會發生變化的方法
子類一(按時間抽獎型別)
- 繼承抽象類父類
- 實現抽象方法
子類二(按抽獎次數抽獎型別)
- 繼承抽象類父類
- 實現抽象方法
子類三(按數額範圍區間抽獎)
- 繼承抽象類父類
- 實現抽象方法
複製程式碼
但是golang裡面沒有繼承的概念,我們就把對抽象類裡抽象方法的依賴轉化成對介面interface
裡抽象方法的依賴,同時也可以利用合成複用
的方式“繼承”模板:
抽象行為的介面`BehaviorInterface`(包含如下需要實現的方法)
- 其他引數校驗的方法`checkParams`
- 獲取node獎品資訊的方法`getPrizesByNode`
抽獎結構體類
- 具體共有方法`Run`,裡面定義了演算法的執行步驟
- 具體私有方法`checkParams` 裡面的邏輯實際依賴的介面BehaviorInterface.checkParams(ctx)的抽象方法
- 具體私有方法`getPrizesByNode` 裡面的邏輯實際依賴的介面BehaviorInterface.getPrizesByNode(ctx)的抽象方法
- 其他具體私有方法,不會發生變化的具體方法
實現`BehaviorInterface`的結構體一(按時間抽獎型別)
- 實現介面方法
實現`BehaviorInterface`的結構體二(按抽獎次數抽獎型別)
- 實現介面方法
實現`BehaviorInterface`的結構體三(按數額範圍區間抽獎)
- 實現介面方法
複製程式碼
同時得到了我們的UML圖:
程式碼demo
package main
import (
"fmt"
"runtime"
)
//------------------------------------------------------------
//我的程式碼沒有`else`系列
//模板模式
//@auhtor TIGERB<https://github.com/TIGERB>
//------------------------------------------------------------
const (
// ConstActTypeTime 按時間抽獎型別
ConstActTypeTime int32 = 1
// ConstActTypeTimes 按抽獎次數抽獎
ConstActTypeTimes int32 = 2
// ConstActTypeAmount 按數額範圍區間抽獎
ConstActTypeAmount int32 = 3
)
// Context 上下文
type Context struct {
ActInfo *ActInfo
}
// ActInfo 上下文
type ActInfo struct {
// 活動抽獎型別1: 按時間抽獎 2: 按抽獎次數抽獎 3:按數額範圍區間抽獎
ActivityType int32
// 其他欄位略
}
// BehaviorInterface 不同抽獎型別的行為差異的抽象介面
type BehaviorInterface interface {
// 其他引數校驗(不同活動型別實現不同)
checkParams(ctx *Context) error
// 獲取node獎品資訊(不同活動型別實現不同)
getPrizesByNode(ctx *Context) error
}
// TimeDraw 具體抽獎行為
// 按時間抽獎型別 比如紅包雨
type TimeDraw struct{}
// checkParams 其他引數校驗(不同活動型別實現不同)
func (draw TimeDraw) checkParams(ctx *Context) (err error) {
fmt.Println(runFuncName(), "按時間抽獎型別:特殊引數校驗...")
return
}
// getPrizesByNode 獲取node獎品資訊(不同活動型別實現不同)
func (draw TimeDraw) getPrizesByNode(ctx *Context) (err error) {
fmt.Println(runFuncName(), "do nothing(抽取該場次的獎品即可,無需其他邏輯)...")
return
}
// TimesDraw 具體抽獎行為
// 按抽獎次數抽獎型別 比如答題闖關
type TimesDraw struct{}
// checkParams 其他引數校驗(不同活動型別實現不同)
func (draw TimesDraw) checkParams(ctx *Context) (err error) {
fmt.Println(runFuncName(), "按抽獎次數抽獎型別:特殊引數校驗...")
return
}
// getPrizesByNode 獲取node獎品資訊(不同活動型別實現不同)
func (draw TimesDraw) getPrizesByNode(ctx *Context) (err error) {
fmt.Println(runFuncName(), "1. 判斷是該使用者第幾次抽獎...")
fmt.Println(runFuncName(), "2. 獲取對應node的獎品資訊...")
fmt.Println(runFuncName(), "3. 複寫原所有獎品資訊(抽取該node節點的獎品)...")
return
}
// AmountDraw 具體抽獎行為
// 按數額範圍區間抽獎 比如訂單金額刮獎
type AmountDraw struct{}
// checkParams 其他引數校驗(不同活動型別實現不同)
func (draw *AmountDraw) checkParams(ctx *Context) (err error) {
fmt.Println(runFuncName(), "按數額範圍區間抽獎:特殊引數校驗...")
return
}
// getPrizesByNode 獲取node獎品資訊(不同活動型別實現不同)
func (draw *AmountDraw) getPrizesByNode(ctx *Context) (err error) {
fmt.Println(runFuncName(), "1. 判斷屬於哪個數額區間...")
fmt.Println(runFuncName(), "2. 獲取對應node的獎品資訊...")
fmt.Println(runFuncName(), "3. 複寫原所有獎品資訊(抽取該node節點的獎品)...")
return
}
// Lottery 抽獎模板
type Lottery struct {
// 不同抽獎型別的抽象行為
concreteBehavior BehaviorInterface
}
// Run 抽獎演算法
// 穩定不變的演算法步驟
func (lottery *Lottery) Run(ctx *Context) (err error) {
// 具體方法:校驗活動編號(serial_no)是否存在、並獲取活動資訊
if err = lottery.checkSerialNo(ctx); err != nil {
return err
}
// 具體方法:校驗活動、場次是否正在進行
if err = lottery.checkStatus(ctx); err != nil {
return err
}
// ”抽象方法“:其他引數校驗
if err = lottery.checkParams(ctx); err != nil {
return err
}
// 具體方法:活動抽獎次數校驗(同時扣減)
if err = lottery.checkTimesByAct(ctx); err != nil {
return err
}
// 具體方法:活動是否需要消費積分
if err = lottery.consumePointsByAct(ctx); err != nil {
return err
}
// 具體方法:場次抽獎次數校驗(同時扣減)
if err = lottery.checkTimesBySession(ctx); err != nil {
return err
}
// 具體方法:獲取場次獎品資訊
if err = lottery.getPrizesBySession(ctx); err != nil {
return err
}
// ”抽象方法“:獲取node獎品資訊
if err = lottery.getPrizesByNode(ctx); err != nil {
return err
}
// 具體方法:抽獎
if err = lottery.drawPrizes(ctx); err != nil {
return err
}
// 具體方法:獎品數量判斷
if err = lottery.checkPrizesStock(ctx); err != nil {
return err
}
// 具體方法:組裝獎品資訊
if err = lottery.packagePrizeInfo(ctx); err != nil {
return err
}
return
}
// checkSerialNo 校驗活動編號(serial_no)是否存在
func (lottery *Lottery) checkSerialNo(ctx *Context) (err error) {
fmt.Println(runFuncName(), "校驗活動編號(serial_no)是否存在、並獲取活動資訊...")
// 獲取活動資訊虛擬碼
ctx.ActInfo = &ActInfo{
// 假設當前的活動型別為按抽獎次數抽獎
ActivityType: ConstActTypeTimes,
}
// 獲取當前抽獎型別的具體行為
switch ctx.ActInfo.ActivityType {
case 1:
// 按時間抽獎
lottery.concreteBehavior = &TimeDraw{}
case 2:
// 按抽獎次數抽獎
lottery.concreteBehavior = &TimesDraw{}
case 3:
// 按數額範圍區間抽獎
lottery.concreteBehavior = &AmountDraw{}
default:
return fmt.Errorf("不存在的活動型別")
}
return
}
// checkStatus 校驗活動、場次是否正在進行
func (lottery *Lottery) checkStatus(ctx *Context) (err error) {
fmt.Println(runFuncName(), "校驗活動、場次是否正在進行...")
return
}
// checkParams 其他引數校驗(不同活動型別實現不同)
// 不同場景變化的演算法 轉化為依賴抽象
func (lottery *Lottery) checkParams(ctx *Context) (err error) {
// 實際依賴的介面的抽象方法
return lottery.concreteBehavior.checkParams(ctx)
}
// checkTimesByAct 活動抽獎次數校驗
func (lottery *Lottery) checkTimesByAct(ctx *Context) (err error) {
fmt.Println(runFuncName(), "活動抽獎次數校驗...")
return
}
// consumePointsByAct 活動是否需要消費積分
func (lottery *Lottery) consumePointsByAct(ctx *Context) (err error) {
fmt.Println(runFuncName(), "活動是否需要消費積分...")
return
}
// checkTimesBySession 活動抽獎次數校驗
func (lottery *Lottery) checkTimesBySession(ctx *Context) (err error) {
fmt.Println(runFuncName(), "活動抽獎次數校驗...")
return
}
// getPrizesBySession 獲取場次獎品資訊
func (lottery *Lottery) getPrizesBySession(ctx *Context) (err error) {
fmt.Println(runFuncName(), "獲取場次獎品資訊...")
return
}
// getPrizesByNode 獲取node獎品資訊(不同活動型別實現不同)
// 不同場景變化的演算法 轉化為依賴抽象
func (lottery *Lottery) getPrizesByNode(ctx *Context) (err error) {
// 實際依賴的介面的抽象方法
return lottery.concreteBehavior.getPrizesByNode(ctx)
}
// drawPrizes 抽獎
func (lottery *Lottery) drawPrizes(ctx *Context) (err error) {
fmt.Println(runFuncName(), "抽獎...")
return
}
// checkPrizesStock 獎品數量判斷
func (lottery *Lottery) checkPrizesStock(ctx *Context) (err error) {
fmt.Println(runFuncName(), "獎品數量判斷...")
return
}
// packagePrizeInfo 組裝獎品資訊
func (lottery *Lottery) packagePrizeInfo(ctx *Context) (err error) {
fmt.Println(runFuncName(), "組裝獎品資訊...")
return
}
func main() {
(&Lottery{}).Run(&Context{})
}
// 獲取正在執行的函式名
func runFuncName() string {
pc := make([]uintptr, 1)
runtime.Callers(2, pc)
f := runtime.FuncForPC(pc[0])
return f.Name()
}
複製程式碼
以下是程式碼執行結果:
[Running] go run ".../easy-tips/go/src/patterns/template/template.go"
main.(*Lottery).checkSerialNo 校驗活動編號(serial_no)是否存在、並獲取活動資訊...
main.(*Lottery).checkStatus 校驗活動、場次是否正在進行...
main.TimesDraw.checkParams 按抽獎次數抽獎型別:特殊引數校驗...
main.(*Lottery).checkTimesByAct 活動抽獎次數校驗...
main.(*Lottery).consumePointsByAct 活動是否需要消費積分...
main.(*Lottery).checkTimesBySession 活動抽獎次數校驗...
main.(*Lottery).getPrizesBySession 獲取場次獎品資訊...
main.TimesDraw.getPrizesByNode 1. 判斷是該使用者第幾次抽獎...
main.TimesDraw.getPrizesByNode 2. 獲取對應node的獎品資訊...
main.TimesDraw.getPrizesByNode 3. 複寫原所有獎品資訊(抽取該node節點的獎品)...
main.(*Lottery).drawPrizes 抽獎...
main.(*Lottery).checkPrizesStock 獎品數量判斷...
main.(*Lottery).packagePrizeInfo 組裝獎品資訊...
複製程式碼
demo程式碼地址:github.com/TIGERB/easy…
程式碼demo2(利用golang的合成複用
特性實現)
package main
import (
"fmt"
"runtime"
)
//------------------------------------------------------------
//我的程式碼沒有`else`系列
//模板模式
//@auhtor TIGERB<https://github.com/TIGERB>
//------------------------------------------------------------
const (
// ConstActTypeTime 按時間抽獎型別
ConstActTypeTime int32 = 1
// ConstActTypeTimes 按抽獎次數抽獎
ConstActTypeTimes int32 = 2
// ConstActTypeAmount 按數額範圍區間抽獎
ConstActTypeAmount int32 = 3
)
// Context 上下文
type Context struct {
ActInfo *ActInfo
}
// ActInfo 上下文
type ActInfo struct {
// 活動抽獎型別1: 按時間抽獎 2: 按抽獎次數抽獎 3:按數額範圍區間抽獎
ActivityType int32
// 其他欄位略
}
// BehaviorInterface 不同抽獎型別的行為差異的抽象介面
type BehaviorInterface interface {
// 其他引數校驗(不同活動型別實現不同)
checkParams(ctx *Context) error
// 獲取node獎品資訊(不同活動型別實現不同)
getPrizesByNode(ctx *Context) error
}
// TimeDraw 具體抽獎行為
// 按時間抽獎型別 比如紅包雨
type TimeDraw struct {
// 合成複用模板
Lottery
}
// checkParams 其他引數校驗(不同活動型別實現不同)
func (draw TimeDraw) checkParams(ctx *Context) (err error) {
fmt.Println(runFuncName(), "按時間抽獎型別:特殊引數校驗...")
return
}
// getPrizesByNode 獲取node獎品資訊(不同活動型別實現不同)
func (draw TimeDraw) getPrizesByNode(ctx *Context) (err error) {
fmt.Println(runFuncName(), "do nothing(抽取該場次的獎品即可,無需其他邏輯)...")
return
}
// TimesDraw 具體抽獎行為
// 按抽獎次數抽獎型別 比如答題闖關
type TimesDraw struct {
// 合成複用模板
Lottery
}
// checkParams 其他引數校驗(不同活動型別實現不同)
func (draw TimesDraw) checkParams(ctx *Context) (err error) {
fmt.Println(runFuncName(), "按抽獎次數抽獎型別:特殊引數校驗...")
return
}
// getPrizesByNode 獲取node獎品資訊(不同活動型別實現不同)
func (draw TimesDraw) getPrizesByNode(ctx *Context) (err error) {
fmt.Println(runFuncName(), "1. 判斷是該使用者第幾次抽獎...")
fmt.Println(runFuncName(), "2. 獲取對應node的獎品資訊...")
fmt.Println(runFuncName(), "3. 複寫原所有獎品資訊(抽取該node節點的獎品)...")
return
}
// AmountDraw 具體抽獎行為
// 按數額範圍區間抽獎 比如訂單金額刮獎
type AmountDraw struct {
// 合成複用模板
Lottery
}
// checkParams 其他引數校驗(不同活動型別實現不同)
func (draw *AmountDraw) checkParams(ctx *Context) (err error) {
fmt.Println(runFuncName(), "按數額範圍區間抽獎:特殊引數校驗...")
return
}
// getPrizesByNode 獲取node獎品資訊(不同活動型別實現不同)
func (draw *AmountDraw) getPrizesByNode(ctx *Context) (err error) {
fmt.Println(runFuncName(), "1. 判斷屬於哪個數額區間...")
fmt.Println(runFuncName(), "2. 獲取對應node的獎品資訊...")
fmt.Println(runFuncName(), "3. 複寫原所有獎品資訊(抽取該node節點的獎品)...")
return
}
// Lottery 抽獎模板
type Lottery struct {
// 不同抽獎型別的抽象行為
ConcreteBehavior BehaviorInterface
}
// Run 抽獎演算法
// 穩定不變的演算法步驟
func (lottery *Lottery) Run(ctx *Context) (err error) {
// 具體方法:校驗活動編號(serial_no)是否存在、並獲取活動資訊
if err = lottery.checkSerialNo(ctx); err != nil {
return err
}
// 具體方法:校驗活動、場次是否正在進行
if err = lottery.checkStatus(ctx); err != nil {
return err
}
// ”抽象方法“:其他引數校驗
if err = lottery.checkParams(ctx); err != nil {
return err
}
// 具體方法:活動抽獎次數校驗(同時扣減)
if err = lottery.checkTimesByAct(ctx); err != nil {
return err
}
// 具體方法:活動是否需要消費積分
if err = lottery.consumePointsByAct(ctx); err != nil {
return err
}
// 具體方法:場次抽獎次數校驗(同時扣減)
if err = lottery.checkTimesBySession(ctx); err != nil {
return err
}
// 具體方法:獲取場次獎品資訊
if err = lottery.getPrizesBySession(ctx); err != nil {
return err
}
// ”抽象方法“:獲取node獎品資訊
if err = lottery.getPrizesByNode(ctx); err != nil {
return err
}
// 具體方法:抽獎
if err = lottery.drawPrizes(ctx); err != nil {
return err
}
// 具體方法:獎品數量判斷
if err = lottery.checkPrizesStock(ctx); err != nil {
return err
}
// 具體方法:組裝獎品資訊
if err = lottery.packagePrizeInfo(ctx); err != nil {
return err
}
return
}
// checkSerialNo 校驗活動編號(serial_no)是否存在
func (lottery *Lottery) checkSerialNo(ctx *Context) (err error) {
fmt.Println(runFuncName(), "校驗活動編號(serial_no)是否存在、並獲取活動資訊...")
return
}
// checkStatus 校驗活動、場次是否正在進行
func (lottery *Lottery) checkStatus(ctx *Context) (err error) {
fmt.Println(runFuncName(), "校驗活動、場次是否正在進行...")
return
}
// checkParams 其他引數校驗(不同活動型別實現不同)
// 不同場景變化的演算法 轉化為依賴抽象
func (lottery *Lottery) checkParams(ctx *Context) (err error) {
// 實際依賴的介面的抽象方法
return lottery.ConcreteBehavior.checkParams(ctx)
}
// checkTimesByAct 活動抽獎次數校驗
func (lottery *Lottery) checkTimesByAct(ctx *Context) (err error) {
fmt.Println(runFuncName(), "活動抽獎次數校驗...")
return
}
// consumePointsByAct 活動是否需要消費積分
func (lottery *Lottery) consumePointsByAct(ctx *Context) (err error) {
fmt.Println(runFuncName(), "活動是否需要消費積分...")
return
}
// checkTimesBySession 活動抽獎次數校驗
func (lottery *Lottery) checkTimesBySession(ctx *Context) (err error) {
fmt.Println(runFuncName(), "活動抽獎次數校驗...")
return
}
// getPrizesBySession 獲取場次獎品資訊
func (lottery *Lottery) getPrizesBySession(ctx *Context) (err error) {
fmt.Println(runFuncName(), "獲取場次獎品資訊...")
return
}
// getPrizesByNode 獲取node獎品資訊(不同活動型別實現不同)
// 不同場景變化的演算法 轉化為依賴抽象
func (lottery *Lottery) getPrizesByNode(ctx *Context) (err error) {
// 實際依賴的介面的抽象方法
return lottery.ConcreteBehavior.getPrizesByNode(ctx)
}
// drawPrizes 抽獎
func (lottery *Lottery) drawPrizes(ctx *Context) (err error) {
fmt.Println(runFuncName(), "抽獎...")
return
}
// checkPrizesStock 獎品數量判斷
func (lottery *Lottery) checkPrizesStock(ctx *Context) (err error) {
fmt.Println(runFuncName(), "獎品數量判斷...")
return
}
// packagePrizeInfo 組裝獎品資訊
func (lottery *Lottery) packagePrizeInfo(ctx *Context) (err error) {
fmt.Println(runFuncName(), "組裝獎品資訊...")
return
}
func main() {
ctx := &Context{
ActInfo: &ActInfo{
ActivityType: ConstActTypeAmount,
},
}
switch ctx.ActInfo.ActivityType {
case ConstActTypeTime: // 按時間抽獎型別
instance := &TimeDraw{}
instance.ConcreteBehavior = instance
instance.Run(ctx)
case ConstActTypeTimes: // 按抽獎次數抽獎
instance := &TimesDraw{}
instance.ConcreteBehavior = instance
instance.Run(ctx)
case ConstActTypeAmount: // 按數額範圍區間抽獎
instance := &AmountDraw{}
instance.ConcreteBehavior = instance
instance.Run(ctx)
default:
// 報錯
return
}
}
// 獲取正在執行的函式名
func runFuncName() string {
pc := make([]uintptr, 1)
runtime.Callers(2, pc)
f := runtime.FuncForPC(pc[0])
return f.Name()
}
複製程式碼
以下是程式碼執行結果:
[Running] go run ".../easy-tips/go/src/patterns/template/templateOther.go"
main.(*Lottery).checkSerialNo 校驗活動編號(serial_no)是否存在、並獲取活動資訊...
main.(*Lottery).checkStatus 校驗活動、場次是否正在進行...
main.(*AmountDraw).checkParams 按數額範圍區間抽獎:特殊引數校驗...
main.(*Lottery).checkTimesByAct 活動抽獎次數校驗...
main.(*Lottery).consumePointsByAct 活動是否需要消費積分...
main.(*Lottery).checkTimesBySession 活動抽獎次數校驗...
main.(*Lottery).getPrizesBySession 獲取場次獎品資訊...
main.(*AmountDraw).getPrizesByNode 1. 判斷屬於哪個數額區間...
main.(*AmountDraw).getPrizesByNode 2. 獲取對應node的獎品資訊...
main.(*AmountDraw).getPrizesByNode 3. 複寫原所有獎品資訊(抽取該node節點的獎品)...
main.(*Lottery).drawPrizes 抽獎...
main.(*Lottery).checkPrizesStock 獎品數量判斷...
main.(*Lottery).packagePrizeInfo 組裝獎品資訊...
複製程式碼
demo2程式碼地址:github.com/TIGERB/easy…
結語
最後總結下,「模板模式」抽象過程的核心是把握不變與變:
- 不變:
Run
方法裡的抽獎步驟 ->被繼承複用
- 變:不同場景下 ->
被具體實現
checkParams
引數校驗邏輯getPrizesByNode
獲取該節點獎品的邏輯
特別說明:
1. 我的程式碼沒有`else`,只是一個在程式碼合理設計的情況下自然而然無限接近或者達到的結果,並不是一個硬性的目標,務必較真。
2. 本系列的一些設計模式的概念可能和原概念存在差異,因為會結合實際使用,取其精華,適當改變,靈活使用。
複製程式碼