【设计模式】状态模式,为何状态切换会如此丝滑?

状态模式属于行为型设计模式

GoF定义:允许一个对象在其内部状态改变时改变它的行为。对象看起来似乎修改了它的类

没错,可以让对象的状态切换变得丝滑

场景

会员等级的降级计算

1.统计近一年内的已完成的订单,累计金额

2.然后判断金额的范围是否符合对应等级的要求,不符合则降级,符合则保级

3.若保级需要赠送保级礼包,并发送短信通知

分析 不同的等级状态,需要执行不同的业务逻辑。非常符合状态模式

1.保级时,进行数据库的会员等级更新、生成保级礼包、赠送保级礼包、发送短信通知

2.降级时,只进行数据库会员等级更新

实现

状态接口

go 复制代码
// 状态接口  
type ILevelState interface {  
    Update(user *User)  
}

User 用户结构体,持有当前状态
SetLevelState 用于切换等级状态

go 复制代码
type User struct {  
    UserInfo *UserInfo  
    State    ILevelState  
} 

func (u *User) SetLevelState(afterLevel *Level) {  
    if u.UserInfo.LevelId > afterLevel.LevelId {  
       u.LevelState = NewDegradeState(u.UserInfo)  
       return  
    }  
  
    u.LevelState = NewRemainState(u.UserInfo)  
}
  
type UserInfo struct {  
    UserId    uint64  
    Nickname  string  
    Telephone string  
    LevelId   uint64  
}  
  

type Level struct {
    Id      uint64
    LevelId uint64
    Name    string
}

降级状态

数据库更新这里就不展开说明了。

这里关键是要实现ILevelState接口定义的方法Update(user *User) ,在这个方法里面实现自己的逻辑

go 复制代码
type DegradeState struct {  
    UserInfo *UserInfo  
}  
  
func NewDegradeState(userInfo *UserInfo) *DegradeState {  
    return &DegradeState{UserInfo: userInfo}  
}  
  
func (d *DegradeState) Update(user *User) {  
    // 更新数据库  
    fmt.Printf("更新数据库, 用户ID: %v, 等级ID: %v\n", d.UserInfo.UserId, d.UserInfo.LevelId)  
    // 降级逻辑  
    fmt.Printf("用户: %v, 会员等级: %v 降级为 %v\n", d.UserInfo.Nickname, findLevel(d.UserInfo.LevelId).Name, findLevel(d.UserInfo.LevelId-1).Name)  
    fmt.Println()  
}

保级状态

同样数据库更新、赠送礼包、发送短信这里就不展开说明了。

同样这里关键是要实现ILevelState接口定义的方法Update(user *User) ,在这个方法里面实现自己的逻辑

go 复制代码
type RemainState struct {  
    UserInfo *UserInfo  
}  
  
func NewRemainState(userInfo *UserInfo) *RemainState {  
    return &RemainState{UserInfo: userInfo}  
}  
  
func (r *RemainState) Update(user *User) {  
    // 更新数据库  
    fmt.Printf("更新数据库, 用户ID: %v, 等级ID: %v\n", r.UserInfo.UserId, r.UserInfo.LevelId)  
    // 保级逻辑  
    fmt.Printf("赠送保级礼包,发送短信通知, 用户ID: %v, 手机号:%v\n", r.UserInfo.UserId, r.UserInfo.Telephone)  
    fmt.Printf("用户: %v, 保级成功, 会员等级: %v\n", r.UserInfo.Nickname, findLevel(r.UserInfo.LevelId).Name)  
    fmt.Println()  
}

等级统计

等级统计完成后,用SetLevelState进行状态切换,随后执行状态自己的更新逻辑。

让不同状态更新逻辑解耦。

go 复制代码
// LevelStatistic 等级统计  
type LevelStatistic struct{}  
  
func NewLevelStatistic() *LevelStatistic {  
    return &LevelStatistic{}  
}  
  
// 计算用户等级并更新状态  
func (c *LevelStatistic) Calculate(user *User) {  
 	// 模拟计算逻辑
	// 实际应根据具体要求进行计算,比如:统计近一年内的已完成的订单,累计金额,然后判断范围
	// 模拟计算,获取随机值,可能会出现升级的情况,这里不考虑,实际是不会出现的
	levels := getAllLevel()
	rand.Seed(time.Now().UnixNano())
	n := rand.Intn(4)
	afterLevel := levels[n]

	// 更改用户状态
	user.SetLevelState(afterLevel)

	// 执行当前状态的逻辑
	user.LevelState.Update(user)
}

完整代码

go 复制代码
package program

import (
	"fmt"
	"math/rand"
	"time"
)

// 用户结构体,持有当前状态
type User struct {
	UserInfo   *UserInfo
	LevelState ILevelState
}

func (u *User) SetLevelState(afterLevel *Level) {
	if u.UserInfo.LevelId > afterLevel.LevelId {
		u.LevelState = NewDegradeState(u.UserInfo)
		return
	}

	u.LevelState = NewRemainState(u.UserInfo)
}

type UserInfo struct {
	UserId    uint64
	Nickname  string
	Telephone string
	LevelId   uint64
}

type Level struct {
	Id      uint64
	LevelId uint64
	Name    string
}

// 获取所有等级
func getAllLevel() []*Level {
	// 模拟数据库返回
	return []*Level{
		{Id: 10, LevelId: 1, Name: "粉丝"},
		{Id: 9, LevelId: 2, Name: "黄金"},
		{Id: 20, LevelId: 3, Name: "铂金"},
		{Id: 18, LevelId: 4, Name: "钻石"},
	}
}

// 查找等级
func findLevel(levelId uint64) *Level {
	// 模拟数据库返回
	levels := getAllLevel()
	for _, item := range levels {
		if levelId == item.LevelId {
			return item
		}
	}
	return nil
}

// 状态接口
type ILevelState interface {
	Update(user *User)
}

// 降级状态
type DegradeState struct {
	UserInfo *UserInfo
}

func NewDegradeState(userInfo *UserInfo) *DegradeState {
	return &DegradeState{UserInfo: userInfo}
}

func (d *DegradeState) Update(user *User) {
	// 更新数据库
	fmt.Printf("更新数据库, 用户ID: %v, 等级ID: %v\n", d.UserInfo.UserId, d.UserInfo.LevelId)
	// 降级逻辑
	fmt.Printf("用户: %v, 会员等级: %v 降级为 %v\n", d.UserInfo.Nickname, findLevel(d.UserInfo.LevelId).Name, findLevel(d.UserInfo.LevelId-1).Name)
	fmt.Println()
}

// 保级状态
type RemainState struct {
	UserInfo *UserInfo
}

func NewRemainState(userInfo *UserInfo) *RemainState {
	return &RemainState{UserInfo: userInfo}
}

func (r *RemainState) Update(user *User) {
	// 更新数据库
	fmt.Printf("更新数据库, 用户ID: %v, 等级ID: %v\n", r.UserInfo.UserId, r.UserInfo.LevelId)
	// 保级逻辑
	fmt.Printf("赠送保级礼包,发送短信通知, 用户ID: %v, 手机号:%v\n", r.UserInfo.UserId, r.UserInfo.Telephone)
	fmt.Printf("用户: %v, 保级成功, 会员等级: %v\n", r.UserInfo.Nickname, findLevel(r.UserInfo.LevelId).Name)
	fmt.Println()
}

// LevelStatistic 等级统计
type LevelStatistic struct{}

func NewLevelStatistic() *LevelStatistic {
	return &LevelStatistic{}
}

// 计算用户等级并更新状态
func (c *LevelStatistic) Calculate(user *User) {
	// 模拟计算逻辑
	// 实际应根据具体要求进行计算,比如:统计近一年内的已完成的订单,累计金额,然后判断范围
	// 模拟计算,获取随机值,可能会出现升级的情况,这里不考虑,实际是不会出现的
	levels := getAllLevel()
	rand.Seed(time.Now().UnixNano())
	n := rand.Intn(4)
	afterLevel := levels[n]

	// 更改用户状态
	user.SetLevelState(afterLevel)

	// 执行当前状态的逻辑
	user.LevelState.Update(user)
}

func main() {
	users := []*UserInfo{
		{UserId: 1, Nickname: "测试1", Telephone: "13999999999", LevelId: 3},
		{UserId: 2, Nickname: "测试2", Telephone: "13999999998", LevelId: 3},
		{UserId: 3, Nickname: "测试3", Telephone: "13999999997", LevelId: 4},
	}

	statistic := NewLevelStatistic()
	for _, userInfo := range users {
		user := &User{
			UserInfo:   userInfo,
			LevelState: NewRemainState(userInfo), // 初始状态为保级状态
		}
		statistic.Calculate(user)
	}
}

总结

状态模式将对象的每个状态封装为独立的类,并通过状态对象的切换来改变对象的行为,从而实现对象在不同状态下的不同行为。

这种模式使得代码更加清晰、模块化,并且易于扩展。

体现了对扩展开放,对修改关闭单一职责 设计原则,让状态切换变得如此丝滑

这里也许有的小伙伴会想,观察者不是也可以实现么?

的确,不否认,观察者也可以实现。

但观察者还可以是对象状态变化通知其他一个或多个对象进行逻辑处理;

状态模式是对象状态变化通知自己进行逻辑处理。

看到这里,我想你应该了解了状态模式。

快看看自己的项目代码,是不是也可以使用状态模式吧!

相关推荐
慕容静漪2 小时前
如何本地安装Python Flask并结合内网穿透实现远程开发
开发语言·后端·golang
ErizJ2 小时前
Golang|锁相关
开发语言·后端·golang
骊山道童2 小时前
设计模式-外观模式
设计模式·外观模式
找了一圈尾巴2 小时前
设计模式(结构型)-享元模式
设计模式·享元模式
烛阴2 小时前
手把手教你搭建 Express 日志系统,告别线上事故!
javascript·后端·express
良许Linux2 小时前
请问做嵌入式开发C语言应该学到什么水平?
后端
Pitayafruit3 小时前
SpringBoot整合Flowable【08】- 前后端如何交互
spring boot·后端·workflow
小丁爱养花3 小时前
驾驭 Linux 云: JavaWeb 项目安全部署
java·linux·运维·服务器·spring boot·后端·spring
uhakadotcom4 小时前
Amazon GameLift 入门指南:六大核心组件详解与实用示例
后端·面试·github