装饰器模式
问题
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):添加压缩