嗯,個人代碼沒有
else
系列,一個設計模式業務真實使用的golang系列。
本系列主要分享,如何在咱們的真實業務場景中使用設計模式。node
本系列文章主要採用以下結構:git
本文主要介紹「模板模式」如何在真實業務場景中使用。github
抽象類裏定義好算法的執行步驟和具體算法,以及可能發生變化的算法定義爲抽象方法。不一樣的子類繼承該抽象類,並實現父類的抽象方法。golang
模板模式的優點:算法
知足以下要求的全部場景:設計模式
算法執行的步驟是穩定 不變的,可是具體的某些算法可能存在 變化的場景。
怎麼理解,舉個例子:好比說你煮個面,必然須要先燒水,水燒開以後再放面進去
,以上的流程咱們稱之爲煮麪過程
。可知:這個煮麪過程
的步驟是穩定不變的,可是在不一樣的環境燒水的方式可能不盡相同,也許有的人用自然氣燒水、有的人用電磁爐燒水、有的人用柴火燒水,等等。咱們能夠獲得如下結論:函數
煮麪過程
的步驟是穩定不變的煮麪過程
的燒水方式是可變的咱們有哪些真實業務場景能夠用「模板模式」呢?
好比抽獎系統的抽獎接口,爲何:工具
關於怎麼用,徹底能夠生搬硬套我總結的使用設計模式的四個步驟:ui
我經過歷史上接觸過的各類抽獎場景(紅包雨、糖果雨、打地鼠、大轉盤(九宮格)、考眼力、答題闖關、遊戲闖關、支付刮刮樂、積分刮刮樂等等),按照真實業務需求梳理了如下抽獎業務抽獎接口的大體文本流程。spa
瞭解具體業務請點擊《通用抽獎工具之需求分析 | 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圖:
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代碼地址:https://github.com/TIGERB/eas...
合成複用
特性實現)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代碼地址:https://github.com/TIGERB/eas...
最後總結下,「模板模式」抽象過程的核心是把握不變與變:
Run
方法裏的抽獎步驟 -> 被繼承複用
變:不一樣場景下 -> 被具體實現
checkParams
參數校驗邏輯getPrizesByNode
獲取該節點獎品的邏輯特別說明: 1. 個人代碼沒有`else`,只是一個在代碼合理設計的狀況下天然而然無限接近或者達到的結果,並非一個硬性的目標,務必較真。 2. 本系列的一些設計模式的概念可能和原概念存在差別,由於會結合實際使用,取其精華,適當改變,靈活使用。
個人代碼沒有else系列 更多文章 點擊此處查看