golang设计模式——状态模式

状态模式

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-SAQ0iQcl-1660620676952)(C:/Users/86158/AppData/Roaming/Typora/typora-user-images/image-20220815223714920.png)]

状态模式使用的相对较少,主要是因为会引入大量的状态类,导致代码比较难维护。但是合适的场景使用状态模式,可以把复杂的判断逻辑简化。

状态模式:当一个对象的内在状态改变时允许改变其行为,这个对象看起来像是改变了其类。

UML

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3YdfBGfR-1660620676953)(C:/Users/86158/AppData/Roaming/Typora/typora-user-images/image-20220816103641221.png)]

分析

状态机有3个组成部分:状态(State)、事件(Event)、动作(Action)。事件触发状态的转移及动作的执行。

只看定义和UML,可能比较难理解使用状态模式有什么好处,举个例子就清晰了。

假设有四种状态A、B、C、D,同时有四种触发事件E1、E2、E3、E4,如果不使用状态模式,写出来的样子是这样的:

func E1() {
   if status == "A" {
      //状态迁移+动作执行
   } else if status == "B" {
      //状态迁移+动作执行
   } else if status == "C" {
      //状态迁移+动作执行
   } else if status == "D" {
      //状态迁移+动作执行
   }
}

func E2() {
   if status == "A" {
      //状态迁移+动作执行
   } else if status == "B" {
      //状态迁移+动作执行
   } else if status == "C" {
      //状态迁移+动作执行
   } else if status == "D" {
      //状态迁移+动作执行
   }
}

单看伪代码可能觉得还好,但是细想一想,如果动作执行比较复杂,代码是不是就很丑了。后期如果状态或者事件变更,如何确保每一处都进行了更改?这时候状态模式便起作用了。

我们创建四个类,如UML中的ConcreteStateA、ConcreteStateB、ConcreteStateC、ConcreteStateD,分别代表四种状态。每个状态类中有4个Handle函数,分别对应4个事件。通过这种方式,将糅杂在一起的逻辑进行了拆分,代码看起来优雅了很多。

应用场景

实际业务场景中做过跨境履约单的状态机,履约单需要经历接单、清关中、清关成功、发货等状态。这种场景相对简单,状态只能单方向流转、单独的接口触发指定状态流转到下一个状态,复杂的部分在于下一个状态可能有多个,有的可以跳过。这种情况下,使用数组维护状态机,比状态模式要好。

如果状态多、动作执行逻辑复杂,那使用状态模式还是挺合理的,一般游戏中使用状态模式相对多一些。本次借用《设计模式之美》里超级马里奥的例子,使用超级马里奥介绍实在是太合适了,一是因为马里奥有多种状态、多种触发事件,特别适合使用状态模式;二是超级马里奥大家都玩过,业务情况大家都熟悉。为了帮助大家回忆,我找了马里奥全系列变身形态https://zhuanlan.zhihu.com/p/250931383。

代码实现

马里奥状态有小马里奥(Small Mario)、超级马里奥(Super Mario)、斗篷马里奥(Cape Mario),小马里奥吃了蘑菇变为超级马里奥,小马里奥和超级马里奥获得斗篷变成斗篷马里奥,超级马里奥和斗篷马里奥碰到怪物变成小马里奥。

package main

import "fmt"

type Mario struct {
	score  int64
	status MarioStatus
}

type MarioStatus interface {
	Name()
	ObtainMushroom()
	ObtainCape()
	MeetMonster()
	SetMario(mario *Mario)
}

/**
 * @Description: 小马里奥
 */
type SmallMarioStatus struct {
	mario *Mario
}

/**
 * @Description: 设置马里奥
 * @receiver s
 * @param mario
 */
func (s *SmallMarioStatus) SetMario(mario *Mario) {
	s.mario = mario
}

func (s *SmallMarioStatus) Name() {
	fmt.Println("小马里奥")
}

/**
 * @Description: 获得蘑菇变为超级马里奥
 * @receiver s
 */
func (s *SmallMarioStatus) ObtainMushroom() {
	s.mario.status = &SuperMarioStatus{
		mario: s.mario,
	}
	s.mario.score += 100
}

/**
 * @Description: 获得斗篷变为斗篷马里奥
 * @receiver s
 */
func (s *SmallMarioStatus) ObtainCape() {
	s.mario.status = &CapeMarioStatus{
		mario: s.mario,
	}
	s.mario.score += 200
}

/**
 * @Description: 遇到怪兽减100
 * @receiver s
 */
func (s *SmallMarioStatus) MeetMonster() {
	s.mario.score -= 100
}

/**
 * @Description: 超级马里奥
 */

type SuperMarioStatus struct {
	mario *Mario
}

/**
 * @Description: 设置马里奥
 * @receiver s
 * @param mario
 */
func (s *SuperMarioStatus) SetMario(mario *Mario) {
	s.mario = mario
}

func (s *SuperMarioStatus) Name() {
	fmt.Println("超级马里奥")
}

/**
 * @Description: 获得蘑菇无变化
 * @receiver s
 */
func (s *SuperMarioStatus) ObtainMushroom() {

}

/**
 * @Description:获得斗篷变为斗篷马里奥
 * @receiver s
 */
func (s *SuperMarioStatus) ObtainCape() {
	s.mario.status = &CapeMarioStatus{
		mario: s.mario,
	}
	s.mario.score += 200
}

/**
 * @Description: 遇到怪兽变为小马里奥
 * @receiver s
 */
func (s *SuperMarioStatus) MeetMonster() {
	s.mario.status = &SmallMarioStatus{
		mario: s.mario,
	}
	s.mario.score -= 200
}

/**
 * @Description: 斗篷马里奥
 */
type CapeMarioStatus struct {
	mario *Mario
}

/**
 * @Description: 设置马里奥
 * @receiver s
 * @param mario
 */
func (c *CapeMarioStatus) SetMario(mario *Mario) {
	c.mario = mario
}

func (c *CapeMarioStatus) Name() {
	fmt.Println("斗篷马里奥")
}

/**
 * @Description:获得蘑菇无变化
 * @receiver c
 */
func (c *CapeMarioStatus) ObtainMushroom() {

}

/**
 * @Description: 获得斗篷无变化
 * @receiver c
 */
func (c *CapeMarioStatus) ObtainCape() {

}

/**
 * @Description: 遇到怪兽变为小马里奥
 * @receiver c
 */
func (c *CapeMarioStatus) MeetMonster() {
	c.mario.status = &SmallMarioStatus{
		mario: c.mario,
	}
	c.mario.score -= 200
}
func main() {
	mario := Mario{
		status: &SmallMarioStatus{},
		score:  0,
	}
	mario.status.SetMario(&mario)

	mario.status.Name()
	fmt.Println("-------------------获得蘑菇\n")
	mario.status.ObtainMushroom()

	mario.status.Name()
	fmt.Println("-------------------获得斗篷\n")
	mario.status.ObtainCape()

	mario.status.Name()
	fmt.Println("-------------------遇到怪兽\n")
	mario.status.MeetMonster()

	mario.status.Name()
}

输出:

➜ myproject go run main.go

小马里奥

——————-获得蘑菇

超级马里奥

——————-获得斗篷

斗篷马里奥

——————-遇到怪兽

小马里奥

总结

仔细看上面的代码

  • 对事件触发状态的转移及动作的执行的改动会很简单
  • 可快速增加新的事件
  • 增加新的状态也方便,只需添加新的状态类,少量修改已有代码

坏处就是类特别多,类里的函数也会特别多,即使这些函数根本无用。不过能获得更好的扩展性,还是值得的。

实例

通过下面的例子可以发现,引入状态模式来写状态机会有引入比较多的结构体,并且改动代码的时候如果要新增或者是删除某一个状态的话,修改也需要在其他状态的结构体方法中修改,所以这个不太适合状态经常变更或者是状态很多的情况

代码

// Package state 状态模式
// 笔记请查看: https://lailin.xyz/state.html
// 这是一个工作流的例子,在企业内部或者是学校我们经常会看到很多审批流程
// 假设我们有一个报销的流程: 员工提交报销申请 -> 直属部门领导审批 -> 财务审批 -> 结束
// 在这个审批流中,处在不同的环节就是不同的状态
// 而流程的审批、驳回就是不同的事件
package state

import "fmt"

// Machine 状态机
type Machine struct {
	state IState
}

// SetState 更新状态
func (m *Machine) SetState(state IState) {
	m.state = state
}

// GetStateName 获取当前状态
func (m *Machine) GetStateName() string {
	return m.state.GetName()
}

func (m *Machine) Approval() {
	m.state.Approval(m)
}

func (m *Machine) Reject() {
	m.state.Reject(m)
}

// IState 状态
type IState interface {
	// 审批通过
	Approval(m *Machine)
	// 驳回
	Reject(m *Machine)
	// 获取当前状态名称
	GetName() string
}

// leaderApproveState 直属领导审批
type leaderApproveState struct{}

// Approval 获取状态名字
func (leaderApproveState) Approval(m *Machine) {
	fmt.Println("leader 审批成功")
	m.SetState(GetFinanceApproveState())
}

// GetName 获取状态名字
func (leaderApproveState) GetName() string {
	return "LeaderApproveState"
}

// Reject 获取状态名字
func (leaderApproveState) Reject(m *Machine) {}

func GetLeaderApproveState() IState {
	return &leaderApproveState{}
}

// financeApproveState 财务审批
type financeApproveState struct{}

// Approval 审批通过
func (f financeApproveState) Approval(m *Machine) {
	fmt.Println("财务审批成功")
	fmt.Println("出发打款操作")
}

// 拒绝
func (f financeApproveState) Reject(m *Machine) {
	m.SetState(GetLeaderApproveState())
}

// GetName 获取名字
func (f financeApproveState) GetName() string {
	return "FinanceApproveState"
}

// GetFinanceApproveState GetFinanceApproveState
func GetFinanceApproveState() IState {
	return &financeApproveState{}
}

单元测试

package state

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestMachine_GetStateName(t *testing.T) {
	m := &Machine{state: GetLeaderApproveState()}
	assert.Equal(t, "LeaderApproveState", m.GetStateName())
	m.Approval()
	assert.Equal(t, "FinanceApproveState", m.GetStateName())
	m.Reject()
	assert.Equal(t, "LeaderApproveState", m.GetStateName())
	m.Approval()
	assert.Equal(t, "FinanceApproveState", m.GetStateName())
	m.Approval()
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是几种常见的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 } ```

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值