设计模式-状态模式 golang实现

目录

目录

一 什么是有限状态机 

1.1四大概念

二 状态机的实现方法

2.1 分支逻辑

2.2 查表法

2.3状态模式

三 总结

3.1实现方法对比

3.2状态机设计基本原则

3.3状态机常⻅设计误区



一 什么是有限状态机 

有限状态机,英⽂翻译是 Finite State Machine,缩写为 FSM,简称为状态机。

状态机不是指一台实际机器,而是指一个数学模型。说白了,一般就是指一张状态转换图。

已订单交易为例:

1.1四大概念

下面来给出状态机的四大概念。

  1. State ,状态。一个状态机至少要包含两个状态。例如上商家交易有 已下单、已支付、已发货等多种状态。
  2. Event,事件。事件也称为转移条件(Transition Condition)。例如 客户下单、 客户完成支付、商家发货 都是一个事件。
  3. Action ,动作。事件发生以后要执行动作。例如用户支付,扣减用户余额就是动作。编程的时候,一个 Action 一般就对应一个函数。不过动作不是必须的,也可能只转移状态,不执⾏任何动作。
  4. Transition ,变换。也就是从一个状态变化为另一个状态。例如 订单从“已支付”转换到“已发货”。

二 状态机的实现方法

将上面业务流程翻译成骨架代码:


type State int64

const StateWaitingPayment State = 1 //等待支付
const StateWaitingShip State = 2    //支付成功待发货

// 订单状态机
type LeaseStateMachine struct {
	State State //订单状态
}

// 订单支付成功
func (p *LeaseStateMachine) EventPaySuccess() {
	//todo
}

// 取消了订单
func (p *LeaseStateMachine) EventCancelOrder() {
	//todo
}

// 商家发货
func (p *LeaseStateMachine) EventShipped() {
	//todo
}

// 确认收货
func (p *LeaseStateMachine) EventConfirmReceipt() {
	//todo
}

2.1 分支逻辑

最简单直接的实现⽅式是,参照状态转移 图,将每⼀个状态转移,直译成代码。这样编写的代码会包含⼤量的 if-else 或 switch-case 分⽀判断逻辑。

type State int64

const StateWaitingPayment State = 1      //等待支付
const StateWaitingShip State = 2         //支付成功待发货
const StateWaitingShipped State = 3      //发货成功
const StateWaitingOrderSuccess State = 4 //订单结束
const StateWaitingOrderCancel State = 5  //订单取消

// 租赁订单状态机
type LeaseStateMachine struct {
	State State //订单状态
}

// 订单支付成功
func (p *LeaseStateMachine) EventPaySuccess() {
	if p.State == StateWaitingPayment {
		p.State = StateWaitingShip
	}
}

// 取消了订单
func (p *LeaseStateMachine) EventCancelOrder() {
	if p.State == StateWaitingShip ||
		p.State == StateWaitingPayment {
		p.State = StateWaitingOrderCancel
	}
}

// 商家发货
func (p *LeaseStateMachine) EventShipped() {
	if p.State == StateWaitingShip {
		p.State = StateWaitingShipped
	}
}

// 确认收货
func (p *LeaseStateMachine) EventConfirmReceipt() {
	if p.State == StateWaitingShipped {
		p.State = StateWaitingOrderSuccess
	}
}

2.2 查表法

除了⽤状态转移图来表示之外,状态机还可以⽤⼆维表来表示;将上面的状态图转换成二维表如下

当前状态/事件

E支付成功

E发货E取消订单E确认收货
等待支付支付成功待发货///
支付成功待发货/发货成功订单取消/
已发货///订单结束
订单结束////
订单取消////

使用查表表修改上述代码:
 


type State int64

const StateWaitingPayment State = 1      //等待支付
const StateWaitingShip State = 2         //支付成功待发货
const StateWaitingShipped State = 3      //发货成功
const StateWaitingOrderSuccess State = 4 //订单结束
const StateWaitingOrderCancel State = 5  //订单取消

type Event int64

const (
	EventPay            Event = 1 //支付事件
	EventShip           Event = 2 //发货 事件
	EventCancel         Event = 3 //取消订单 事件
	EventConfirmReceipt Event = 4 //确认收货
)

// 状态二维表配置
var StateTable map[State]map[Event]State = map[State]map[Event]State{
	StateWaitingPayment: {
		EventPay: StateWaitingShip, //待支付订单 ,支付事件 => 已支付
	},
	StateWaitingShip: {
		EventShip:   StateWaitingShipped,
		EventCancel: StateWaitingOrderCancel,
	},
	//.......
}

// 租赁订单状态机
type LeaseStateMachine struct {
	State State //订单状态

}

// 订单支付成功
func (p *LeaseStateMachine) EventPaySuccess() {
	p.ExecEventConfirmReceipt(EventPay)
}

// 取消了订单
func (p *LeaseStateMachine) EventCancelOrder() {
	p.ExecEventConfirmReceipt(EventCancel)
}

// 商家发货
func (p *LeaseStateMachine) EventShipped() {
	p.ExecEventConfirmReceipt(EventShip)
}

// 确认收货
func (p *LeaseStateMachine) EventConfirmReceipt() {

	p.ExecEventConfirmReceipt(EventConfirmReceipt)
}

// 执行事件
func (p *LeaseStateMachine) ExecEventConfirmReceipt(event Event) {
	EventNewStateTable, ok := StateTable[p.State]
	if ok {
		newState, ok := EventNewStateTable[event]
		if ok {
			p.State = newState
		}
	}
}

在查表法的代码实现中,事件触发的动作只是简单状态变换,所以⽤⼀个 int 类型 的⼆维数组 actionTable 就能表示。但是,如果要执⾏ 动作并⾮这么简单,⽽是⼀系列复杂的逻辑操作(⽐如加减积分、写数据库,还有可能发 送消息通知等等),我们就没法⽤如此简单的⼆维数组来表示了。

2.3状态模式

状态模式通过将事件触发的状态转移和动作执⾏,拆分到不同的状态类中,来避免分⽀判断

逻辑。
 

1.定义interface 所有事件

type ILeaseState interface {
    //定义事件
    EventPay() //支付事件
    EventShip() //发货事件
    EventCancel() //取消订单事件
    EventConfirmReceipt() //确认收货事件
}

2.状态类实现 事件对应的action

将事件对饮的代码逻辑被分散到各个状态类中。

//==================================================================
// 待支付状态
type StateWaitingPaymentImp struct{}

// 订单支付成功
func (p *StateWaitingPaymentImp) EventPay() {
	//todo 更新订单状态
}

// 发货
func (p *StateWaitingPaymentImp) EventShip() {
	//不做处理
}

// 取消
func (p *StateWaitingPaymentImp) EventCancel() {
	//todo 取消
}

// 确认收货事件
func (p *StateWaitingPaymentImp) EventConfirmReceipt() {
	//不做处理
}
//==================================================================
// 支付成功 状态
type StateWaitingShipImp struct{}

// 订单支付成功
func (p *StateWaitingShipImp) EventPay() {
	//不做任何处理
}

// 发货
func (p *StateWaitingShipImp) EventShip() {
	//更新订单未发货
}

// 取消
func (p *StateWaitingShipImp) EventCancel() {
	//更新订单未发货
}

// 确认收货事件
func (p *StateWaitingShipImp) EventConfirmReceipt() {
	//不做处理
}
//===============================================================
//........其他状态对应的事件

三 总结

3.1实现方法对比

实现方法优点缺点
分支逻辑
  • 简单、直接,易理解。
  • 对简单的状态机首选该方法实现。

  • 对于复杂的状态机来说,代码中充斥着⼤量的 ifelse 或者 switch-case 分⽀判断逻辑,可读性和可维护性差。

    易漏写或者错写某个状态转移。
    如果哪天修改了状态机 中的某个状态转移,我们要在冗⻓的分⽀逻辑中找到对应的代码进⾏修改,很容易改错,导致 bug。
 
查表法
  • 查表法的代码实现更加清晰,可读性和可维护性更好。
  • 当修改 状态机时,只需要修改 transitionTable 和 actionTable 状态转移配置
     
  • 查表法的实现⽅式有⼀定局限性,
    执行的action只能是简单的状态转移操作。

    如果要执⾏的action是⼀系列复杂的逻辑操作(⽐如加减积分、写数据库,还有可能发送消息通知等等),我们就没法⽤如此简单的⼆维数组来表示了。
状态模式
 
  • 对于状态并不多、状态转移也⽐较简单,但事件触发执⾏的action包含的业务逻辑可能⽐较复杂的状态机来说,⾸选状态模式
 
  • 状态模式会引⼊⾮常多的状态类,会导致代码⽐较难维护

像电商下单这种状态并不多,状态转移也⽐较简单,但事件触发执⾏的动作包含的业务逻辑可能会⽐较复杂,更加推荐使⽤状态模式来实现。

像游戏⽐较复杂的状态机,包含的状态⽐较多,优先推荐使⽤查表法;

3.2状态机设计基本原则

  • 明确性:状态和转换必须清晰定义,避免含糊不清的状态
  • 完备性:为所有可能的事件-状态组合定义转换逻辑
  • 可预测性:系统应根据当前状态和给定事件可预测地响应
  • 最⼩化:状态数应保持最⼩,避免不必要的复杂性

3.3状态机常⻅设计误区

  • 过度设计:引⼊不必要的状态和复杂性,使系统难以理解和维护。
  • 不完备的处理:未能处理所有可能的状态转换,导致系统⾏为不确定。
  • 硬编码逻辑:过多的硬编码转换逻辑,使系统不具备灵活性和可扩展性。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
以下是几种常见的Golang设计模式: 1. 工厂模式(Factory Pattern):用于创建对象的模式,通过定义一个创建对象的接口来实现对象的实例化。 ```go type Shape interface { Draw() } type Circle struct{} func (c *Circle) Draw() { fmt.Println("Drawing a circle") } type Rectangle struct{} func (r *Rectangle) Draw() { fmt.Println("Drawing a rectangle") } type ShapeFactory struct{} func (sf *ShapeFactory) GetShape(shapeType string) Shape { if shapeType == "circle" { return &Circle{} } else if shapeType == "rectangle" { return &Rectangle{} } return nil } func main() { factory := &ShapeFactory{} circle := factory.GetShape("circle") circle.Draw() // 输出:Drawing a circle rectangle := factory.GetShape("rectangle") rectangle.Draw() // 输出:Drawing a rectangle } ``` 2. 单例模式(Singleton Pattern):确保一个类只有一个实例,并提供一个全局访问点。 ```go type Singleton struct{} var instance *Singleton func GetInstance() *Singleton { if instance == nil { instance = &Singleton{} } return instance } func main() { singleton1 := GetInstance() singleton2 := GetInstance() fmt.Println(singleton1 == singleton2) // 输出:true } ``` 3. 观察者模式(Observer Pattern):定义了一种一对多的依赖关系,当一个对象的状态发生改变时,所有依赖它的对象都会得到通知并自动更新。 ```go type Subject struct { observers []Observer } func (s *Subject) Attach(observer Observer) { s.observers = append(s.observers, observer) } func (s *Subject) Notify() { for _, observer := range s.observers { observer.Update() } } type Observer interface { Update() } type ConcreteObserver struct{} func (co *ConcreteObserver) Update() { fmt.Println("Observer is updated") } func main() { subject := &Subject{} observer := &ConcreteObserver{} subject.Attach(observer) subject.Notify() // 输出:Observer is updated } ```

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值