状态模式
本课属于 Coddy GO 学习旅程的“面向对象编程”单元。第 95 课,共 107 课。
状态模式允许对象在内部状态发生变化时改变其行为,让对象看起来像是改变了类。模板方法模式控制算法步骤,而状态模式将特定于状态的行为封装到不同的对象中,并委托给当前状态。
在 Go 中,我们定义一个状态接口,并创建实现不同行为的具体状态:
type State interface {
Handle(d *Document) string
}
type Document struct {
state State
}
func (d *Document) SetState(s State) {
d.state = s
}
func (d *Document) Publish() string {
return d.state.Handle(d)
}每种状态都会决定接下来发生什么,以及下一个状态是什么:
type DraftState struct{}
func (s DraftState) Handle(d *Document) string {
d.SetState(ModerationState{})
return "Draft submitted for moderation"
}
type ModerationState struct{}
func (s ModerationState) Handle(d *Document) string {
d.SetState(PublishedState{})
return "Moderation approved, now published"
}
type PublishedState struct{}
func (s PublishedState) Handle(d *Document) string {
return "Already published"
}同一个方法调用会根据当前状态产生不同的结果:
doc := &Document{state: DraftState{}}
fmt.Println(doc.Publish()) // 草稿已提交审核
fmt.Println(doc.Publish()) // 审核通过,现已发布
fmt.Println(doc.Publish()) // 已经发布状态非常适合具有不同运行模式的对象,例如订单处理工作流、UI 组件或连接处理程序。这些对象的行为完全取决于当前状态。
挑战
简单让我们使用状态模式来构建一个工单支持系统!你将创建一个支持工单,让它依次经历不同阶段,从已开启、处理中到已解决。每个状态都会决定可以执行哪些操作,以及接下来会发生什么。
你需要将代码分成三个文件:
state.go:定义状态接口,以及代表工单生命周期各阶段的具体状态。创建一个
TicketState接口,其中包含方法Handle(t *Ticket) string,用于处理工单,并可能将其转换到下一个状态。实现三个状态:
OpenState:处理时,将工单转换到InProgressState,并返回Ticket opened, assigning to support teamInProgressState:处理时,将工单转换到ResolvedState,并返回Working on ticket, issue resolvedResolvedState:处理时,保持当前状态,并返回Ticket already resolved
ticket.go:创建工单结构体,用于保存当前状态并将行为委托给该状态。创建一个
Ticket结构体,包含ID字段(string 类型)和state字段(TicketState 类型)。添加以下方法:SetState(s TicketState):用于更改工单的当前状态Process() string:将处理委托给当前状态的 Handle 方法
创建一个
NewTicket(id string) *Ticket构造函数,返回初始状态为OpenState的工单。main.go:演示同一操作如何根据工单的当前状态产生不同结果。读取工单 ID 和处理工单的次数。使用该 ID 创建新工单,然后按指定次数调用
Process(),并将每个结果分别打印在单独一行。
输入格式如下:
- 第 1 行:工单 ID
- 第 2 行:处理工单的次数
例如,给定:
TKT-001
3输出应为:
Ticket opened, assigning to support team
Working on ticket, issue resolved
Ticket already resolved再例如,给定:
TKT-500
5输出应为:
Ticket opened, assigning to support team
Working on ticket, issue resolved
Ticket already resolved
Ticket already resolved
Ticket already resolved再例如,给定:
ISSUE-42
1输出应为:
Ticket opened, assigning to support team注意,对同一工单调用 Process() 会在每次调用时产生不同结果:工单在不同状态间转换时,其行为也会随之改变。工单解决后,无论你处理多少次,它都会保持已解决状态。工单对象看起来像是改变了行为,但实际上,它把操作委托给了不同的状态对象!
亲手试试
package main
import (
"fmt"
)
func main() {
// 读取输入
var ticketID string
var numProcesses int
fmt.Scanln(&ticketID)
fmt.Scanln(&numProcesses)
// TODO: 使用给定的 ID 创建一张新工单
// TODO: 按指定次数处理工单
// 并将每次的结果分别打印在单独一行
}
这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: Go 在线编译器