状态变换 | 我的代码没有else
时间:2022-07-28
本文章向大家介绍状态变换 | 我的代码没有else,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
嗯,我的代码没有
else
系列,一个设计模式业务真实使用的golang系列。
前言
本系列主要分享,如何在我们的真实业务场景中使用设计模式。
本系列文章主要采用如下结构:
- 什么是「XX设计模式」?
- 什么真实业务场景可以使用「XX设计模式」?
- 怎么用「XX设计模式」?
本文主要介绍「状态模式」如何在真实业务场景中使用。
「状态模式」比较简单,就是算法的选取取决于自己的内部状态。相较「策略模式」算法的选取由用户决策变成为内部状态决策,「策略模式」是用户(客户端)选择具体的算法,「状态模式」只是通过内部不同的状态选择具体的算法。
什么是「状态模式」?
不同的算法按照统一的标准封装,根据不同的内部状态,决策使用何种算法
「状态模式」和「策略模式」的区别
- 策略模式:依靠客户决策
- 状态模式:依靠内部状态决策
什么真实业务场景可以用「状态模式」?
具体算法的选取是由内部状态决定的
- 首先,内部存在多种状态
- 其次,不同的状态的业务逻辑各不相同
我们有哪些真实业务场景可以用「状态模式」呢?
比如,发送短信接口、限流等等。
- 短信接口
- 服务内部根据最优算法,实时推举出最优的短信服务商,并修改使用何种短信服务商的状态
- 限流
- 服务内部根据当前的实时流量,选择不同的限流算法,并修改使用何种限流算法的状态
怎么用「状态模式」?
关于怎么用,完全可以生搬硬套我总结的使用设计模式的四个步骤:
- 业务梳理
- 业务流程图
- 代码建模
- 代码demo
业务梳理
先来看看一个短信验证码登录的界面。
可以得到:
- 发送短信,用户只需要输入手机号即可
- 至于短信服务使用何种短信服务商,是由短信服务自身的当前短信服务商实例的状态决定
- 当前短信服务商实例的状态又是由服务自身的算法修改
业务流程图
我们通过梳理的文本业务流程得到了如下的业务流程图:
代码建模
「状态模式」的核心是:
- 一个接口:
- 短信服务接口
SmsServiceInterface
- 短信服务接口
- 一个实体类:
- 状态管理实体类
StateManager
- 状态管理实体类
伪代码如下:
// 定义一个短信服务接口
同时得到了我们的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系列 更多文章
- Android Material Design系列之RecyclerView和CardView
- 在Linux安装ASP.Net Core的运行时(Runtime)
- 使用xUnit为.net core程序进行单元测试(下1)
- Otto开发初探——微服务依赖管理新利器
- Apache Eagle——eBay开源分布式实时Hadoop数据安全方案
- Spring/Hibernate 应用性能优化的7种方法
- 浅谈应用型机器学习作为一种搜索问题
- 自相关和偏自相关的简单介绍
- 机器学习中分类与回归的差异
- 自然语言处理指南(第1部分)
- GreenDao 兼容升级,保留旧数据的---全方面解决方案
- 基于 xorm 的服务端框架 XGoServer
- 全面总结: Golang 调用 C/C++,例子式教程
- 架构之路(六):把框架拉出来
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Anroid四大组件service之本地服务的示例代码
- Android使用Activity实现简单的可输入对话框
- ANDROID BottomNavigationBar底部导航栏的实现示例
- Android实现时间倒计时功能
- Android开发基于Drawable实现圆角矩形的方法
- Android开发中滑动分页功能实例详解
- Android登录注册功能 数据库SQLite验证
- CMQ消费者报错,无法获取本机ip地址问题排查
- 腾讯云TKE-Metrics-Server案例: TKE中自建Metrics-Server问题
- (建议收藏)关于JS事件循环, 这一篇就够啦
- TensorFlow2 开发指南 | 02 回归问题之汽车燃油效率预测
- 腾讯云TKE-Ingress案例: TKE-Ingress与Nginx-Ingress共存
- 玩转Kotlin 彻底弄懂Lambda和高阶函数
- leetcode之仅仅反转字母
- 3分钟短文:Laravel的“南天门”,过滤掉七七八八的数据