跳转至

代理模式

分类:结构型模式。

类结构

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,不会随返回值把机器引用一同序列化。

协作过程

  1. 在 RMI 注册表可用的前提下,GumballMachineTestDrive 用主机名和库存参数创建 GumballMachine,通过 Naming.rebind() 将它注册为 gumballmachine。
  2. GumballMonitorTestDrive 根据主机名构造地址,用 Naming.lookup() 取得 GumballMachineRemote 引用,并将它传入 GumballMonitor。
  3. report() 依次调用 getLocation()、getCount()、getState();RMI 代理转发这些请求,由服务端的 GumballMachine 返回相应信息。
  4. 监控端打印查询结果,其中状态对象通过序列化按值传回;远程调用发生 RemoteException 时,由 report() 的异常处理分支处理。

该固定版本的 GumballMonitorTestDrive 使用 args.length >= 0 判断后读取 args[0],因此需要提供主机名参数,不能把源码中的三个预置地址理解为无参数运行时可用的回退路径。这里说明调用协作关系,不执行或修补配套程序。

固定版本源码:

评论