应使用 interface。Go 代理模式依赖接口定义行为契约(如 Service 接口),所有真实服务和代理均实现它,确保类型安全、可替换、易 mock 和可组合;struct 仅用于具体实现。

代理对象该用 struct 还是 interface?
Go 没有传统 OOP 的“类继承”,代理模式不能靠继承实现,必须靠组合 + 接口抽象。关键不是“怎么写代理”,而是“先定义好被代理者的行为契约”。
所以第一步永远是定义 Service 接口(或类似命名),比如:
type Service interface {
DoSomething(ctx context.Context, req *Request) (*Response, error)
}
所有真实服务和代理都要实现它。否则后续的替换、装饰、拦截都会失去类型安全。
常见错误:直接对具体 struct 做代理(如 *HTTPClient),结果无法统一管理、难 mock、难加中间逻辑。
如何在代理中透传并增强请求?
典型场景是加日志、超时、重试、熔断——这些都发生在调用前后,且需访问原始参数和返回值。核心是让代理持有一个 Service 字段,并在方法里调用它:
type LoggingProxy struct {
next Service
}
func (p *LoggingProxy) DoSomething(ctx context.Context, req *Request) (*Response, error) {
log.Printf("proxy: start DoSomething with ID=%s", req.ID)
defer log.Printf("proxy: end DoSomething")
return p.next.DoSomething(ctx, req)
}
注意点:
• 必须透传 ctx,否则超时/取消会失效
• 错误不能吞掉,除非你明确要兜底(如降级)
• 如果需要改请求(如加 header、签名),就 new 一个新 *Request 再传给 p.next
立即学习“go语言免费学习笔记(深入)”;
多个代理如何链式组装?
Go 里没有“自动 AOP”,但可以手动链式构造:后一个代理把前一个代理作为 next。顺序很重要,比如超时代理应包在重试代理外层,否则重试会受单次超时限制。
-
TimeoutProxy{next: RetryProxy{next: RealService{}}}✅ -
RetryProxy{next: TimeoutProxy{next: RealService{}}}❌(重试每次都被 timeout 中断)
建议用函数式构造器简化初始化:
func WithTimeout(s Service, timeout time.Duration) Service {
return &TimeoutProxy{next: s, timeout: timeout}
}
func WithRetry(s Service, max int) Service {
return &RetryProxy{next: s, maxRetries: max}
}
// 使用
svc := WithRetry(WithTimeout(&RealService{}, 5*time.Second), 3)
HTTP 客户端代理为什么不能直接包装 http.Client?
因为 http.Client 本身不是接口,它的 Do 方法不满足“可被统一代理”的契约。硬包会导致后续无法插入中间逻辑(比如修改 URL、记录响应体大小)。正确做法是封装一层业务接口:
type HTTPService interface {
GetUser(ctx context.Context, id string) (*User, error)
PostOrder(ctx context.Context, order *Order) error
}
type HTTPServiceImpl struct {
client *http.Client
baseURL string
}
func (s *HTTPServiceImpl) GetUser(ctx context.Context, id string) (*User, error) {
// 构造 request、发 HTTP、解析 response
}
然后对 HTTPService 做代理,而不是对 *http.Client。否则你会反复写重复的中间件逻辑,且无法做单元测试隔离。
真正容易被忽略的是:代理不是为“看起来像”而存在,是为让「行为可插拔」。一旦发现某个代理逻辑只用一次、或者所有方法都 copy-paste 同一套 wrapper,说明接口粒度太粗,该拆了。










