Skip to content

职责链与状态模式

本章讲两个行为型模式:职责链(Chain of Responsibility)和状态(State)。职责链在 Go 中有极为常见的应用——HTTP 中间件就是职责链的典型实现。状态模式则用于实现状态机,订单流转、工作流审批等场景都离不开它。我们看 Go 如何用接口和函数类型实现这两个模式。

一、职责链模式

1. 意图

职责链模式:让多个对象都有机会处理请求,从而避免请求的发送者和接收者之间的耦合关系。把这些对象连成一条链,并沿着链传递请求,直到有一个对象处理它为止。

2. 经典场景

  • HTTP 中间件:鉴权 → 限流 → 日志 → 业务。
  • 审批流程:组长 → 经理 → 总监 → CEO,按额度逐级上报。
  • 异常处理:try/catch 链。
  • 事件冒泡:DOM 事件从子节点冒泡到根节点。
  • 过滤器链:请求过滤、响应过滤。

3. Go 实现:链式处理器

职责链的核心:每个处理器持有一个「下一个处理器」的引用,能处理就处理,不能处理就传给下一个。

go
package main

import "fmt"

// 处理器接口
type Handler interface {
	SetNext(Handler) Handler
	Handle(request string) string
}

// 基础结构:提供默认的 SetNext 实现
type BaseHandler struct {
	next Handler
}

func (b *BaseHandler) SetNext(h Handler) Handler {
	b.next = h
	return h // 返回 h 方便链式调用
}

// 具体处理器:鉴权
type AuthHandler struct {
	BaseHandler
}

func (h *AuthHandler) Handle(req string) string {
	if len(req) > 0 && req[0:4] == "auth" {
		return "AuthHandler: 已鉴权"
	}
	if h.next != nil {
		return h.next.Handle(req)
	}
	return "AuthHandler: 无法处理"
}

// 具体处理器:日志
type LogHandler struct {
	BaseHandler
}

func (h *LogHandler) Handle(req string) string {
	if len(req) > 0 && req[0:3] == "log" {
		return "LogHandler: 已记录"
	}
	if h.next != nil {
		return h.next.Handle(req)
	}
	return "LogHandler: 无法处理"
}

// 具体处理器:业务
type BizHandler struct {
	BaseHandler
}

func (h *BizHandler) Handle(req string) string {
	if req == "biz" {
		return "BizHandler: 业务处理完成"
	}
	if h.next != nil {
		return h.next.Handle(req)
	}
	return "BizHandler: 无法处理"
}

func main() {
	auth := &AuthHandler{}
	log := &LogHandler{}
	biz := &BizHandler{}

	// 构建链:auth -> log -> biz
	auth.SetNext(log)
	log.SetNext(biz)

	for _, req := range []string{"auth", "log", "biz", "unknown"} {
		fmt.Printf("请求 %q -> %s\n", req, auth.Handle(req))
	}
}

BaseHandler 提供了 SetNext 的默认实现,具体处理器通过嵌入获得,只需实现 Handle。这是 Go 模拟「抽象基类」的常见技巧。

4. Go 中间件就是职责链

Go 的 HTTP 中间件 func(http.Handler) http.Handler 本质就是职责链:每个中间件决定是处理请求、拒绝、还是传给下一个。我们在装饰器模式章节已经演示过,这里再看一个带「中断」能力的例子:

go
package main

import (
	"fmt"
	"net/http"
	"net/http/httptest"
)

type Middleware func(http.Handler) http.Handler

// 鉴权中间件:不通过则中断链
func AuthMiddleware(token string) Middleware {
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			if r.Header.Get("Authorization") != token {
				http.Error(w, "未授权", http.StatusUnauthorized)
				return // 中断,不调用 next
			}
			next.ServeHTTP(w, r)
		})
	}
}

// 限流中间件:超过限制则中断
func RateLimitMiddleware(limit int) Middleware {
	count := 0
	return func(next http.Handler) http.Handler {
		return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
			count++
			if count > limit {
				http.Error(w, "限流", http.StatusTooManyRequests)
				return
			}
			next.ServeHTTP(w, r)
		})
	}
}

// 日志中间件:记录后继续
func LogMiddleware(next http.Handler) http.Handler {
	return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		fmt.Printf("[LOG] %s %s\n", r.Method, r.URL.Path)
		next.ServeHTTP(w, r)
	})
}

func Chain(h http.Handler, mws ...Middleware) http.Handler {
	for i := len(mws) - 1; i >= 0; i-- {
		h = mws[i](h)
	}
	return h
}

func main() {
	final := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		w.Write([]byte("业务响应"))
	})

	handler := Chain(final,
		LogMiddleware,
		AuthMiddleware("Bearer secret"),
		RateLimitMiddleware(2),
	)

	// 测试 1:无 token
	doRequest(handler, "", "/api")
	// 测试 2:有 token,第 1 次
	doRequest(handler, "Bearer secret", "/api")
	// 测试 3:有 token,第 2 次
	doRequest(handler, "Bearer secret", "/api")
	// 测试 4:有 token,第 3 次(被限流)
	doRequest(handler, "Bearer secret", "/api")
}

func doRequest(h http.Handler, token, path string) {
	req := httptest.NewRequest("GET", path, nil)
	if token != "" {
		req.Header.Set("Authorization", token)
	}
	w := httptest.NewRecorder()
	h.ServeHTTP(w, req)
	fmt.Printf("  -> 状态码 %d, body %q\n", w.Code, w.Body.String())
}

中间件链的特点:每个中间件可以选择「调用 next」(继续)或「不调用 next」(中断),这就是职责链的精髓——请求可能传到链尾,也可能中途被拦截。

5. 实战示例:审批流程

下面用职责链实现一个多级审批流程,金额越大审批层级越高:

go
package main

import "fmt"

type Expense struct {
	Amount  float64
	Purpose string
}

type Approver interface {
	SetNext(Approver) Approver
	Approve(Expense) string
}

type BaseApprover struct {
	next   Approver
	name   string
	limit  float64
}

func (b *BaseApprover) SetNext(a Approver) Approver {
	b.next = a
	return a
}

func (b *BaseApprover) Approve(e Expense) string {
	if e.Amount <= b.limit {
		return fmt.Sprintf("%s 批准 %.2f 元(%s)", b.name, e.Amount, e.Purpose)
	}
	if b.next != nil {
		return b.next.Approve(e)
	}
	return fmt.Sprintf("无人能批准 %.2f 元", e.Amount)
}

func main() {
	// 构建审批链:组长(1000) -> 经理(10000) -> 总监(50000) -> CEO(无上限)
	teamLead := &BaseApprover{name: "组长", limit: 1000}
	manager := &BaseApprover{name: "经理", limit: 10000}
	director := &BaseApprover{name: "总监", limit: 50000}
	ceo := &BaseApprover{name: "CEO", limit: 1 << 62}

	teamLead.SetNext(manager).SetNext(director).SetNext(ceo)

	expenses := []Expense{
		{500, "团建"},
		{5000, "采购电脑"},
		{30000, "市场活动"},
		{100000, "新项目启动"},
	}
	for _, e := range expenses {
		fmt.Println(teamLead.Approve(e))
	}
}

SetNext 返回下一个 approver,所以可以链式 a.SetNext(b).SetNext(c).SetNext(d),写起来很流畅。

二、状态模式

1. 意图

状态模式:让一个对象在其内部状态改变时改变其行为。对象看起来像是改变了它的类。

2. 与策略模式的区别

第 8 章我们对比过:策略是「客户端选」,状态是「自己变」。状态模式的核心是 状态转换——某个状态处理完事件后,对象自动进入下一个状态,客户端不感知。

3. Go 实现:状态接口 + 状态实现

go
package main

import "fmt"

// 状态接口
type State interface {
	Handle(ctx *OrderContext, event string)
}

// 上下文:持有当前状态
type OrderContext struct {
	id    string
	state State
}

func NewOrderContext(id string, initial State) *OrderContext {
	return &OrderContext{id: id, state: initial}
}

func (c *OrderContext) SetState(s State) {
	c.state = s
}

func (c *OrderContext) Trigger(event string) {
	fmt.Printf("[订单 %s] 当前状态=%s, 事件=%s\n", c.id, c.state, event)
	c.state.Handle(c, event)
}

// === 具体状态:待支付 ===
type PendingState struct{}

func (PendingState) String() string { return "待支付" }

func (PendingState) Handle(ctx *OrderContext, event string) {
	switch event {
	case "pay":
		fmt.Println("  -> 支付成功,转为已支付")
		ctx.SetState(PaidState{})
	case "cancel":
		fmt.Println("  -> 取消订单,转为已取消")
		ctx.SetState(CancelledState{})
	default:
		fmt.Println("  -> 待支付状态不处理此事件")
	}
}

// === 具体状态:已支付 ===
type PaidState struct{}

func (PaidState) String() string { return "已支付" }

func (PaidState) Handle(ctx *OrderContext, event string) {
	switch event {
	case "ship":
		fmt.Println("  -> 发货,转为已发货")
		ctx.SetState(ShippedState{})
	case "refund":
		fmt.Println("  -> 退款,转为已退款")
		ctx.SetState(RefundedState{})
	default:
		fmt.Println("  -> 已支付状态不处理此事件")
	}
}

// === 具体状态:已发货 ===
type ShippedState struct{}

func (ShippedState) String() string { return "已发货" }

func (ShippedState) Handle(ctx *OrderContext, event string) {
	switch event {
	case "deliver":
		fmt.Println("  -> 签收,转为已完成")
		ctx.SetState(CompletedState{})
	default:
		fmt.Println("  -> 已发货状态不处理此事件")
	}
}

// === 终态 ===
type CompletedState struct{}

func (CompletedState) String() string { return "已完成" }
func (CompletedState) Handle(ctx *OrderContext, event string) {
	fmt.Println("  -> 已完成,终态")
}

type CancelledState struct{}

func (CancelledState) String() string { return "已取消" }
func (CancelledState) Handle(ctx *OrderContext, event string) {
	fmt.Println("  -> 已取消,终态")
}

type RefundedState struct{}

func (RefundedState) String() string { return "已退款" }
func (RefundedState) Handle(ctx *OrderContext, event string) {
	fmt.Println("  -> 已退款,终态")
}

func main() {
	order := NewOrderContext("ORD-001", PendingState{})

	// 正常流程
	order.Trigger("pay")
	order.Trigger("ship")
	order.Trigger("deliver")
	order.Trigger("pay") // 终态,无效

	fmt.Println()

	// 异常流程
	order2 := NewOrderContext("ORD-002", PendingState{})
	order2.Trigger("cancel")
	order2.Trigger("pay") // 终态,无效
}

关键点:

  • 每个 State 实现自己的 Handle,根据事件决定是否转换状态。
  • OrderContext 只持有当前状态,把事件委托给状态处理。
  • 状态转换由状态自己决定(ctx.SetState(...)),客户端不感知——这是状态模式与策略模式的本质区别。

4. 状态机实现

上面的写法把状态转换逻辑分散在各个状态里。如果转换规则复杂,可以把规则集中到一个状态机表里:

go
package main

import "fmt"

// 状态和事件用字符串表示,便于配置化
type State string
type Event string

type Transition struct {
	From  State
	Event Event
	To    State
	Action func()
}

type StateMachine struct {
	current     State
	transitions []Transition
}

func NewStateMachine(initial State) *StateMachine {
	return &StateMachine{current: initial}
}

func (m *StateMachine) AddTransition(from State, event Event, to State, action func()) {
	m.transitions = append(m.transitions, Transition{from, event, to, action})
}

func (m *StateMachine) Trigger(event Event) {
	for _, t := range m.transitions {
		if t.From == m.current && t.Event == event {
			if t.Action != nil {
				t.Action()
			}
			fmt.Printf("  %s --%s--> %s\n", t.From, t.Event, t.To)
			m.current = t.To
			return
		}
	}
	fmt.Printf("  %s 状态下不接受事件 %s\n", m.current, event)
}

func (m *StateMachine) Current() State { return m.current }

func main() {
	sm := NewStateMachine("待支付")
	sm.AddTransition("待支付", "pay", "已支付", func() {
		fmt.Println("  动作:扣款")
	})
	sm.AddTransition("待支付", "cancel", "已取消", func() {
		fmt.Println("  动作:释放库存")
	})
	sm.AddTransition("已支付", "ship", "已发货", func() {
		fmt.Println("  动作:通知物流")
	})
	sm.AddTransition("已支付", "refund", "已退款", func() {
		fmt.Println("  动作:退款")
	})
	sm.AddTransition("已发货", "deliver", "已完成", func() {
		fmt.Println("  动作:积分")
	})

	events := []Event{"pay", "ship", "deliver", "cancel"}
	for _, e := range events {
		fmt.Printf("事件 %s:\n", e)
		sm.Trigger(e)
	}
	fmt.Println("最终状态:", sm.Current())
}

表驱动状态机的优点:

  • 转换规则集中,一目了然。
  • 易于从配置文件加载(JSON/YAML)。
  • 易于扩展,加状态/事件只需加一行。
  • 转换与动作分离,清晰。

5. 实战示例:订单状态机(完整版)

下面把订单状态机和事件通知结合,模拟一个真实的订单流转:

go
package main

import (
	"fmt"
	"sync"
)

type OrderState string

const (
	StatePending   OrderState = "待支付"
	StatePaid      OrderState = "已支付"
	StateShipped   OrderState = "已发货"
	StateCompleted OrderState = "已完成"
	StateCancelled OrderState = "已取消"
)

type OrderEvent string

const (
	EventPay     OrderEvent = "pay"
	EventShip    OrderEvent = "ship"
	EventDeliver OrderEvent = "deliver"
	EventCancel  OrderEvent = "cancel"
)

type Order struct {
	ID    string
	state OrderState
	mu    sync.Mutex
}

type TransitionRule struct {
	From    OrderState
	Event   OrderEvent
	To      OrderState
	Handler func(*Order) error
}

var rules = []TransitionRule{
	{StatePending, EventPay, StatePaid, func(o *Order) error {
		fmt.Printf("  [%s] 扣款成功\n", o.ID)
		return nil
	}},
	{StatePending, EventCancel, StateCancelled, func(o *Order) error {
		fmt.Printf("  [%s] 释放库存\n", o.ID)
		return nil
	}},
	{StatePaid, EventShip, StateShipped, func(o *Order) error {
		fmt.Printf("  [%s] 通知物流\n", o.ID)
		return nil
	}},
	{StatePaid, EventCancel, StateCancelled, func(o *Order) error {
		fmt.Printf("  [%s] 退款\n", o.ID)
		return nil
	}},
	{StateShipped, EventDeliver, StateCompleted, func(o *Order) error {
		fmt.Printf("  [%s] 发放积分\n", o.ID)
		return nil
	}},
}

func NewOrder(id string) *Order {
	return &Order{ID: id, state: StatePending}
}

func (o *Order) Trigger(event OrderEvent) error {
	o.mu.Lock()
	defer o.mu.Unlock()

	for _, r := range rules {
		if r.From == o.state && r.Event == event {
			fmt.Printf("[%s] %s --%s--> %s\n", o.ID, r.From, r.Event, r.To)
			if r.Handler != nil {
				if err := r.Handler(o); err != nil {
					return err
				}
			}
			o.state = r.To
			return nil
		}
	}
	return fmt.Errorf("[%s] 非法转换: %s + %s", o.ID, o.state, event)
}

func (o *Order) State() OrderState {
	o.mu.Lock()
	defer o.mu.Unlock()
	return o.state
}

func main() {
	order := NewOrder("ORD-001")
	events := []OrderEvent{EventPay, EventShip, EventDeliver, EventCancel}
	for _, e := range events {
		if err := order.Trigger(e); err != nil {
			fmt.Println("错误:", err)
		}
	}
	fmt.Println("最终状态:", order.State())

	fmt.Println()
	// 异常流程:已发货后想取消
	order2 := NewOrder("ORD-002")
	for _, e := range []OrderEvent{EventPay, EventShip, EventCancel} {
		if err := order2.Trigger(e); err != nil {
			fmt.Println("错误:", err)
		}
	}
}

这个版本加了:

  • sync.Mutex 保证并发安全。
  • Handler 返回 error,失败则不转换状态。
  • rules 是全局的转换规则表,可配置化。
  • 非法转换返回 error,保护状态机一致性。

三、职责链 vs 状态

两者都涉及「把逻辑分散到多个对象」,区别:

维度职责链状态
关注点请求 谁来处理状态 怎么变
处理方式沿链传递,可能无人处理当前状态决定行为
是否改变自身通常不改(无状态处理器)改变自身状态
链结构显式(next 指针)隐式(状态转换图)
客户端知道链的入口只触发事件

四、小结

  • 职责链让多个处理器依次尝试处理请求,能处理就处理,不能就传给下一个。Go 的 HTTP 中间件 func(http.Handler) http.Handler 是职责链的典型应用。
  • 职责链有「传递」和「中断」两种模式:中间件可以选择调用 next(继续)或不调用(中断)。
  • BaseHandler 提供 SetNext 默认实现,具体处理器通过嵌入获得,是 Go 模拟抽象基类的常用技巧。
  • 状态模式让对象在状态改变时改变行为,状态转换由状态自己决定(与策略模式的「客户端选」相对)。
  • Go 实现状态模式有两种:分散式(每个状态一个结构体,自己处理转换)和集中式(表驱动状态机,规则集中)。
  • 表驱动状态机适合复杂场景:转换规则集中、可配置、易扩展、易测试。
  • 实战中状态机要加锁保证并发安全,转换失败要返回 error 不改状态,防止非法转换破坏一致性。
  • 职责链关注「谁来处理」,状态关注「怎么变」——前者是请求路由,后者是状态流转。

下一篇讲迭代器与组合模式,看 Go 的 range 如何替代显式迭代器,以及如何用组合模式处理树形结构。