模式补全

这一节按 八面剖析以理解一个概念 / ljg-learn 的思路整理为 Markdown 版本:先定锚,再用八个方向切开概念,最后压缩成公式、例子、类图和检验题。

定锚

代理模式(结构型)的通行定义:为真实对象提供一个替身,以控制访问、延迟加载、缓存、权限或远程调用。

常见误解:代理不是简单转发;代理的价值在于控制访问时机和访问规则。

核心词素:Proxy 是代行者。它站在真实对象之前,既像门卫,也像经纪人。

八刀

历史

它在远程对象、虚拟加载、安全控制中长期存在。现代前端里的 Proxy API 又让这个词更常见。

辩证

反面是客户端直接访问真实对象。更高理解是:访问本身也可以成为可设计的对象。

现象

图片懒加载时,页面先拿到占位图代理,真正图片等进入视口再加载。

语言

Proxy 是代行者。它站在真实对象之前,既像门卫,也像经纪人。

形式

Proxy implements Subject and has RealSubject。若代理加入过多业务,会让真实对象行为变得难预测。

存在

它让系统在对象前面加一道可控边界,访问不再是裸奔。

美感

它美在帘幕:真实对象在幕后,代理按需要拉开。

元反思

替身隐喻容易让人忽略透明性。换成“边界网关”隐喻,会更重视权限、缓存、监控等横切能力。

内观

我长得像真实对象。你以为在和它说话,其实先经过我。我决定是否、何时、怎样把请求交给它。

八刀共同指向的深层结构:代理模式不是为了“炫技”,而是在某个变化点上建立边界,让稳定部分继续稳定,让变化部分有自己的位置。

压缩

公式:代理 = 同接口替身 + 持有真实对象 + 访问控制

一句话:代理模式把对对象的访问变成一个可控制的边界。

结构图:

Client -> Proxy -> RealSubject

动机/意图

当直接访问真实对象成本高、风险大,或需要附加控制逻辑时,客户端不应直接触碰真实对象。代理模式的意图是提供一个和真实对象同接口的替身,在访问前后加入权限、缓存、延迟加载、远程调用等控制。

结构/角色

  • Subject:抽象主题,声明真实对象和代理共同实现的接口。
  • RealSubject:真实主题,包含核心业务逻辑。
  • Proxy:代理,持有或按需创建真实主题,并控制对它的访问。
  • Client:客户端面向主题接口调用,通常不知道自己使用的是代理。

典型 UML

classDiagram
    class Subject {
        <<interface>>
        +request(): void
    }
    class RealSubject {
        +request(): void
    }
    class Proxy {
        -realSubject: RealSubject
        +request(): void
    }
    Subject <|.. RealSubject
    Subject <|.. Proxy
    Proxy --> RealSubject

使用场景

  • 延迟加载重对象,例如图片、文档、远程连接。
  • 访问真实对象前需要权限校验、限流或审计。
  • 需要缓存真实对象的结果。
  • 本地对象代表远程服务,隐藏网络通信细节。

正例:TypeScript

class User {
  constructor(
    public readonly id: string,
    public readonly role: "guest" | "member",
  ) {}
}
 
class Post {
  constructor(
    public readonly id: string,
    public readonly authorId: string,
    public content: string,
  ) {}
}
 
interface Forum {
  viewPost(post: Post): void
  publishPost(content: string): void
  editProfile(profileText: string): void
  editPost(post: Post, newContent: string): void
}
 
class RealForum implements Forum {
  viewPost(post: Post) {
    console.log(`view post: ${post.content}`)
  }
 
  publishPost(content: string) {
    console.log(`publish post: ${content}`)
  }
 
  editProfile(profileText: string) {
    console.log(`edit profile: ${profileText}`)
  }
 
  editPost(post: Post, newContent: string) {
    post.content = newContent
    console.log(`edit post ${post.id}: ${post.content}`)
  }
}
 
class ForumProxy implements Forum {
  constructor(
    private readonly realForum: Forum,
    private readonly currentUser: User,
  ) {}
 
  viewPost(post: Post) {
    this.realForum.viewPost(post)
  }
 
  publishPost(content: string) {
    if (this.currentUser.role !== "member") {
      console.log("guest cannot publish post")
      return
    }
 
    this.realForum.publishPost(content)
  }
 
  editProfile(profileText: string) {
    if (this.currentUser.role !== "member") {
      console.log("guest cannot edit profile")
      return
    }
 
    this.realForum.editProfile(profileText)
  }
 
  editPost(post: Post, newContent: string) {
    if (this.currentUser.role !== "member") {
      console.log("guest cannot edit post")
      return
    }
 
    if (post.authorId !== this.currentUser.id) {
      console.log("cannot edit other user's post")
      return
    }
 
    this.realForum.editPost(post, newContent)
  }
}
 
const realForum = new RealForum()
const ownPost = new Post("post-1", "u100", "hello forum")
const otherPost = new Post("post-2", "u200", "other user's content")
 
const guest = new ForumProxy(realForum, new User("u001", "guest"))
guest.viewPost(ownPost)
guest.publishPost("I want to speak")
 
const member = new ForumProxy(realForum, new User("u100", "member"))
member.publishPost("new member post")
member.editProfile("new profile text")
member.editPost(ownPost, "updated by owner")
member.editPost(otherPost, "try to edit others")

正例:UML 类图

classDiagram
    class Forum {
        <<interface>>
        +viewPost(post: Post): void
        +publishPost(content: string): void
        +editProfile(profileText: string): void
        +editPost(post: Post, newContent: string): void
    }
    class RealForum {
        +viewPost(post: Post): void
        +publishPost(content: string): void
        +editProfile(profileText: string): void
        +editPost(post: Post, newContent: string): void
    }
    class ForumProxy {
        -realForum: Forum
        -currentUser: User
        +viewPost(post: Post): void
        +publishPost(content: string): void
        +editProfile(profileText: string): void
        +editPost(post: Post, newContent: string): void
    }
    class User {
        +id: string
        +role: string
    }
    class Post {
        +id: string
        +authorId: string
        +content: string
    }
    Forum <|.. RealForum
    Forum <|.. ForumProxy
    ForumProxy --> Forum
    ForumProxy --> User
    Forum --> Post

反例:TypeScript

class User {
  constructor(
    public readonly id: string,
    public readonly role: "guest" | "member",
  ) {}
}
 
class Post {
  constructor(
    public readonly id: string,
    public readonly authorId: string,
    public content: string,
  ) {}
}
 
class ForumService {
  viewPost(post: Post) {
    console.log(`view post: ${post.content}`)
  }
 
  publishPost(user: User, content: string) {
    if (user.role === "member") {
      console.log(`publish post: ${content}`)
      return
    }
 
    console.log("guest cannot publish post")
  }
 
  editProfile(user: User, profileText: string) {
    if (user.role === "member") {
      console.log(`edit profile: ${profileText}`)
      return
    }
 
    console.log("guest cannot edit profile")
  }
 
  editPost(user: User, post: Post, newContent: string) {
    if (user.role !== "member") {
      console.log("guest cannot edit post")
      return
    }
 
    if (post.authorId !== user.id) {
      console.log("cannot edit other user's post")
      return
    }
 
    post.content = newContent
    console.log(`edit post ${post.id}: ${post.content}`)
  }
}

反例:UML 类图

classDiagram
    class ForumService {
        +viewPost(post: Post): void
        +publishPost(user, content): void
        +editProfile(user, profileText): void
        +editPost(user, post, newContent): void
    }
    class User
    class Post
    ForumService --> User : permission checks
    ForumService --> Post : ownership checks
    ForumService ..> ForumService : mixed with permission logic

常见的几种代理

  • 远程代理:隐藏掉被代理对象的细节
  • 虚拟代理:代理当做真实的属性很多的被代理对象的虚拟对象,只提取用到的属性,以节约内存
  • 动态代理:

案例

在一个论坛中已注册用户和游客的权限不同,已注册的用户拥有发帖、修改自己的注册信息、修改自己的帖子等功能;而游客只能看到别人发的帖子,没有其他权限。使用代理模式来设计该权限管理模块。 在本实例中我们使用代理模式中的保护代理,该代理用于控制对一个对象的访问,可以给不同的用户提供不同级别的使用权限

模拟应用远程代理来访问另外一个应用程序域中的对象,如果在远程实现了加减乘除等运算,在本地需要调用,那么可以考虑在本地设置一个代理。

掌握检验

  1. 代理模式和装饰模式结构相似,意图有什么不同?
  2. 虚拟代理、保护代理、缓存代理分别控制什么?
  3. 代理怎样保持对客户端透明?