代理对象必须实现与真实对象相同的接口,否则无法透明替换;应使用sync.Once保证延迟初始化的并发安全;代理可组合但需职责分明,避免过度嵌套;HTTP反向代理与代理模式无关。

代理对象必须实现与真实对象相同的接口
Go 没有类继承,靠接口契约约束行为。如果 RealSubject 和 Proxy 不共用同一个接口,调用方就无法透明替换——这直接破坏代理模式的核心目标:控制访问、延迟初始化或添加横切逻辑。
常见错误是让 Proxy 只“包裹”真实对象却不实现接口,结果调用方只能写 proxy.DoSomething() 而不是面向接口编程。正确做法是定义如下的 Subject 接口:
type Subject interface {
DoWork() string
}
然后确保 RealSubject 和 Proxy 都实现它:
type RealSubject struct{}
func (r *RealSubject) DoWork() string {
return "real work done"
}
type Proxy struct {
real *RealSubject
}
func (p *Proxy) DoWork() string {
if p.real == nil {
p.real = &RealSubject{}
}
return p.real.DoWork()
}
延迟初始化时注意并发安全
代理常用于懒加载(lazy init),但多个 goroutine 同时首次调用 DoWork() 会导致重复初始化 RealSubject,甚至引发数据竞争。
立即学习“go语言免费学习笔记(深入)”;
不要只靠简单判空:
if p.real == nil {
p.real = &RealSubject{} // ⚠️ 非线程安全
}
应使用 sync.Once 或 sync.Mutex。推荐 sync.Once,轻量且语义清晰:
type Proxy struct {
real *RealSubject
once sync.Once
}
func (p *Proxy) DoWork() string {
p.once.Do(func() {
p.real = &RealSubject{}
})
return p.real.DoWork()
}
-
sync.Once保证Do内函数仅执行一次,无论多少 goroutine 并发触发 - 避免在
DoWork中加锁整个方法体——那会严重拖慢吞吐 - 若初始化逻辑需传参或返回错误,
sync.Once不够用,得改用带锁的 lazy-init 模式
代理可组合多种行为,但别过度嵌套
一个 Proxy 可同时做日志、权限校验、缓存、重试等事,但每层职责要分明。比如:
type LoggingProxy struct {
next Subject
}
func (l *LoggingProxy) DoWork() string {
log.Println("before")
res := l.next.DoWork()
log.Println("after")
return res
}
type AuthProxy struct {
next Subject
user string
}
func (a *AuthProxy) DoWork() string {
if a.user != "admin" {
return "access denied"
}
return a.next.DoWork()
}
组合时顺序很重要:AuthProxy{next: LoggingProxy{next: real}} 表示先鉴权再打日志;反过来就可能在未授权时也输出了 “before”。
- 组合建议用构造函数封装,避免手动 new 多层嵌套出错
- 不要让一个代理既做缓存又做限流又做熔断——拆成独立代理更易测、易复用
- 每个代理应只依赖
Subject接口,不感知下一层具体类型
HTTP 反向代理不是 Go 标准库的 net/http/httputil.NewSingleHostReverseProxy
很多人搜“Golang 代理模式”却误入 HTTP 反向代理场景。这是两类东西:net/http/httputil 是网络层代理,而 Proxy Pattern 是面向对象设计模式,解决的是程序内对象访问控制问题。
如果你实际想做的是转发 HTTP 请求(比如网关),那应该用:
import "net/http/httputil"
proxy := httputil.NewSingleHostReverseProxy(&url.URL{
Scheme: "http",
Host: "localhost:8081",
})
http.ListenAndServe(":8080", proxy)
但这和你定义 Subject 接口、写 Proxy 结构体完全无关。混淆这两者会导致架构分层错乱——业务逻辑层不该混入 HTTP transport 细节。
真正难的是:当你要代理一个已有结构体(比如第三方库的 Client)且它没提供接口时,你得自己抽象出接口,再写适配器 + 代理。这个环节最容易被跳过,结果就是代理变成硬编码耦合,失去扩展性。










