装饰器模式和代理模式有什么区别?
简化版
装饰器模式关注“动态叠加功能”,代理模式关注“控制访问”。两者结构上都可能包装同接口对象,但装饰器像给对象加插件,代理更像中间人或门卫。
详细版
装饰器和代理都可能长这样:
class Wrapper implements Service {
private final Service target;
}
所以它们不能只看代码结构,要看设计意图。
| 维度 | 装饰器模式 | 代理模式 |
|---|---|---|
| 核心意图 | 增强功能、叠加能力 | 控制访问、隐藏访问细节 |
| 典型场景 | Java IO、加密压缩、UI 增强 | Spring AOP、RPC、权限、缓存 |
| 组合方式 | 常常多层显式组合 | 常由框架或工厂创建 |
| 关注点 | 原能力基础上加新能力 | 是否访问、何时访问、如何访问 |
| 类比 | 插件、外套、加料 | 门卫、中介、替身 |
装饰器例子:
InputStream in = new BufferedInputStream(new FileInputStream(file));
重点是给文件输入流增加缓冲能力。
代理例子:
UserService service = proxyFactory.create(UserService.class);
重点可能是权限校验、事务控制、远程调用转发。
面试中更稳的回答是:结构相似,意图不同;功能叠加偏装饰器,访问控制偏代理。
完整版教学
一、为什么二者最容易混
装饰器和代理都属于结构型模式,而且都可能使用共同接口。
例如:
interface Component {
void operation();
}
装饰器:
class LogDecorator implements Component {
private final Component component;
}
代理:
class ComponentProxy implements Component {
private final Component target;
}
从类结构看,几乎一样。因此区分它们要看“包装的目的”。
二、装饰器的目的:能力叠加
装饰器模式通常是为了让对象获得更多能力。
比如:
DataSource source = new CompressionDecorator(
new EncryptionDecorator(
new FileDataSource()
)
);
这里重点是:
- 文件数据源提供基础读写;
- 加密装饰器增加加解密;
- 压缩装饰器增加压缩解压;
- 多个能力可以自由组合。
如果去掉装饰器,基础对象仍然可以工作,只是能力少一些。
三、代理的目的:访问控制
代理更强调控制访问过程。
例如权限代理:
public void deleteUser(Long id) {
if (!hasPermission()) {
throw new AccessDeniedException();
}
target.deleteUser(id);
}
这里重点不是给 deleteUser 增加一种业务能力,而是决定是否允许访问目标对象。
远程代理也是类似:
userService.getUser(id);
表面是本地调用,代理对象实际把请求发到远程服务。它隐藏的是访问细节。
四、缓存到底算代理还是装饰器
缓存很容易让人纠结。
如果缓存的主要目的是控制是否访问真实对象,命中缓存就不调目标对象,它更像代理。
if (cache.containsKey(key)) {
return cache.get(key);
}
return target.query(id);
如果缓存被设计成一个可组合增强层,也可以看出装饰器味道。
这说明设计模式不是死标签。真实项目里要看主意图。如果面试官追问,回答“结构相似,按主要意图区分”比硬分类更稳。
五、Spring AOP 更偏代理还是装饰器
Spring AOP 更常被归为代理模式。
原因是 Spring 通过代理对象控制方法调用,在调用前后织入事务、权限、日志等切面逻辑。调用方通常不是主动组合多层装饰器,而是容器返回一个代理对象。
虽然从“增强行为”的角度看它和装饰器也有相似处,但主意图是通过代理拦截方法调用。
六、能力叠加和访问控制的分界
压缩装饰器把 100 KB 响应变成 30 KB,核心是增加能力;权限代理拒绝 1 个未授权请求,核心是决定能否访问目标。
decorator -> delegate + enhance; proxy -> guard/route -> target
这个推演把模式带来的收益和成本放在同一条调用链上。设计评审时既要确认结果正确,也要确认额外层次没有改变原有契约。
七、边界、代价与验证
| 检查维度 | 应确认的内容 |
|---|---|
| 正确性 | 结构相似是类图层面的事实,意图不同才是面试回答的核心。 |
| 适用边界 | 两者可同时存在,例如权限代理外再套压缩装饰器;模式可以组合,名称取决于该对象承担的主要职责。 |
| 测试证据 | 覆盖正常路径、异常路径、空值或未知类型,并断言调用次数与顺序 |
| 运行成本 | 记录额外对象、调用层数、延迟、线程或内存开销,不用模式名称代替测量 |
模式落地后要用单元测试验证独立职责,用集成测试验证对象装配和真实调用入口。涉及并发、远程或异步时,还要补充竞态、超时、重复执行与资源释放测试。
易错点:结构相似是类图层面的事实,意图不同才是面试回答的核心。
八、常见误区与追问
- 误区:缓存永远只能归为代理模式。 若强调避免访问真实对象更像代理;若强调为组件叠加可组合缓存能力,也可能按装饰器讨论。
- 误区:装饰器可以任意改变组件接口。 透明装饰依赖共同的 Component 契约;随意增加只能向下转型调用的方法,会破坏可替换性和继续组合的能力。
- 误区:示例代码能运行,就代表模式边界设计正确。 能运行只证明一条路径,仍要检查异常、并发、顺序、生命周期以及新增实现时是否修改稳定代码。
- 追问:AOP 增强为什么通常称为代理? 框架向客户端提供目标替身并控制方法访问,访问治理意图更突出。
- 追问:装饰器链越长,扩展性就一定越好吗? 不一定;链越长,顺序、异常传播和排障成本越高,应由装配层集中管理并限制职责粒度。
- 追问:怎样验证这套设计没有改变原有语义? 先做基线行为测试,再对新增层验证返回值、异常类型、调用次数和顺序;性能敏感处还要比较改造前后的量化指标。
九、加强记忆
装饰器和代理的代码外形可能很像,但装饰器问的是“还能加什么能力”,代理问的是“访问目标时要经过什么控制”。一个像加料,一个像门卫,按意图区分最稳。