嗯,個人代碼沒有
else
系列,一個設計模式業務真實使用的golang系列。git
本系列主要分享,如何在咱們的真實業務場景中使用設計模式。github
本系列文章主要採用以下結構:golang
本文主要介紹「狀態模式」如何在真實業務場景中使用。算法
「狀態模式」比較簡單,就是算法的選取取決於於本身的內部狀態。相較於「策略模式」算法的選取由用戶決策變成內部狀態決策,「策略模式」是用戶(客戶端)選擇具體的算法,「狀態模式」只是經過內部不一樣的狀態選擇具體的算法。設計模式
不一樣的算法按照統一的標準封裝,根據不一樣的內部狀態,決策使用何種算法bash
具體算法的選取是由內部狀態決定的ide
咱們有哪些真實業務場景能夠用「狀態模式」呢?函數
好比,發送短信接口、限流等等。ui
關於怎麼用,徹底能夠生搬硬套我總結的使用設計模式的四個步驟:阿里雲
先來看看一個短信驗證碼登陸的界面。
能夠獲得:
咱們經過梳理的文本業務流程獲得了以下的業務流程圖:
「狀態模式」的核心是:
SmsServiceInterface
StateManager
僞代碼以下:
// 定義一個短信服務接口
- 接口`SmsServiceInterface`
+ 抽象方法`Send(ctx *Context) error`發送短信的抽象方法
// 定義具體的短信服務實體類 實現接口`SmsServiceInterface`
- 實體類`ServiceProviderAliyun`
+ 成員方法`Send(ctx *Context) error`具體的發送短信邏輯
- 實體類`ServiceProviderTencent`
+ 成員方法`Send(ctx *Context) error`具體的發送短信邏輯
- 實體類`ServiceProviderYunpian`
+ 成員方法`Send(ctx *Context) error`具體的發送短信邏輯
// 定義狀態管理實體類`StateManager`
- 成員屬性
+ `currentProviderType ProviderType`當前使用的服務提供商類型
+ `currentProvider SmsServiceInterface`當前使用的服務提供商實例
+ `setStateDuration time.Duration`更新狀態時間間隔
- 成員方法
+ `initState(duration time.Duration)`初始化狀態
+ `setState(t time.Time)`設置狀態
複製代碼
同時獲得了咱們的UML圖:
package main
//------------------------------------------------------------
//個人代碼沒有`else`系列
//狀態模式
//@auhtor TIGERB<https://github.com/TIGERB>
//------------------------------------------------------------
import (
"fmt"
"math/rand"
"runtime"
"time"
)
// Context 上下文
type Context struct {
Tel string // 手機號
Text string // 短信內容
TemplateID string // 短信模板ID
}
// SmsServiceInterface 短信服務接口
type SmsServiceInterface interface {
Send(ctx *Context) error
}
// ServiceProviderAliyun 阿里雲
type ServiceProviderAliyun struct {
}
// Send Send
func (s *ServiceProviderAliyun) Send(ctx *Context) error {
fmt.Println(runFuncName(), "【阿里雲】短信發送成功,手機號:"+ctx.Tel)
return nil
}
// ServiceProviderTencent 騰訊雲
type ServiceProviderTencent struct {
}
// Send Send
func (s *ServiceProviderTencent) Send(ctx *Context) error {
fmt.Println(runFuncName(), "【騰訊雲】短信發送成功,手機號:"+ctx.Tel)
return nil
}
// ServiceProviderYunpian 雲片
type ServiceProviderYunpian struct {
}
// Send Send
func (s *ServiceProviderYunpian) Send(ctx *Context) error {
fmt.Println(runFuncName(), "【雲片】短信發送成功,手機號:"+ctx.Tel)
return nil
}
// 獲取正在運行的函數名
func runFuncName() string {
pc := make([]uintptr, 1)
runtime.Callers(2, pc)
f := runtime.FuncForPC(pc[0])
return f.Name()
}
// ProviderType 短信服務提供商類型
type ProviderType string
const (
// ProviderTypeAliyun 阿里雲
ProviderTypeAliyun ProviderType = "aliyun"
// ProviderTypeTencent 騰訊雲
ProviderTypeTencent ProviderType = "tencent"
// ProviderTypeYunpian 雲片
ProviderTypeYunpian ProviderType = "yunpian"
)
var (
// stateManagerInstance 當前使用的服務提供商實例
// 默認aliyun
stateManagerInstance *StateManager
)
// StateManager 狀態管理
type StateManager struct {
// CurrentProviderType 當前使用的服務提供商類型
// 默認aliyun
currentProviderType ProviderType
// CurrentProvider 當前使用的服務提供商實例
// 默認aliyun
currentProvider SmsServiceInterface
// 更新狀態時間間隔
setStateDuration time.Duration
}
// initState 初始化狀態
func (m *StateManager) initState(duration time.Duration) {
// 初始化
m.setStateDuration = duration
m.setState(time.Now())
// 定時器更新狀態
go func() {
for {
// 每一段時間後根據回調的發送成功率 計算獲得當前應該使用的 廠商
select {
case t := <-time.NewTicker(m.setStateDuration).C:
m.setState(t)
}
}
}()
}
// setState 設置狀態
// 根據短信雲商回調的短信發送成功率 獲得下階段發送短信使用哪一個廠商的服務
func (m *StateManager) setState(t time.Time) {
// 這裏用隨機模擬
ProviderTypeArray := [3]ProviderType{
ProviderTypeAliyun,
ProviderTypeTencent,
ProviderTypeYunpian,
}
m.currentProviderType = ProviderTypeArray[rand.Intn(len(ProviderTypeArray))]
switch m.currentProviderType {
case ProviderTypeAliyun:
m.currentProvider = &ServiceProviderAliyun{}
case ProviderTypeTencent:
m.currentProvider = &ServiceProviderTencent{}
case ProviderTypeYunpian:
m.currentProvider = &ServiceProviderYunpian{}
default:
panic("無效的短信服務商")
}
fmt.Printf("時間:%s| 變動短信發送廠商爲: %s \n", t.Format("2006-01-02 15:04:05"), m.currentProviderType)
}
// getState 獲取當前狀態
func (m *StateManager) getState() SmsServiceInterface {
return m.currentProvider
}
// GetState 獲取當前狀態
func GetState() SmsServiceInterface {
return stateManagerInstance.getState()
}
func main() {
// 初始化狀態管理
stateManagerInstance = &StateManager{}
stateManagerInstance.initState(300 * time.Millisecond)
// 模擬發送短信的接口
sendSms := func() {
// 發送短信
GetState().Send(&Context{
Tel: "+8613666666666",
Text: "3232",
TemplateID: "TYSHK_01",
})
}
// 模擬用戶調用發送短信的接口
sendSms()
time.Sleep(1 * time.Second)
sendSms()
time.Sleep(1 * time.Second)
sendSms()
time.Sleep(1 * time.Second)
sendSms()
time.Sleep(1 * time.Second)
sendSms()
}
複製代碼
代碼運行結果:
[Running] go run "./easy-tips/go/src/patterns/state/state.go"
時間:2020-05-30 18:02:37| 變動短信發送廠商爲: yunpian
main.(*ServiceProviderYunpian).Send 【雲片】短信發送成功,手機號:+8613666666666
時間:2020-05-30 18:02:37| 變動短信發送廠商爲: aliyun
時間:2020-05-30 18:02:38| 變動短信發送廠商爲: yunpian
時間:2020-05-30 18:02:38| 變動短信發送廠商爲: yunpian
main.(*ServiceProviderYunpian).Send 【雲片】短信發送成功,手機號:+8613666666666
時間:2020-05-30 18:02:38| 變動短信發送廠商爲: tencent
時間:2020-05-30 18:02:39| 變動短信發送廠商爲: aliyun
時間:2020-05-30 18:02:39| 變動短信發送廠商爲: tencent
main.(*ServiceProviderTencent).Send 【騰訊雲】短信發送成功,手機號:+8613666666666
時間:2020-05-30 18:02:39| 變動短信發送廠商爲: yunpian
時間:2020-05-30 18:02:40| 變動短信發送廠商爲: tencent
時間:2020-05-30 18:02:40| 變動短信發送廠商爲: aliyun
main.(*ServiceProviderAliyun).Send 【阿里雲】短信發送成功,手機號:+8613666666666
時間:2020-05-30 18:02:40| 變動短信發送廠商爲: yunpian
時間:2020-05-30 18:02:40| 變動短信發送廠商爲: tencent
時間:2020-05-30 18:02:41| 變動短信發送廠商爲: aliyun
時間:2020-05-30 18:02:41| 變動短信發送廠商爲: yunpian
main.(*ServiceProviderYunpian).Send 【雲片】短信發送成功,手機號:+8613666666666
複製代碼
最後總結下,「狀態模式」抽象過程的核心是:
interface
特別說明:
1. 個人代碼沒有`else`,只是一個在代碼合理設計的狀況下天然而然無限接近或者達到的結果,並非一個硬性的目標,務必較真。
2. 本系列的一些設計模式的概念可能和原概念存在差別,由於會結合實際使用,取其精華,適當改變,靈活使用。
複製代碼