为什么要学习状态模式
在软件开发中,我们经常会遇到这样的场景:一个对象的行为取决于它的状态,并且它必须在运行时刻根据状态改变它的行为。比如一个订单可以处于“待支付”、“已支付”、“已发货”、“已完成”、“已取消”等状态,每种状态下能执行的操作不同。传统的处理方式往往是使用大量的 if-else 或 switch-case 语句来判断当前状态,然后执行相应的逻辑。随着状态的增多,这些条件分支会变得臃肿、难以阅读和维护,同时也违反了开闭原则——每增加一个状态都需要修改原有的判断逻辑。
状态模式(State Pattern)正是为解决这类问题而生。它通过将每个状态的行为封装到独立的结构体中,将状态相关的逻辑分散到不同的类中,上下文对象只需要维护一个当前状态对象的引用,并将请求委托给这个状态对象。这样一来,条件判断被多态取代,代码变得清晰,新增状态也只需增加新的状态实现,无需改动原有代码。
状态模式的结构
状态模式主要包含三个角色:
上下文(Context):维护一个具体状态实例的引用,该实例定义了当前的状态。上下文将客户端的请求委托给状态对象来处理。
抽象状态(State):定义一个接口,用于封装与上下文特定状态相关的行为。
具体状态(Concrete State):实现抽象状态接口,实现与该状态相关的行为,并在必要时负责状态的切换。
一个典型的条件判断场景
假设我们正在开发一个简单的订单处理系统,订单有以下几种状态:Pending(待支付)、Paid(已支付)、Shipped(已发货)、Completed(已完成)、Cancelled(已取消)。订单可以执行以下操作:Pay(支付)、Ship(发货)、Complete(完成)、Cancel(取消)。每种状态下允许的操作不尽相同,例如待支付状态下可以支付或取消,但不能发货或完成;已支付状态下可以发货或取消,但不能重复支付等。
如果使用条件判断来实现,代码可能会是这样的:
func (o *Order) Pay() error {
if o.state != "Pending" {
return errors.New("只能对待支付订单进行支付")
}
// 执行支付逻辑...
o.state = "Paid"
return nil
}
func (o *Order) Ship() error {
if o.state != "Paid" {
return errors.New("只能对已支付订单进行发货")
}
// 执行发货逻辑...
o.state = "Shipped"
return nil
}
func (o *Order) Complete() error {
if o.state != "Shipped" {
return errors.New("只能对已发货订单进行完成操作")
}
// 执行完成逻辑...
o.state = "Completed"
return nil
}
func (o *Order) Cancel() error {
if o.state == "Completed" || o.state == "Cancelled" {
return errors.New("已完成或已取消的订单不能取消")
}
// 执行取消逻辑...
o.state = "Cancelled"
return nil
}随着业务规则的复杂化(例如增加了部分退款、已退货等状态),这种方式会变得非常脆弱,修改任何一个方法都可能影响其他状态,测试也变得越来越困难。
使用状态模式重构
我们首先定义一个 OrderState 接口,它包含了订单所有可能的行为:
type OrderState interface {
Pay(*Order) error
Ship(*Order) error
Complete(*Order) error
Cancel(*Order) error
Name() string
}接下来为每种状态创建一个具体类型,并实现该接口。每个状态只负责自己允许执行的操作,对于不允许的操作返回错误。以下以 PendingState 为例:
type PendingState struct{}
func (s *PendingState) Pay(order *Order) error {
fmt.Println("支付成功")
// 变更状态为已支付
order.SetState(&PaidState{})
return nil
}
func (s *PendingState) Ship(order *Order) error {
return errors.New("待支付订单不能发货")
}
func (s *PendingState) Complete(order *Order) error {
return errors.New("待支付订单不能完成")
}
func (s *PendingState) Cancel(order *Order) error {
fmt.Println("订单已取消")
order.SetState(&CancelledState{})
return nil
}
func (s *PendingState) Name() string {
return "Pending"
}同样地,我们可以实现 PaidState、ShippedState、CompletedState 和 CancelledState。在 PaidState 中,Pay 方法会返回错误,而 Ship 方法会切换状态到 ShippedState,Cancel 方法会切换到 CancelledState。在完成状态和取消状态下,大多数操作都会返回错误,因为这些是终态。
定义上下文——订单
上下文 Order 持有一个 OrderState 接口的实例,并提供改变状态的方法,以及将具体操作委托给当前状态:
type Order struct {
state OrderState
}
// NewOrder 创建一个新订单,初始状态为待支付
func NewOrder() *Order {
return &Order{
state: &PendingState{},
}
}
// SetState 允许状态对象改变订单状态
func (o *Order) SetState(s OrderState) {
o.state = s
}
// 以下方法将请求委托给当前状态对象
func (o *Order) Pay() error {
return o.state.Pay(o)
}
func (o *Order) Ship() error {
return o.state.Ship(o)
}
func (o *Order) Complete() error {
return o.state.Complete(o)
}
func (o *Order) Cancel() error {
return o.state.Cancel(o)
}
func (o *Order) CurrentState() string {
return o.state.Name()
}注意,状态对象在执行操作时,如果需要切换状态,会调用订单的 SetState 方法。这种方式让状态本身拥有了决定下一个状态的能力。
完整的示例及测试
将上述代码整合到一个包中,并编写一个简单的使用示例:
package main
import (
"errors"
"fmt"
)
// OrderState 接口定义
type OrderState interface {
Pay(*Order) error
Ship(*Order) error
Complete(*Order) error
Cancel(*Order) error
Name() string
}
// PendingState 实现
type PendingState struct{}
func (s *PendingState) Pay(order *Order) error {
fmt.Println("支付处理中...")
order.SetState(&PaidState{})
return nil
}
func (s *PendingState) Ship(_ *Order) error {
return errors.New("待支付订单不能发货")
}
func (s *PendingState) Complete(_ *Order) error {
return errors.New("待支付订单不能完成")
}
func (s *PendingState) Cancel(order *Order) error {
fmt.Println("订单取消")
order.SetState(&CancelledState{})
return nil
}
func (s *PendingState) Name() string { return "Pending" }
// PaidState 实现
type PaidState struct{}
func (s *PaidState) Pay(_ *Order) error {
return errors.New("订单已支付,不能重复支付")
}
func (s *PaidState) Ship(order *Order) error {
fmt.Println("开始发货...")
order.SetState(&ShippedState{})
return nil
}
func (s *PaidState) Complete(_ *Order) error {
return errors.New("已支付订单不能直接完成")
}
func (s *PaidState) Cancel(order *Order) error {
fmt.Println("订单取消,将发起退款")
order.SetState(&CancelledState{})
return nil
}
func (s *PaidState) Name() string { return "Paid" }
// ShippedState 实现
type ShippedState struct{}
func (s *ShippedState) Pay(_ *Order) error {
return errors.New("已发货订单不能支付")
}
func (s *ShippedState) Ship(_ *Order) error {
return errors.New("订单已发货,不能重复发货")
}
func (s *ShippedState) Complete(order *Order) error {
fmt.Println("订单已完成")
order.SetState(&CompletedState{})
return nil
}
func (s *ShippedState) Cancel(_ *Order) error {
return errors.New("已发货订单不能取消,请申请退货")
}
func (s *ShippedState) Name() string { return "Shipped" }
// CompletedState 实现
type CompletedState struct{}
func (s *CompletedState) Pay(_ *Order) error {
return errors.New("已完成订单不能支付")
}
func (s *CompletedState) Ship(_ *Order) error {
return errors.New("已完成订单不能发货")
}
func (s *CompletedState) Complete(_ *Order) error {
return errors.New("订单已完成,不能重复完成")
}
func (s *CompletedState) Cancel(_ *Order) error {
return errors.New("已完成订单不能取消")
}
func (s *CompletedState) Name() string { return "Completed" }
// CancelledState 实现
type CancelledState struct{}
func (s *CancelledState) Pay(_ *Order) error {
return errors.New("已取消订单不能支付")
}
func (s *CancelledState) Ship(_ *Order) error {
return errors.New("已取消订单不能发货")
}
func (s *CancelledState) Complete(_ *Order) error {
return errors.New("已取消订单不能完成")
}
func (s *CancelledState) Cancel(_ *Order) error {
return errors.New("订单已经取消,不能重复取消")
}
func (s *CancelledState) Name() string { return "Cancelled" }
// Order 上下文
type Order struct {
state OrderState
}
func NewOrder() *Order {
return &Order{state: &PendingState{}}
}
func (o *Order) SetState(s OrderState) {
o.state = s
}
func (o *Order) Pay() error {
return o.state.Pay(o)
}
func (o *Order) Ship() error {
return o.state.Ship(o)
}
func (o *Order) Complete() error {
return o.state.Complete(o)
}
func (o *Order) Cancel() error {
return o.state.Cancel(o)
}
func (o *Order) CurrentState() string {
return o.state.Name()
}
func main() {
order := NewOrder()
fmt.Println("当前状态:", order.CurrentState())
// 正常流程:支付 -> 发货 -> 完成
if err := order.Pay(); err != nil {
fmt.Println("支付错误:", err)
}
fmt.Println("当前状态:", order.CurrentState())
if err := order.Ship(); err != nil {
fmt.Println("发货错误:", err)
}
fmt.Println("当前状态:", order.CurrentState())
if err := order.Complete(); err != nil {
fmt.Println("完成错误:", err)
}
fmt.Println("当前状态:", order.CurrentState())
// 尝试非法操作
if err := order.Cancel(); err != nil {
fmt.Println("取消错误:", err)
}
// 重新演示取消路径
order2 := NewOrder()
order2.Cancel()
fmt.Println("订单2状态:", order2.CurrentState())
}运行该程序,输出结果符合预期:
当前状态: Pending 支付处理中... 当前状态: Paid 开始发货... 当前状态: Shipped 订单已完成 当前状态: Completed 取消错误: 已完成订单不能取消 订单取消 订单2状态: Cancelled
状态模式的优点与注意事项
优点:
消除庞大的条件分支语句:状态模式将不同状态的行为分布到不同的结构体中,使用多态代替条件判断,代码更简洁、易读。
单一职责与开闭原则:每个状态只负责自己的行为,新增状态只需要增加新的实现类,无需修改上下文或已有状态类,符合开闭原则。
状态转换显式化:状态变化被封装在状态类内部或通过上下文接口进行,使得状态变化路径更加清晰可控。
便于测试:可以单独对每一个状态类编写单元测试,而不用模拟大量的上下文条件。
注意事项:
类的数量增加:状态模式会引入很多小的状态结构体,如果状态很多且逻辑简单,可能会显得冗余。此时需要权衡是否使用该模式。
状态之间的耦合:如果具体状态对象直接创建并使用下一个状态(如
PendingState内直接&PaidState{}),会导致状态之间的耦合。可以通过从外部注入状态来降低耦合,例如在上下文中维护状态映射表或使用工厂模式创建状态对象。状态共享:如果状态对象是无状态的(没有成员变量),可以设计为单例以节省内存,但需要注意并发安全。
总结
状态模式是处理对象行为随状态变化的优秀解决方案,尤其在Go语言中,通过接口和多态可以很自然地实现。当你在代码中看到过多的 switch-case 或 if-else 语句来判定状态并执行不同逻辑时,就可以考虑使用状态模式进行重构。它让你的代码更具扩展性,也更容易理解和维护。
在实际项目中,订单、工单、游戏角色状态机、TCP连接状态等场景都是状态模式的绝佳用武之地。合理运用这一模式,可以显著提升代码质量。