状态模式属于行为型设计模式,核心思想是将对象的不同状态封装成独立的状态类,让对象在状态发生变化时,自动切换对应的行为逻辑,避免在主对象中写大量的条件判断语句。在Golang中实现状态模式,需要定义状态接口、具体状态类以及持有状态的主对象,通过接口多态实现不同状态下的行为差异。

状态模式的核心角色
在Golang实现状态模式时,通常会包含以下几个核心角色:
- 状态接口:定义所有具体状态需要实现的行为方法,让不同状态可以统一被主对象调用。
- 具体状态类:实现状态接口,每个类对应一种具体的状态,实现该状态下的具体行为逻辑。
- 上下文对象:持有当前状态的引用,负责状态的切换,并且将外部请求委托给当前状态对象处理。
业务场景示例
我们以订单状态管理为例,订单通常有创建、已支付、已发货、已完成四个状态,不同状态下订单支持的操作不同,比如已完成的订单不能再发起支付,已发货的订单不能再修改收货地址。下面我们用状态模式实现这个场景。
1. 定义状态接口
首先定义订单状态接口,包含订单支持的所有操作方法:
// 订单状态接口,定义订单支持的操作
type OrderState interface {
// 支付订单
Pay(ctx *OrderContext) error
// 发货
Ship(ctx *OrderContext) error
// 完成订单
Complete(ctx *OrderContext) error
// 获取当前状态名称
GetName() string
}
2. 实现具体状态类
分别实现四个具体状态,每个状态只处理自己支持的操作,不支持的操作返回错误:
// 创建状态
type CreatedState struct{}
func (s *CreatedState) Pay(ctx *OrderContext) error {
// 创建状态下可以支付,切换到已支付状态
ctx.SetState(&PaidState{})
return nil
}
func (s *CreatedState) Ship(ctx *OrderContext) error {
return fmt.Errorf("订单未支付,无法发货")
}
func (s *CreatedState) Complete(ctx *OrderContext) error {
return fmt.Errorf("订单未完成支付,无法完成")
}
func (s *CreatedState) GetName() string {
return "已创建"
}
// 已支付状态
type PaidState struct{}
func (s *PaidState) Pay(ctx *OrderContext) error {
return fmt.Errorf("订单已支付,无需重复支付")
}
func (s *PaidState) Ship(ctx *OrderContext) error {
// 已支付状态下可以发货,切换到已发货状态
ctx.SetState(&ShippedState{})
return nil
}
func (s *PaidState) Complete(ctx *OrderContext) error {
return fmt.Errorf("订单未发货,无法完成")
}
func (s *PaidState) GetName() string {
return "已支付"
}
// 已发货状态
type ShippedState struct{}
func (s *ShippedState) Pay(ctx *OrderContext) error {
return fmt.Errorf("订单已支付,无需重复支付")
}
func (s *ShippedState) Ship(ctx *OrderContext) error {
return fmt.Errorf("订单已发货,无需重复发货")
}
func (s *ShippedState) Complete(ctx *OrderContext) error {
// 已发货状态下可以完成,切换到已完成状态
ctx.SetState(&CompletedState{})
return nil
}
func (s *ShippedState) GetName() string {
return "已发货"
}
// 已完成状态
type CompletedState struct{}
func (s *CompletedState) Pay(ctx *OrderContext) error {
return fmt.Errorf("订单已完成,无法支付")
}
func (s *CompletedState) Ship(ctx *OrderContext) error {
return fmt.Errorf("订单已完成,无法发货")
}
func (s *CompletedState) Complete(ctx *OrderContext) error {
return fmt.Errorf("订单已完成,无需重复完成")
}
func (s *CompletedState) GetName() string {
return "已完成"
}
3. 实现上下文对象
上下文对象持有当前状态,提供状态切换和外部调用的入口:
import "fmt"
// 订单上下文对象
type OrderContext struct {
// 当前状态
currentState OrderState
// 订单ID
OrderId string
}
// 初始化订单上下文,默认状态为已创建
func NewOrderContext(orderId string) *OrderContext {
return &OrderContext{
OrderId: orderId,
currentState: &CreatedState{},
}
}
// 设置当前状态
func (ctx *OrderContext) SetState(state OrderState) {
ctx.currentState = state
}
// 获取当前状态名称
func (ctx *OrderContext) GetStateName() string {
return ctx.currentState.GetName()
}
// 支付订单,委托给当前状态处理
func (ctx *OrderContext) Pay() error {
return ctx.currentState.Pay(ctx)
}
// 发货,委托给当前状态处理
func (ctx *OrderContext) Ship() error {
return ctx.currentState.Ship(ctx)
}
// 完成订单,委托给当前状态处理
func (ctx *OrderContext) Complete() error {
return ctx.currentState.Complete(ctx)
}
4. 测试状态模式效果
编写测试代码验证状态切换和行为的正确性:
func main() {
// 创建订单
order := NewOrderContext("ORDER_001")
fmt.Printf("初始订单状态:%sn", order.GetStateName())
// 尝试发货,应该失败
err := order.Ship()
if err != nil {
fmt.Printf("发货失败:%sn", err.Error())
}
// 支付订单
err = order.Pay()
if err != nil {
fmt.Printf("支付失败:%sn", err.Error())
} else {
fmt.Printf("支付成功,当前订单状态:%sn", order.GetStateName())
}
// 发货
err = order.Ship()
if err != nil {
fmt.Printf("发货失败:%sn", err.Error())
} else {
fmt.Printf("发货成功,当前订单状态:%sn", order.GetStateName())
}
// 完成订单
err = order.Complete()
if err != nil {
fmt.Printf("完成失败:%sn", err.Error())
} else {
fmt.Printf("完成成功,当前订单状态:%sn", order.GetStateName())
}
// 尝试再次支付,应该失败
err = order.Pay()
if err != nil {
fmt.Printf("再次支付失败:%sn", err.Error())
}
}
状态模式的优势
使用状态模式实现状态管理,相比在上下文对象中写大量的if-else或者switch-case判断,有以下几个明显优势:
- 符合单一职责原则,每个状态的行为逻辑封装在独立的类中,修改某个状态的逻辑不会影响其他状态。
- 符合开闭原则,新增状态时只需要新增一个状态类,不需要修改上下文和其他状态类的代码。
- 避免了大量的条件判断语句,让代码结构更清晰,可读性和可维护性更高。
适用场景
状态模式适合以下场景使用:
- 对象的行为依赖它的状态,并且状态会在运行时频繁切换。
- 对象包含大量的和状态相关的条件判断语句,导致代码难以维护。
- 需要清晰管理多个状态之间的切换规则,避免状态切换逻辑分散在各个业务方法中。