前言

装饰器模式 是实现功能解耦和动态扩展的核心设计模式,而 洋葱模型 作为装饰器模式的链式进阶,是 Web 框架中间件、RPC 拦截器的底层核心。

本文介绍用 Go 实现支持 context.Context 上下文传递的装饰器模式(包括函数式和接口式两种实现),并进阶实现洋葱模型


一、装饰器模式

模式定义

装饰器模式是一种结构型设计模式,核心原则:不修改原有代码逻辑,动态为对象/函数添加额外功能。 它遵循开放封闭原则:对扩展开放,对修改关闭。

核心特性

  1. 动态增强:运行期为目标添加前置/后置逻辑
  2. 无侵入性:不改动核心业务代码
  3. 可组合:多个装饰器链式叠加,自由组合功能
  4. 上下文透传:配合 context.Context 实现全链路数据传递(超时、请求ID、用户信息等)

适用场景

  • 日志打印、耗时统计
  • 权限校验、参数校验
  • 缓存、重试、限流熔断
  • 全链路上下文管理

二、Go 实现装饰器模式(带 Context 上下文)

函数式装饰器

Go 没有类继承,通过函数包装实现装饰器是最简洁的方案。 为装饰器加入 context.Context,实现上下文全链路传递(核心需求)。

设计思路

  1. 定义统一的业务函数类型(强制携带 Context)
  2. 编写核心业务函数(无任何增强逻辑)
  3. 编写装饰器函数:接收原函数 → 返回包装后的新函数
  4. 装饰器内部通过 Context 传递/读取数据,实现全链路交互

函数式装饰器代码示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
package main

import (
	"context"
	"fmt"
	"time"
)

// 定义业务函数类型:携带 context.Context,支持上下文传递
// 这是装饰器的统一接口
type BusinessFunc func(ctx context.Context, name string) string

// 原始核心业务函数:仅实现核心逻辑,无增强代码
func SayHello(ctx context.Context, name string) string {
	// 从上下文读取中间件传递的数据
	reqID, _ := ctx.Value("req_id").(string)
	user, _ := ctx.Value("user").(string)
	fmt.Printf("[核心业务] 请求ID:%s | 操作用户:%s | 执行核心逻辑\n", reqID, user)
	return fmt.Sprintf("你好,%s!", name)
}

// 装饰器1:日志装饰器(携带上下文)
func LogDecorator(f BusinessFunc) BusinessFunc {
	return func(ctx context.Context, name string) string {
		// 前置增强:打印请求日志
		reqID := ctx.Value("req_id").(string)
		fmt.Printf("[日志装饰器] 请求ID:%s | 开始调用,参数:%s\n", reqID, name)

		// 调用原函数(透传上下文)
		result := f(ctx, name)

		// 后置增强:打印响应日志
		fmt.Printf("[日志装饰器] 请求ID:%s | 调用结束,结果:%s\n", reqID, result)
		return result
	}
}

// 装饰器2:计时装饰器(携带上下文)
func TimeDecorator(f BusinessFunc) BusinessFunc {
	return func(ctx context.Context, name string) string {
		start := time.Now()
		reqID := ctx.Value("req_id").(string)

		// 调用原函数(透传上下文)
		result := f(ctx, name)

		// 后置增强:打印耗时
		fmt.Printf("[计时装饰器] 请求ID:%s | 执行耗时:%s\n", reqID, time.Since(start))
		return result
	}
}

// 装饰器3:上下文初始化装饰器(往ctx存入数据,供下游使用)
func ContextDecorator(f BusinessFunc) BusinessFunc {
	return func(ctx context.Context, name string) string {
		// 给上下文添加请求ID、用户信息(全链路透传)
		ctx = context.WithValue(ctx, "req_id", "REQ_123456")
		ctx = context.WithValue(ctx, "user", "admin")
		fmt.Println("[上下文装饰器] 初始化上下文完成")

		return f(ctx, name)
        // 无后置增强
	}
}

func main() {
	// 链式装饰:顺序 = 上下文装饰 → 日志装饰 → 计时装饰
	decoratedFunc := ContextDecorator(LogDecorator(TimeDecorator(SayHello)))

	// 根上下文
	ctx := context.Background()
	// 执行增强后的函数
	res := decoratedFunc(ctx, "张三")
	fmt.Println("\n最终返回结果:", res)
}

运行结果

注意每层装饰器中被装饰函数 BusinessFunc 的调用位置,后面的代码是被装饰函数返回后再执行的

1
2
3
4
5
6
7
[上下文装饰器] 初始化上下文完成
[日志装饰器] 请求ID:REQ_123456 | 开始调用,参数:张三
[核心业务] 请求ID:REQ_123456 | 操作用户:admin | 执行核心逻辑
[计时装饰器] 请求ID:REQ_123456 | 执行耗时:22.083µs
[日志装饰器] 请求ID:REQ_123456 | 调用结束,结果:你好,张三!

最终返回结果: 你好,张三!

执行流程

    graph TD
    A[调用 decoratedFunc] --> B[ContextDecorator 注入上下文]
    B --> C[调用 LogDecorator]
    C --> D[打印前置日志]
    D --> F[调用 TimeDecorator]
    F --> G[记录开始时间]
    G --> H[调用核心业务 SayHello]
    H --> I[返回TimeDecorator执行后置代码]
    I --> J[返回LogDecorator执行后置代码]
    J --> K[返回最终结果]

接口式装饰器

除了函数式装饰器,Go 中还可以通过接口实现装饰器模式,这种方式更适合面向对象的场景。

接口式装饰器设计思路

  1. 定义业务接口
  2. 实现基础业务结构体
  3. 实现装饰器结构体(包装基础业务)
  4. 通过组合实现功能增强

接口式装饰器示例代码

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106

package main

import (
	"context"
	"fmt"
	"time"
)

// 1. 定义业务接口
type Service interface {
	Process(ctx context.Context, input string) (string, error)
}

// 2. 基础业务实现
type BasicService struct{}

func (s *BasicService) Process(ctx context.Context, input string) (string, error) {
	reqID := ctx.Value("req_id").(string)
	user := ctx.Value("user").(string)

	fmt.Printf("[基础服务] 请求ID:%s | 用户:%s | 处理输入:%s\n",
		reqID, user, input)

	// 业务处理
	time.Sleep(10 * time.Millisecond)
	return fmt.Sprintf("业务参数:%s", input), nil
}

// 3. 日志装饰器(接口式)
type LogDecorator struct {
	service Service
}

func (d *LogDecorator) Process(ctx context.Context, input string) (string, error) {
	reqID := ctx.Value("req_id").(string)

	// 前置日志
	fmt.Printf("[日志装饰器] 请求ID:%s | 开始处理\n", reqID)

	start := time.Now()
	result, err := d.service.Process(ctx, input)
	duration := time.Since(start)

	// 后置日志
	fmt.Printf("[日志装饰器] 请求ID:%s | 处理完成 | 耗时:%v\n",
		reqID, duration)

	return result, err
}

// 4. 权限装饰器(接口式)
type AuthDecorator struct {
	service Service
}

func (d *AuthDecorator) Process(ctx context.Context, input string) (string, error) {
	reqID := ctx.Value("req_id").(string)
	user := ctx.Value("user").(string)

	// 权限检查
	if user == "" {
		return "", fmt.Errorf("请求ID %s: 用户未认证", reqID)
	}

	fmt.Printf("[权限装饰器] 请求ID:%s | 用户 %s 权限校验通过\n",
		reqID, user)

	return d.service.Process(ctx, input)
}

// 5. 上下文装饰器(接口式)
type ContextDecorator struct {
	service Service
}

func (d *ContextDecorator) Process(ctx context.Context, input string) (string, error) {
	// 注入请求级数据
	ctx = context.WithValue(ctx, "req_id", "REQ_"+time.Now().Format("150405"))
	ctx = context.WithValue(ctx, "user", "admin")

	fmt.Println("[上下文装饰器] 初始化全链路数据")

	return d.service.Process(ctx, input)
}

func main() {
	// 创建基础服务
	var service Service = &BasicService{}

	// 链式装饰:上下文 → 权限 → 日志 → 基础服务
	service = &ContextDecorator{service: &AuthDecorator{service: &LogDecorator{service: &BasicService{}}}}
    // 等价于
    // service = &LogDecorator{service: service}
	// service = &AuthDecorator{service: service}
	// service = &ContextDecorator{service: service}

	ctx := context.Background()
	result, err := service.Process(ctx, "test_input")

	if err != nil {
		fmt.Printf("错误:%v\n", err)
	} else {
		fmt.Printf("结果:%s\n", result)
	}
}

运行结果

1
2
3
4
5
6
[上下文装饰器] 初始化全链路数据
[权限装饰器] 请求ID:REQ_140913 | 用户 admin 权限校验通过
[日志装饰器] 请求ID:REQ_140913 | 开始处理
[基础服务] 请求ID:REQ_140913 | 用户:admin | 处理输入:test_input
[日志装饰器] 请求ID:REQ_140913 | 处理完成 | 耗时:11.032334ms
结果:业务参数:test_input

接口式装饰器特点

  • 面向对象:通过结构体组合实现装饰器
  • 类型安全:编译时检查接口实现
  • 易于扩展:新增装饰器只需实现相同接口

洋葱模型:装饰器的链式进阶(带 Context)

洋葱模型核心原理

其实前面的示例中就已经包含了洋葱模型,洋葱模型是多层装饰器的标准编排模式,执行流程: 请求从外层进入 → 层层穿透到核心业务 → 响应从核心层层向外退出

配合 context.Context,可实现全链路上下文共享(请求ID、超时、用户信息贯穿所有层)。

执行流程图解

    graph LR
    A[请求进入] --> B[外层中间件 前置逻辑]
    B --> C[内层中间件 前置逻辑]
    C --> D[核心业务]
    D --> E[内层中间件 后置逻辑]
    E --> F[外层中间件 后置逻辑]
    F --> G[响应返回]
    
    style B fill:#e1f5fe
    style C fill:#f3e5f5
    style D fill:#e8f5e8
    style E fill:#f3e5f5
    style F fill:#e1f5fe

Go 实现带 Context 的洋葱模型

这里用常见的中间件写法实现一个简单的示例

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
package main

import (
	"context"
	"fmt"
)

// 1. 核心处理器:携带Context,业务最终执行点
type Handler func(ctx context.Context)

// 2. 中间件类型:洋葱模型的核心,接收Handler,返回新Handler(带Context)
type Middleware func(next Handler) Handler

// 3. 中间件1:请求日志(外层洋葱)
func LoggerMiddleware(next Handler) Handler {
	return func(ctx context.Context) {
		// 前置逻辑:进入
		reqID := ctx.Value("req_id").(string)
		fmt.Printf("→ [日志中间件] 请求ID:%s | 接收请求\n", reqID)

		// 调用下一层(穿透到内层)
		next(ctx)

		// 后置逻辑:退出
		fmt.Printf("← [日志中间件] 请求ID:%s | 处理完成\n", reqID)
	}
}

// 4. 中间件2:权限校验(内层洋葱)
func AuthMiddleware(next Handler) Handler {
	return func(ctx context.Context) {
		reqID := ctx.Value("req_id").(string)
		user := ctx.Value("user").(string)

		// 前置逻辑:权限校验
		fmt.Printf("  → [权限中间件] 请求ID:%s | 校验用户:%s 权限 ✅\n", reqID, user)

		// 调用核心业务
		next(ctx)

		// 后置逻辑
		fmt.Printf("  ← [权限中间件] 请求ID:%s | 权限校验结束\n", reqID)
	}
}

// 5. 中间件3:上下文初始化(最外层,注入公共数据)
func ContextMiddleware(next Handler) Handler {
	return func(ctx context.Context) {
		// 初始化上下文:注入请求ID、用户、超时时间
		ctx = context.WithValue(ctx, "req_id", "REQ_789012")
		ctx = context.WithValue(ctx, "user", "test_user")
		fmt.Println("[上下文中间件] 初始化全链路上下文")

		next(ctx)
	}
}

// 6. 核心业务逻辑(洋葱最内层)
func coreBusiness(ctx context.Context) {
	reqID := ctx.Value("req_id").(string)
	user := ctx.Value("user").(string)
	fmt.Printf("    ✅ [核心业务] 请求ID:%s | 用户:%s | 执行核心逻辑\n", reqID, user)
}

// 7. 洋葱组装函数:将中间件按顺序包装成最终Handler
func compose(handler Handler, middlewares ...Middleware) Handler {
	// 倒序包装:从内到外,层层包裹
	for i := len(middlewares) - 1; i >= 0; i-- {
		handler = middlewares[i](handler)
	}
	return handler
}

func main() {
	// 组装洋葱:顺序 = 上下文 → 日志 → 权限
	finalHandler := compose(coreBusiness, ContextMiddleware, LoggerMiddleware, AuthMiddleware)

	// 根上下文
	ctx := context.Background()

	finalHandler(ctx)
}

洋葱模型运行结果

1
2
3
4
5
6
[上下文中间件] 初始化全链路上下文
→ [日志中间件] 请求ID:REQ_789012 | 接收请求
  → [权限中间件] 请求ID:REQ_789012 | 校验用户:test_user 权限 ✅
    ✅ [核心业务] 请求ID:REQ_789012 | 用户:test_user | 执行核心逻辑
  ← [权限中间件] 请求ID:REQ_789012 | 权限校验结束
← [日志中间件] 请求ID:REQ_789012 | 处理完成

洋葱模型执行流程(序列图)

    sequenceDiagram
    participant A as 客户端
    participant CM as ContextMiddleware
    participant LM as LoggerMiddleware
    participant AM as AuthMiddleware
    participant CB as CoreBusiness
    
    A->>CM: 请求到达
    Note over CM: 初始化上下文数据
    CM->>LM: 调用下一层
    Note over LM: 前置日志
    LM->>AM: 调用下一层
    Note over AM: 权限校验
    AM->>CB: 调用核心业务
    Note over CB: 执行业务逻辑
    CB-->>AM: 返回结果
    Note over AM: 权限校验后置
    AM-->>LM: 返回结果
    Note over LM: 后置日志
    LM-->>CM: 返回结果
    CM-->>A: 返回最终响应

应用场景

  • Web 框架中间件:Gin/Beego 中间件底层就是洋葱模型 + Context

  • RPC 服务拦截器:gRPC 拦截器、微服务全链路监控

  • 全链路监控:通过 Context 传递 TraceID、请求ID,实现日志串联

  • 通用切面功能:超时控制、限流、熔缓存统一处理


总结

遵循开闭原则,解耦核心业务与通用功能,代码更易维护、扩展

核心概念

  1. 装饰器模式:Go 支持函数式接口式,配合 Context 实现上下文传递
  2. 洋葱模型:装饰器的链式编排,先进后出,是中间件的标准实现

选择建议

  • 简单场景:使用函数式装饰器,更轻量、更符合Go惯用法
  • 复杂对象:使用接口式装饰器,类型安全、易于扩展
  • Web框架:使用洋葱模型实现中间件

掌握这两种模式能轻松实现通用切面逻辑,写出更优雅、更健壮的代码!