代理模式¶
分类:结构型模式。
类结构¶
classDiagram
direction TB
class Client
class Subject {
<<interface>>
+request()
}
class RealSubject {
+request()
}
class Proxy {
+request()
}
Subject <|.. RealSubject
Subject <|.. Proxy
Proxy --> RealSubject : 委托
Client ..> Subject : 使用
角色定义¶
| 角色 | 定义 |
|---|---|
| Client(客户端) | 仅依赖主题接口发起请求,使用代理时无需改变调用方式。 |
| Subject(主题接口) | 定义代理与真实主题共同实现的请求契约。 |
| RealSubject(真实主题) | 实现主题接口,执行实际业务操作。 |
| Proxy(代理) | 实现主题接口,持有真实主题并控制对它的访问,在需要时委托请求;可承担延迟加载、权限检查或缓存等职责。 |
Head First 示例:远程查看糖果机库存与状态¶
《Head First 设计模式》第 11 章让糖果机监控端跨进程查询不同地点的机器,而无需直接持有服务端的本地机器对象。这里核对的是作者 2020 年第二版配套代码 proxy/gumball:GumballMachineRemote 定义远程查询契约,GumballMachine 实现契约,GumballMonitor 只持有这个接口并打印位置、库存与当前状态。
服务端通过 Naming.rebind() 注册机器,监控端通过 Naming.lookup() 取得远程引用。代理承担网络调用的转发,由 RMI 基础设施提供;现代 RMI 可以使用运行时生成的动态代理,这个源码目录没有供本节绘制的作者手写具体桩类。图中因此保留真实接口与持有关系,通过文字说明代理所处的位置。
| 具体类名 | 对应角色 | 职责 |
|---|---|---|
GumballMachineRemote |
远程主题接口 | 继承 Remote,声明 getCount()、getLocation()、getState(),各方法允许抛出 RemoteException。 |
GumballMachine |
真实主题 | 继承 UnicastRemoteObject 并实现远程接口,保存位置、库存及当前状态,执行服务端查询。 |
GumballMonitor |
客户端 | 保存 GumballMachineRemote machine,通过 report() 发起查询并处理远程异常。 |
State |
可序列化的状态接口 | 继承 Serializable,定义机器状态操作;具体状态对象作为查询结果传回监控端。 |
GumballMachineTestDrive |
服务端入口 | 根据参数创建机器,并将其绑定到 RMI 注册表名称。 |
GumballMonitorTestDrive |
监控端入口 | 查找远程引用,创建监控对象并调用 report()。 |
示例 UML¶
classDiagram
direction TB
class GumballMachineRemote {
<<interface>>
+getCount() int
+getLocation() String
+getState() State
}
class GumballMachine {
String location
int count
State state
+getCount() int
+getLocation() String
+getState() State
}
class GumballMonitor {
GumballMachineRemote machine
+GumballMonitor(GumballMachineRemote machine)
+report()
}
class State {
<<interface>>
+insertQuarter()
+ejectQuarter()
+turnCrank()
+dispense()
}
class GumballMachineTestDrive {
+main(String[] args)$
}
class GumballMonitorTestDrive {
+main(String[] args)$
}
GumballMachineRemote <|.. GumballMachine
GumballMonitor --> GumballMachineRemote : machine 远程引用
GumballMachine --> State : state 当前状态
GumballMachineRemote ..> State : getState 返回
GumballMachineTestDrive ..> GumballMachine : 创建并注册
GumballMonitorTestDrive ..> GumballMachineRemote : 查找远程引用
GumballMonitorTestDrive ..> GumballMonitor : 创建并调用
图中省略 Remote、UnicastRemoteObject、Serializable 等库类型、RMI 注册表与传输细节,以及五个具体状态类。GumballMonitor 中的 machine 在远程场景下指向实现接口的代理引用,不是直接跨进程持有实体。getState() 返回可序列化的状态副本;具体状态类的 gumballMachine 字段标记为 transient,不会随返回值把机器引用一同序列化。
协作过程¶
- 在 RMI 注册表可用的前提下,
GumballMachineTestDrive用主机名和库存参数创建GumballMachine,通过Naming.rebind()将它注册为gumballmachine。 GumballMonitorTestDrive根据主机名构造地址,用Naming.lookup()取得GumballMachineRemote引用,并将它传入GumballMonitor。report()依次调用getLocation()、getCount()、getState();RMI 代理转发这些请求,由服务端的GumballMachine返回相应信息。- 监控端打印查询结果,其中状态对象通过序列化按值传回;远程调用发生
RemoteException时,由report()的异常处理分支处理。
该固定版本的 GumballMonitorTestDrive 使用 args.length >= 0 判断后读取 args[0],因此需要提供主机名参数,不能把源码中的三个预置地址理解为无参数运行时可用的回退路径。这里说明调用协作关系,不执行或修补配套程序。
固定版本源码:
- GumballMachineRemote:远程查询接口
- GumballMachine:远程接口的服务端实现
- GumballMonitor:依赖远程接口的监控端
- GumballMonitorTestDrive:远程查找与监控入口