库存查询的构建成本较高;访客又不允许查询。旧对象一创建就调用加载器,随后查询时才核对身份,结果是未授权请求已经触发了一次昂贵加载。能否做到先拒绝、授权后首次使用才加载,并让后续查询复用已加载对象?

控制访问与加载时机

Before 在构造器里调用 loader.get();反例断言访客随后被拒绝,加载次数却已经是 1。After 实现与真实 Inventory 相同的 available(sku) 接口,在构造时绑定本章教学角色。调用入口先检查是否为 stock-reader,然后首次使用加载对象、再次使用复用;测试分别断言未授权 0 次加载、授权两次读取只加载 1 次、加载器的异常向外传播。Alternative 用一个普通函数先查角色再加载,适用于只查一次、无需复用实例的场合。

1
2
3
4
5
public int available(String sku) {
authorize(role);
if (loaded == null) loaded = loader.get();
return loaded.available(sku);
}

此处 Proxy 的意图是把访问控制与延迟加载放在和真实对象相同的接口之后;不是 Adapter(协议转换)、Decorator(叠加价格/行为)、Facade(多对象编排)或 Bridge(两个维度独立演化)。相同的“包一层”形状不能证明属于同一个模式。示例把角色绑定到实例,不能把这个实例交给其他主体共享,否则主体边界会混乱;角色撤销、短期令牌、空加载结果和缓存失效均没有合同。本地懒加载也没有同步,不能推断并发访问只加载一次,更不是安全框架的正式鉴权。

设计 访客读取前是否加载 同一授权实例读两次
Before 是,构造阶段 1 次 已提前加载
After 否,先拒绝 第一次加载,第二次复用
Alternative 否,先拒绝 若调用两次会分别加载
flowchart LR
    Caller[库存查询方] --> Proxy[After 代理]
    Proxy --> Auth[检查角色]
    Auth --> Lazy[首次加载 Inventory]
    Lazy --> Real[真实 Inventory.available]

验证与练习

运行 ./mvnw -B -ntp -pl labs/27 -am test 及累计 ./mvnw -B -ntp verify;输入、计数、异常、退出码与原始输出见 examples/design-patterns/evidence/27/RUN.md。正例是库存 3、授权访问;反例是旧版提前加载;失败边界包括未授权、加载器抛错。没有真实仓储、网络或并发测试。

  1. 新增“授权角色在两次读取间被撤销”,先写旧读数与新增拒绝断言,再将可变授权检查移到请求上下文,不要让代理保留过期角色。
  2. 若访问始终为单次且加载器很便宜,删掉代理改用 Alternative,复跑授权/拒绝/异常合同,说明去掉缓存失去了什么。

参考资料

上一节:26 哪部分对象值得共享;下一节:28 三种报价如何共享契约。