状态变换 | Go设计模式实战
2020-05-31
嗯,Go设计模式实战系列,一个设计模式业务真实使用的golang系列。
前言
本系列主要分享,如何在我们的真实业务场景中使用设计模式。
本系列文章主要采用如下结构:
- 什么是「XX设计模式」?
- 什么真实业务场景可以使用「XX设计模式」?
- 怎么用「XX设计模式」?
本文主要介绍「状态模式」如何在真实业务场景中使用。
「状态模式」比较简单,就是算法的选取取决于于自己的内部状态。相较于「策略模式」算法的选取由用户决策变成内部状态决策,「策略模式」是用户(客户端)选择具体的算法,「状态模式」只是通过内部不同的状态选择具体的算法。
什么是「状态模式」?
不同的算法按照统一的标准封装,根据不同的内部状态,决策使用何种算法
** 「状态模式」和「策略模式」的区别**
- 策略模式:依靠客户决策
- 状态模式:依靠内部状态决策
什么真实业务场景可以用「状态模式」?
具体算法的选取是由内部状态决定的
- 首先,内部存在多种状态
- 其次,不同的状态的业务逻辑各不相同
我们有哪些真实业务场景可以用「状态模式」呢?
比如,发送短信接口、限流等等。
- 短信接口
- 服务内部根据最优算法,实时推举出最优的短信服务商,并修改使用何种短信服务商的状态
- 限流
- 服务内部根据当前的实时流量,选择不同的限流算法,并修改使用何种限流算法的状态
怎么用「状态模式」?
关于怎么用,完全可以生搬硬套我总结的使用设计模式的四个步骤:
业务梳理
先来看看一个短信验证码登录的界面。
可以得到:
- 发送短信,用户只需要输入手机号即可
- 至于短信服务使用何种短信服务商,是由短信服务自身的当前短信服务商实例的状态决定
- 当前短信服务商实例的状态又是由服务自身的算法修改
业务流程图
我们通过梳理的文本业务流程得到了如下的业务流程图:
代码建模
「状态模式」的核心是:
- 一个接口:
- 短信服务接口
SmsServiceInterface
- 一个实体类:
伪代码如下:
// 定义一个短信服务接口接口
- 接口`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
import ( "fmt" "math/rand" "runtime" "time" )
type Context struct { Tel string Text string TemplateID string }
type SmsServiceInterface interface { Send(ctx *Context) error }
type ServiceProviderAliyun struct { }
func (s *ServiceProviderAliyun) Send(ctx *Context) error { fmt.Println(runFuncName(), "【阿里云】短信发送成功,手机号:"+ctx.Tel) return nil }
type ServiceProviderTencent struct { }
func (s *ServiceProviderTencent) Send(ctx *Context) error { fmt.Println(runFuncName(), "【腾讯云】短信发送成功,手机号:"+ctx.Tel) return nil }
type ServiceProviderYunpian struct { }
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() }
type ProviderType string
const ( ProviderTypeAliyun ProviderType = "aliyun" ProviderTypeTencent ProviderType = "tencent" ProviderTypeYunpian ProviderType = "yunpian" )
var ( stateManagerInstance *StateManager )
type StateManager struct { currentProviderType ProviderType
currentProvider SmsServiceInterface
setStateDuration time.Duration }
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) } } }() }
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) }
func (m *StateManager) getState() SmsServiceInterface { return m.currentProvider }
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. Go设计模式实战,只是一个在代码合理设计的情况下自然而然无限接近或者达到的结果,并不是一个硬性的目标,务必较真。 2. 本系列的一些设计模式的概念可能和原概念存在差异,因为会结合实际使用,取其精华,适当改变,灵活使用。
|
文章列表
Go设计模式实战系列 更多文章 点击此处查看