代理模式和装饰器模式有什么区别?
简化版
代理模式关注“控制访问”,装饰器模式关注“增强能力”。两者结构上都可能包装一个对象,但代理通常由代理对象决定是否、何时、如何访问目标对象;装饰器通常用于给对象动态叠加功能。
详细版
代理和装饰器都属于结构型模式,也都常见“包装一个同接口对象”的结构,所以容易混。
区别主要看目的:
| 维度 | 代理模式 | 装饰器模式 |
|---|---|---|
| 核心目的 | 控制访问目标对象 | 动态增强对象能力 |
| 关注点 | 权限、远程调用、缓存、延迟加载、事务 | 功能叠加、行为组合 |
| 客户端是否知道目标 | 通常不关心真实对象在哪里 | 通常主动组合多个装饰器 |
| 典型例子 | Spring AOP、RPC 代理、权限代理 | Java IO 流、饮料加料 |
| 关系 | 代理更像中介或守门人 | 装饰器更像功能插件 |
代理示例:
service = new UserServiceProxy(realService);
代理可能判断权限,不允许访问真实对象。
装饰器示例:
InputStream in = new BufferedInputStream(new FileInputStream(file));
装饰器是在原能力上叠加缓冲能力。
一句判断思路:如果重点是“要不要让你访问目标对象”,偏代理;如果重点是“在原对象基础上加新功能”,偏装饰器。
完整版教学
一、为什么二者容易混
代理模式和装饰器模式的类图很像:
接口
├── 真实对象
└── 包装对象(持有接口引用)
两者都可能写成:
class Wrapper implements Subject {
private final Subject target;
}
所以不能只看结构,要看意图。
设计模式最重要的是意图,而不是长得像不像。
二、代理模式的意图是控制访问
代理对象站在客户端和真实对象之间,控制访问过程。
它可以:
- 拒绝访问;
- 延迟访问;
- 远程转发;
- 命中缓存后不访问;
- 加事务后访问;
- 记录日志后访问。
例如权限代理:
public void deleteUser(Long id) {
if (!hasPermission()) {
throw new AccessDeniedException();
}
target.deleteUser(id);
}
这里重点是访问控制:你能不能删、什么时候删、删之前要检查什么。
三、装饰器模式的意图是功能叠加
装饰器模式强调在不修改原对象的情况下动态叠加功能。
Java IO 是经典例子:
InputStream in = new BufferedInputStream(
new FileInputStream("a.txt")
);
FileInputStream 提供文件读取能力,BufferedInputStream 增加缓冲能力。你还可以继续叠加其他装饰器。
装饰器的重点不是“控制你能不能访问 FileInputStream”,而是“给读取能力增加新的行为”。
四、从使用方式看差异
代理通常由框架或工厂创建,调用方不一定主动组装。
例如 Spring AOP:
@Autowired
private UserService userService;
你注入的可能已经是代理对象,但业务代码不需要手动套代理。
装饰器通常更强调显式组合:
Coffee coffee = new MilkDecorator(new SugarDecorator(new PlainCoffee()));
调用方可以按需要组合不同装饰器。
当然这不是绝对规则,但有助于判断意图。
五、二者能否同时出现
可以。同一个包装结构,既可能有代理意图,也可能有装饰器意图。
比如一个对象包装后既检查权限,又增加统计功能。具体归类要看主要目的。
面试时不用陷入“这个例子必须是哪一个”的争吵。更好的回答是:结构相似,区别看设计意图;控制访问偏代理,叠加职责偏装饰器。
六、从意图区分相似包装结构
同样包一层接口:权限代理可能拒绝 1 次未授权调用;压缩装饰器则仍执行原功能,只把输出从 100 KB 变成约 30 KB。
proxy: allow/deny -> target; decorator: target -> add capability -> result
这个推演把模式带来的收益和成本放在同一条调用链上。设计评审时既要确认结果正确,也要确认额外层次没有改变原有契约。
七、边界、代价与验证
| 检查维度 | 应确认的内容 |
|---|---|
| 正确性 | 缓存既可能是控制访问的代理,也可能被描述为能力增强;命名最终要服从上下文中的主要意图。 |
| 适用边界 | 代理常由框架或服务端决定真实对象的访问方式,装饰器通常由客户端按能力组合;这不是绝对语法规则,而是意图线索。 |
| 测试证据 | 覆盖正常路径、异常路径、空值或未知类型,并断言调用次数与顺序 |
| 运行成本 | 记录额外对象、调用层数、延迟、线程或内存开销,不用模式名称代替测量 |
模式落地后要用单元测试验证独立职责,用集成测试验证对象装配和真实调用入口。涉及并发、远程或异步时,还要补充竞态、超时、重复执行与资源释放测试。
易错点:缓存既可能是控制访问的代理,也可能被描述为能力增强;命名最终要服从上下文中的主要意图。
八、常见误区与追问
- 误区:只要类里持有同接口对象,它就是装饰器。 代理、装饰器甚至责任链都可能有相同结构,不能脱离职责与调用语义判断。
- 误区:代理类存在,就代表所有调用都会经过代理。 只有客户端持有并调用代理引用时增强才生效;绕过代理直接调用目标对象,调用链自然不会出现增强。
- 误区:示例代码能运行,就代表模式边界设计正确。 能运行只证明一条路径,仍要检查异常、并发、顺序、生命周期以及新增实现时是否修改稳定代码。
- 追问:日志增强更像代理还是装饰器? 若它统一治理目标访问更偏代理;若作为可自由叠加的组件能力也可视作装饰器,需要说明语境。
- 追问:代理模式和装饰器模式能只靠类图区分吗? 不能;两者都可能包装同一接口,必须结合“控制访问”还是“叠加职责”的设计意图区分。
- 追问:怎样验证这套设计没有改变原有语义? 先做基线行为测试,再对新增层验证返回值、异常类型、调用次数和顺序;性能敏感处还要比较改造前后的量化指标。
九、加强记忆
代理像门卫,决定你能不能进、怎么进、进之前做什么;装饰器像外挂组件,在原能力上叠加新能力。结构可能相似,但意图不同,这是区分它们的关键。