狀態變換 | Go語言設計模式實戰

嗯,個人代碼沒有 else系列,一個設計模式業務真實使用的golang系列。

個人代碼沒有else系列.jpg

前言

本系列主要分享,如何在咱們的真實業務場景中使用設計模式。git

本系列文章主要採用以下結構:github

  • 什麼是「XX設計模式」?
  • 什麼真實業務場景可使用「XX設計模式」?
  • 怎麼用「XX設計模式」?

本文主要介紹「狀態模式」如何在真實業務場景中使用。golang

「狀態模式」比較簡單,就是算法的選取取決於於本身的內部狀態。相較於「策略模式」算法的選取由用戶決策變成內部狀態決策,「策略模式」是用戶(客戶端)選擇具體的算法,「狀態模式」只是經過內部不一樣的狀態選擇具體的算法。算法

什麼是「狀態模式」?

不一樣的算法按照統一的標準封裝,根據不一樣的 內部狀態,決策使用何種算法

「狀態模式」和「策略模式」的區別

  • 策略模式:依靠客戶決策
  • 狀態模式:依靠內部狀態決策

什麼真實業務場景能夠用「狀態模式」?

具體算法的選取是由內部狀態決定的
  • 首先,內部存在多種狀態
  • 其次,不一樣的狀態的業務邏輯各不相同
咱們有哪些真實業務場景能夠用「狀態模式」呢?

好比,發送短信接口、限流等等。segmentfault

  • 短信接口設計模式

    • 服務內部根據最優算法,實時推舉出最優的短信服務商,並修改使用何種短信服務商的狀態
  • 限流ide

    • 服務內部根據當前的實時流量,選擇不一樣的限流算法,並修改使用何種限流算法的狀態

怎麼用「狀態模式」?

關於怎麼用,徹底能夠生搬硬套我總結的使用設計模式的四個步驟:函數

  • 業務梳理
  • 業務流程圖
  • 代碼建模
  • 代碼demo

業務梳理

先來看看一個短信驗證碼登陸的界面。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圖:

代碼demo

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. 本系列的一些設計模式的概念可能和原概念存在差別,由於會結合實際使用,取其精華,適當改變,靈活使用。

文章列表

個人代碼沒有else系列 更多文章 點擊此處查看

3911642037-d2bb08d8702e7c91_articlex.jpg

相關文章
相關標籤/搜索