设计模式 27:谁能读取延迟加载的库存
库存查询的构建成本较高;访客又不允许查询。旧对象一创建就调用加载器,随后查询时才核对身份,结果是未授权请求已经触发了一次昂贵加载。能否做到先拒绝、授权后首次使用才加载,并让后续查询复用已加载对象?
控制访问与加载时机
Before 在构造器里调用 loader.get();反例断言访客随后被拒绝,加载次数却已经是 1。After 实现与真实 Inventory 相同的 available(sku) 接口,在构造时绑定本章教学角色。调用入口先检查是否为 stock-reader,然后首次使用加载对象、再次使用复用;测试分别断言未授权 0 次加载、授权两次读取只加载 1 次、加载器的异常向外传播。Alternative 用一个普通函数先查角色再加载,适用于只查一次、无需复用实例的场合。
1 | |
此处 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、授权访问;反例是旧版提前加载;失败边界包括未授权、加载器抛错。没有真实仓储、网络或并发测试。
- 新增“授权角色在两次读取间被撤销”,先写旧读数与新增拒绝断言,再将可变授权检查移到请求上下文,不要让代理保留过期角色。
- 若访问始终为单次且加载器很便宜,删掉代理改用
Alternative,复跑授权/拒绝/异常合同,说明去掉缓存失去了什么。
参考资料
- GoF 原书公开图书馆 PDF,4.7 Proxy,目录页标注起始页 233:https://cpcc.chd.gov.in/Content/PDFs/w8UkV3tWNyEtsbUZSWJ7fVhuB9A3tsGYdm4w6VGzg2wUTNFYikqnvvFbbkiW2zmfspPEghd7QTamiMby3lVIBemrhdVWwt6rOQnm.pdf 。
- Proxy 模式简定义,InfoWorld/JavaWorld:https://www.infoworld.com/article/2170730/design-patterns-the-big-picture-part-1-design-pattern-history-and-classification.html 。
- 本篇来源边界:
writing-plans/design-patterns/SOURCES.md。 - 实验代码:
examples/design-patterns/labs/27/。
上一节:26 哪部分对象值得共享;下一节:28 三种报价如何共享契约。





