12. 代理模式(Proxy)

问题:想在访问对象时加一层控制(延迟加载、权限校验、缓存等)。

核心:代理对象和目标对象实现同样接口,代理内部控制对目标的访问。

interface DisplayableImage {
  display(): void;
}

class RealImage implements DisplayableImage {
  constructor(private filename: string) {
    this.loadFromDisk();
  }
  private loadFromDisk(): void { console.log(`从磁盘加载: ${this.filename}`); }
  display(): void { console.log(`显示: ${this.filename}`); }
}

class ProxyImage implements DisplayableImage {
  #realImage?: RealImage;
  constructor(private filename: string) {}
  display(): void {
    if (!this.#realImage) {
      this.#realImage = new RealImage(this.filename);
    }
    this.#realImage.display();
  }
}

const img = new ProxyImage('photo.jpg');
img.display(); // 从磁盘加载 → 显示(第一次,触发加载)
img.display(); // 显示(第二次,直接用缓存)
package proxy

import "fmt"

type Image interface {
  Display()
}

type RealImage struct {
  filename string
}

func NewRealImage(filename string) *RealImage {
  img := &RealImage{filename: filename}
  img.loadFromDisk()
  return img
}

func (r *RealImage) loadFromDisk() {
  fmt.Printf("从磁盘加载: %s\n", r.filename)
}

func (r *RealImage) Display() {
  fmt.Printf("显示: %s\n", r.filename)
}

type ProxyImage struct {
  filename  string
  realImage *RealImage
}

func NewProxyImage(filename string) *ProxyImage {
  return &ProxyImage{filename: filename}
}

func (p *ProxyImage) Display() {
  if p.realImage == nil {
    p.realImage = NewRealImage(p.filename)
  }
  p.realImage.Display()
}

实战场景

  • 图片懒加载:代理先占位,真正进入视口或被显示时才加载真实图片。
  • 权限代理:访问敏感资源前先检查登录状态、角色、租户范围,通过后才调用真实服务。
  • 缓存代理:查询接口前先查缓存,未命中再请求真实数据源,并把结果写回缓存。
  • 远程代理:本地对象看起来像普通 service,实际调用通过 HTTP、RPC 或消息队列转发到远程服务。
  • 虚拟代理:真实对象创建成本很高时,代理延迟初始化,例如大型模型、报表、编辑器实例。
  • 日志审计:对关键操作包一层代理,记录调用人、参数摘要、结果和耗时。
  • 限流代理:在调用真实接口前检查频率、配额或并发数,超限时直接拒绝。
  • 响应式代理:前端框架用代理拦截对象读写,收集依赖并触发更新。
  • 安全沙箱:插件或脚本不直接拿到真实对象,而是拿到受限代理,只暴露允许的能力。
  • Mock 代理:开发环境代理 API 请求,某些接口返回本地 mock,其他接口转发到真实后端。

ES6 的 Proxy 就是语言级别的代理支持。