首页 > 其他分享 >Go设计模式实战--用状态模式实现系统工作流和状态机

Go设计模式实战--用状态模式实现系统工作流和状态机

时间:2023-06-17 15:45:01浏览次数:47  
标签:状态 -- State 模式 TrafficLight 状态机 state func 设计模式

大家好,这里是每周都在陪你进步的网管~!本节我们讲一个行为型的设计模式--状态模式,并通过Golang示例进行实战演示。

状态模式(State Pattern)也叫作状态机模式(State Machine Pattern)状态模式允许对象的内部状态发生改变时,改变它的行为,就好像对象看起来修改了它实例化的类,状态模式是一种对象行为型模式。

状态模式用于解决系统中复杂对象的状态转换以及不同状态下行为的封装问题。当系统中某个对象存在多个状态,这些状态之间可以进行转换,而且对象在不同状态下行为不相同时可以使用状态模式,把特定于状态的代码抽象到一组独立的状态类中避免过多的状态条件判断,减少维护成本。

状态模式的结构十分简单清晰主要包含三种角色,我们一起来看下。

状态模式的构成

状态模式的结构如下面的UML类图所示

图片状态模式 -- UML类图 网管叨bi叨 分享软件开发和系统架构设计基础、Go 语言和Kubernetes。 241篇原创内容 公众号

主要由环境类角色、抽象状态角色和具体状态角色,三个角色构成。

  • Context(环境类):环境类又称为上下文类,它定义客户端需要的接口,内部维护一个当前状态实例,并负责具体状态的切换。
  • State(抽象状态):定义状态下的行为,可以有一个或多个行为。
  • ConcreteState(具体状态):每一个具体状态类对应环境的一个具体状态,不同的具体状态类其行为有所不同。

状态模式实战

下面举个现实生活中可以用到状态模式的例子,想使用状态模式首先我们得先确定这个业务实体在不同的状态下得拥有不同的行为。

日常生活中常见的拥有状态机的业务实体比如:OA系统的考勤请假审批,每个环节中审批的状态不一样时允许进行的操作也不一样。再比如,大街上的红绿灯,红黄绿不同状态下拍到路上行驶的汽车和检测到车的行驶速度时也会有不同的行为。

下面用 Golang 实现状态模式来解构红绿灯在不同灯的状态下所具有的行为。

图片首先针对交通红绿灯,每种灯状态下都有亮灯、变灯、测速的行为,所以我们首先定义出交通灯的状态接口。

"本文使用的完整可运行源码
去公众号「网管叨bi叨」发送【设计模式】即可领取"
// State interface
type LightState interface {
 // 亮起当前状态的交通灯
 Light()
 // 转换到新状态的时候,调用的方法
 EnterState()
 // 设置一个状态要转变的状态
 NextLight(light *TrafficLight)
 // 检测车速
 CarPassingSpeed(*TrafficLight, int, string)
}

然后我们定义环境类 Context,它提供客户端调用状态行为的接口。

// Context
type TrafficLight struct {
 State LightState
 SpeedLimit int
}

func NewSimpleTrafficLight(speedLimit int) *TrafficLight {
 return &TrafficLight{
  SpeedLimit: speedLimit,
  State: NewRedState(),
 }
}

接下来我们在实现具体状态前,先定义一个DefaultLightState类型用于让具体的LightState 嵌套组合,减少公用法在每个具体 LightState 实现类中的重复实现。

"本文使用的完整可运行源码
去公众号「网管叨bi叨」发送【设计模式】即可领取"
type DefaultLightState struct {
 StateName string
}

func (state *DefaultLightState) CarPassingSpeed(road *TrafficLight, speed int, licensePlate string) {
 if speed > road.SpeedLimit {
  fmt.Printf("Car with license %s was speeding\n", licensePlate)
 }
}

func (state *DefaultLightState) EnterState(){
 fmt.Println("changed state to:", state.StateName)
}

func (tl *TrafficLight) TransitionState(newState LightState) {
 tl.State = newState
 tl.State.EnterState()
}
网管叨bi叨 分享软件开发和系统架构设计基础、Go 语言和Kubernetes。 241篇原创内容 公众号

这个技巧我们也在模版和策略模式使用过,诀窍是它只实现LightState里的通用方法的默认版,不能实现所有的方法,那样的话他也就算一个 LightState 具体实现了,而这不是我们想要的,我们想要的是把接口中每个类型实现逻辑不同的关键方法以及覆盖默认版的通用方法的工作,留给具体类型去实现。

接下来我们定义三个具体状态类型,去实现LightState接口,首先是红灯的状态实现。

// 红灯状态
type redState struct {
 DefaultLightState
}

func NewRedState() *redState {
 state := &redState{}
 state.StateName = "RED"
 return state
}

func (state *redState) Light() {
 fmt.Println("红灯亮起,不可行驶")
}

func (state *redState) CarPassingSpeed(light *TrafficLight, speed int, licensePlate string) {
 // 红灯时不能行驶, 所以这里要重写覆盖 DefaultLightState 里定义的这个方法
 if speed > 0 {
  fmt.Printf("Car with license \"%s\" ran a red light!\n", licensePlate)
 }
}

func (state *redState) NextLight(light *TrafficLight){
 light.TransitionState(NewGreenState())
}

红灯的时候不能行使,所以这里要重写覆盖DefaultLightState 里定义的CarPassingSpeed方法。

接下来是绿灯和黄灯状态:

"本文使用的完整可运行源码
去公众号「网管叨bi叨」发送【设计模式】即可领取"
// 绿灯状态
type greenState struct{
 DefaultLightState
}

func NewGreenState() *greenState{
 state :=  &greenState{}
 state.StateName = "GREEN"
 return state
}

func (state *greenState) Light(){
 fmt.Println("绿灯亮起,请行驶")
}

func (state *greenState) NextLight(light *TrafficLight){
 light.TransitionState(NewAmberState())
}

// 黄灯状态
type amberState struct {
 DefaultLightState
}

func NewAmberState() *amberState{
 state :=  &amberState{}
 state.StateName = "AMBER"
 return state
}

func (state *amberState) Light(){
 fmt.Println("黄灯亮起,请注意")
}

func (state *amberState) NextLight(light *TrafficLight){
 light.TransitionState(NewRedState())
}
网管叨bi叨 分享软件开发和系统架构设计基础、Go 语言和Kubernetes。 241篇原创内容 公众号

通过上面的代码我们可以看到状态实现类在内部确定了状态可以转换的下个状态,这样就把系统流程的状态机留在了内部,避免让客户端代码再去做状态链初始化和转换的判断,符合高内聚的设计原则,从而解放了客户端。

func main() {
 trafficLight := NewSimpleTrafficLight(500)

 interval := time.NewTicker(5 * time.Second)
 for {
  select {
   case <- interval.C:
    trafficLight.State.Light()
    trafficLight.State.CarPassingSpeed(trafficLight, 25, "CN1024")
    trafficLight.State.NextLight(trafficLight)
  default:
  }
 }
}

程序编辑后执行,在终端能看到几个灯的状态会循环切换

图片运行结果

本文的完整源码,已经同步收录到我整理的电子教程里啦,可向我的公众号「网管叨bi叨」发送关键字【设计模式】领取。

标签:状态,--,State,模式,TrafficLight,状态机,state,func,设计模式
From: https://www.cnblogs.com/cheyunhua/p/17487538.html

相关文章

  • 爬取图片写入时报错--添加个等待时间
    当爬取图片时报requests.exceptions.JSONDecodeError:Invalid\escape:line29column132(char62481)这个错时,在写入的时候加个等待时间就好 ......
  • stress模拟系统负载较高时的场景
    #!/bin/bash#获取网卡名称network_adapter=`ipa|grep"BROADCAST"|awk'{print$2}'|awk-F:'{print$1}'`functionNet_speed_limiting{#输入提示read-p"PleaseinputNetspeedlimitingrate(kbit):"Net_raterea......
  • 闲话 Day12.5
    啥时候想起来了就写一写比较好。因为这几天一直在颓所以没啥学术内容。而奇数闲话是学术诶。所以就整了个分数闲话。这种东西可能篇幅会比较短吧。这几天一直在高强度水QQ。而且貌似强度越来越高。不过仔细想一想,之前在机房的时候也差不多。当时经常找APJ一聊一下午或......
  • 使用tcpdump+wirkshark分析nginx反向代理无法访问问题
    问题描述在使用nginx配置多路径反向代理后端prometheus应用的时候由于prometheus本身是自动跳转到/graph才能正常访问,而使用nginx反向代理如果只是配置简单的http_proxy到后端prometheus的端口会报404错误,以下是具体的配置和报错:location/prometheus{proxy_p......
  • HarmonyOS应用开发者基础认证题库
    祝大家都顺利通过判断题1.Web组件对于所有的网页都可以使用zoom(factor:number)方法进行缩放。错误(False)2.每一个自定义组件都有自己的生命周期正确(True)3.每调用一次router.pushUrl()方法,默认情况下,页面栈数量会加1,页面栈支持的最大页面数量为32。正确(True)4.所......
  • NFT数字藏品平台在我国经营需要哪些资质牌照呢?
    (一)区块链信息服务备案NFT的上链、交易等都离不开区块链,根据《区块链信息服务管理规定》规定,需通过国家互联网信息办公室区块链信息服务备案管理系统(bcbeian.ifcert.cn/index)履行备案手续。若平台还属于区块链信息服务提供者(如自带上链服务),则还需履行用户信息认证等义务。(二)增......
  • 保障网络安全与提升爬虫效率:深入探究IP代理技术
    在当今数字化时代,网络安全和数据获取效率是互联网应用中至关重要的两个方面。为了满足这些需求,IP代理技术应运而生。本文将着重介绍socks5和HTTP代理协议,以及如何编写高效的爬虫程序来保障网络安全和提升爬虫效率。IP代理是一种中间服务器,它允许用户通过代理服务器访问互联网资源,同......
  • 搭建发卡平台需要什么配置的服务器?103.219.30.x
    能看到这篇文章的,多数都是想要搭建自己的发卡平台,搭建发卡平台需要有源码、备案域名和服务器,今天我就给你们分享一下搭建发卡平台用什么服务器配置比较好?发卡网对服务器的稳定性和安全性的要求相对一般,如果是按照云服务器的配置来选择,建议宜高不宜低,配置越高,稳定性和安全性就越好。......
  • 【深入浅出Docker原理及实战】「原理实战体系」零基础+全方位带你学习探索Docker容器
    专栏简介本专栏将带领您进入Docker的世界。您是否对Docker有所耳闻?那么,您是否知道使用Docker可以带来什么样的好处呢?如果您还不了解Docker,不用担心,让我们一起探索这个神奇的世界吧!DockerDocker最初是dotCloud公司内部项目,由SolomonHykes在法国创立。它基于dotCloud公司多年......
  •  SourceTree安装说明
    SourceTree安装之后需要使用账号登陆以授权,以前是可以不登陆的,但是现在是强制登陆。虽然是免费授权,但是碰上不可抗力因素,登陆不是很方便,这里记录一下跳过这个初始化的步骤。先运行一次安装包,出现需要登录的窗口后退出。此时桌面上就以出现快捷方式,不需要再使用安装文件了。安装之......