跳到主要内容

装饰器模式

问题

Go 中如何实现装饰器模式?和中间件模式有什么关系?

答案

函数装饰器

// 装饰器:给函数添加日志
func WithLogging(fn func(ctx context.Context) error) func(ctx context.Context) error {
return func(ctx context.Context) error {
log.Println("开始执行")
start := time.Now()
err := fn(ctx)
log.Printf("执行完成,耗时: %v, err: %v", time.Since(start), err)
return err
}
}

// 装饰器:添加重试
func WithRetry(fn func(ctx context.Context) error, maxRetries int) func(ctx context.Context) error {
return func(ctx context.Context) error {
var err error
for i := 0; i <= maxRetries; i++ {
if err = fn(ctx); err == nil {
return nil
}
time.Sleep(time.Second * time.Duration(i+1))
}
return err
}
}

// 组合装饰器
handler := WithLogging(WithRetry(doSomething, 3))
handler(ctx)

HTTP 中间件就是装饰器

type Middleware func(http.Handler) http.Handler

func Logging(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
log.Printf("%s %s", r.Method, r.URL.Path)
next.ServeHTTP(w, r) // 调用被装饰的 Handler
})
}

func Auth(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.Header.Get("Authorization") == "" {
http.Error(w, "unauthorized", 401)
return
}
next.ServeHTTP(w, r)
})
}

// 层层包装 = 装饰器链
handler := Logging(Auth(actualHandler))

接口装饰器

// 装饰 io.Writer
type CountWriter struct {
w io.Writer
count int64
}

func NewCountWriter(w io.Writer) *CountWriter {
return &CountWriter{w: w}
}

func (cw *CountWriter) Write(p []byte) (int, error) {
n, err := cw.w.Write(p) // 委托给原始 Writer
cw.count += int64(n) // 额外功能:统计字节数
return n, err
}

// 使用
file, _ := os.Create("output.txt")
cw := NewCountWriter(file)
io.WriteString(cw, "hello")
fmt.Println(cw.count) // 5

常见面试问题

Q1: Go 的装饰器和 Java/Python 装饰器有什么不同?

答案

  • Java:通过类继承/包装实现,如 BufferedReader(new FileReader(...))
  • Python@decorator 语法糖
  • Go:函数包装 + 接口包装,没有语法糖但同样简洁

Go 的优势是一等函数使函数装饰器写起来特别自然。

Q2: 标准库中有哪些装饰器?

答案

  • io.LimitReader(r, n):限制读取字节数
  • bufio.NewReader(r):添加缓冲
  • http.TimeoutHandler(h, dt, msg):添加超时
  • gzip.NewWriter(w):添加压缩

相关链接