跳到主要内容

代理模式

问题

Go 中如何实现代理模式?有哪些常见应用?

答案

接口代理

type UserService interface {
GetUser(id int) (*User, error)
}

// 真实服务
type userServiceImpl struct {
db *sql.DB
}

func (s *userServiceImpl) GetUser(id int) (*User, error) {
// 查数据库
return queryUser(s.db, id)
}

// 缓存代理
type cachedUserService struct {
real UserService
cache *redis.Client
}

func (s *cachedUserService) GetUser(id int) (*User, error) {
// 先查缓存
key := fmt.Sprintf("user:%d", id)
cached, err := s.cache.Get(ctx, key).Result()
if err == nil {
var user User
json.Unmarshal([]byte(cached), &user)
return &user, nil
}

// 缓存未命中,查真实服务
user, err := s.real.GetUser(id)
if err != nil {
return nil, err
}

// 写入缓存
data, _ := json.Marshal(user)
s.cache.Set(ctx, key, data, time.Hour)
return user, nil
}

常见代理类型

类型说明示例
缓存代理缓存结果避免重复查询上面的例子
日志代理记录调用日志gRPC Interceptor
限流代理控制调用频率API 限流
懒加载代理延迟初始化重资源数据库连接

gRPC Interceptor 就是代理

// 一元 RPC 拦截器(日志代理)
func LoggingInterceptor(
ctx context.Context,
req interface{},
info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler,
) (interface{}, error) {
start := time.Now()
resp, err := handler(ctx, req) // 调用真实 Handler
log.Printf("method=%s duration=%v err=%v", info.FullMethod, time.Since(start), err)
return resp, err
}

server := grpc.NewServer(grpc.UnaryInterceptor(LoggingInterceptor))

常见面试问题

Q1: Go 没有动态代理(Java Proxy),怎么办?

答案

  • 手动实现:如上面的接口代理,写起来并不多
  • 代码生成:mockgen、Wire 等工具自动生成代理代码
  • gRPC Interceptor:框架层面的 AOP 能力

Go 哲学偏好显式代码而非运行时反射,手动实现更清晰。

Q2: 代理模式 vs 装饰器模式?

答案

  • 代理控制访问(缓存、权限、限流),客户端不知道有代理
  • 装饰器增强功能(添加日志、计时),客户端主动选择装饰

实现方式相同(都是接口包装),区别在于意图。

相关链接