适配器模式和装饰器、代理、外观模式有什么区别?
简化版
适配器模式关注接口转换,让不兼容接口能协作;装饰器模式关注动态增强对象功能;代理模式关注控制对象访问;外观模式关注给复杂子系统提供统一入口。适配器是“接口不合适,转一下”,装饰器是“能力不够,加一层”,代理是“访问要控制”,外观是“子系统太复杂,包一个门面”。
详细版
这几个模式都可能出现包装对象,所以容易混淆。核心区别是设计目的不同:
| 模式 | 目的 | 是否改变接口 | 典型场景 |
|---|---|---|---|
| 适配器模式 | 接口转换 | 通常改变 | 第三方 SDK、旧接口兼容 |
| 装饰器模式 | 动态增强功能 | 通常不改变 | Java I/O、功能叠加 |
| 代理模式 | 控制访问 | 通常不改变 | AOP、远程代理、权限控制 |
| 外观模式 | 简化子系统调用 | 提供更粗粒度入口 | 统一服务门面、子系统封装 |
适配器强调“兼容”,装饰器强调“增强”,代理强调“控制”,外观强调“简化”。
面试时不要只看代码结构是否有包装类。很多模式结构相似,真正区分它们的是意图。
完整版教学
一、适配器模式:接口不匹配,需要转换
适配器模式的典型问题是:客户端期望一种接口,但已有类提供另一种接口。
比如系统统一接口是:
interface PayService {
void pay(String orderId, int amount);
}
第三方 SDK 是:
class WxPayClient {
void createTransaction(WxRequest request) {}
}
适配器负责把 pay 转换成 createTransaction。
它的关键词是接口转换。
二、装饰器模式:接口合适,但想增加能力
装饰器模式中,被包装对象通常已经符合目标接口。
比如:
InputStream in = new BufferedInputStream(new FileInputStream("a.txt"));
FileInputStream 本来就是 InputStream,不需要转换接口。BufferedInputStream 的作用是增加缓冲能力。
所以装饰器的关键词是功能增强,而不是接口转换。
三、代理模式:接口合适,但访问需要控制
代理模式中,代理对象和真实对象通常实现同一个接口。
代理的目的可能是:
- 权限校验;
- 延迟加载;
- 缓存;
- 远程调用;
- 事务控制;
- 日志监控。
比如 Spring AOP 代理:
ServiceProxy -> RealService
客户端以为自己在调用真实服务,实际先经过代理。代理关注的是访问控制和附加处理。
四、外观模式:子系统太复杂,需要统一入口
外观模式不是为了转换某一个对象的接口,而是为了给一组复杂子系统提供简单入口。
比如:
class OrderFacade {
void createOrder() {
stockService.lock();
priceService.calculate();
couponService.freeze();
orderService.create();
}
}
调用方只调 OrderFacade,不用知道内部有多少子系统。
它的关键词是简化调用和隐藏复杂性。
五、从问题句式快速判断
可以用问题句式来区分:
- 接口不兼容,但已有能力想复用:适配器;
- 接口兼容,但想叠加功能:装饰器;
- 接口兼容,但要控制访问:代理;
- 子系统复杂,想提供统一入口:外观。
这个判断比背 UML 更实用。
六、它们可以组合出现
真实项目中,这些模式可能组合使用。
比如接入第三方支付:
- 用适配器统一不同支付 SDK;
- 用代理给支付服务加日志和熔断;
- 用装饰器给某个支付实现叠加监控;
- 用外观给订单系统提供统一支付入口。
多个模式同时出现不冲突,关键看每一层解决什么问题。
七、用接口是否改变和主要意图区分
旧接口 send(String) 转成 notify(Message) 是适配;同接口加重试是装饰;限制调用是代理;把 6 个子系统合成 1 个入口是外观。
adapter changes interface | decorator adds duty | proxy controls access | facade simplifies subsystem
适配器的验收标准不是“调用成功”,而是 Target 的业务语义在转换前后保持成立。参数、单位、时区、精度、空值、错误码和资源所有权都应逐项形成契约样例,否则最危险的错误会以“成功响应”的形式潜伏。
八、语义边界与契约样例验证
| 检查维度 | 应确认的内容 |
|---|---|
| 机制正确性 | 判断名称时先看当前对象解决的首要问题,而不是只看它持有了谁。 |
| 适用边界 | 这些模式可叠加:Facade 内调用 Adapter,Adapter 外套重试装饰器,再由代理做鉴权。 |
| 测试证据 | 对同一业务样例验证转换前后的单位、精度、空值和异常语义,并覆盖不可逆转换 |
| 工程代价 | 重点统计字段映射与对象分配成本、外部模型变更影响面,以及多层版本适配造成的认知债务 |
易错点:判断名称时先看当前对象解决的首要问题,而不是只看它持有了谁。
九、常见误区与追问
- 误区:外观模式和适配器都会提供新接口,所以完全相同。 外观主要简化复杂子系统,适配器主要解决既有接口与目标契约不兼容。
- 误区:适配器只需要让方法签名能够编译。 真正的适配还包括单位、时区、错误码、空值、资源释放和幂等语义;签名一致不代表行为兼容。
- 追问:代理能否同时转换接口? 可以承担额外职责,但若主要问题是接口转换,更准确的核心模式仍是适配器。
- 追问:适配器应该放在哪一层? 通常放在系统边界或防腐层,让业务层只依赖 Target,避免第三方模型和异常向内扩散。
- 追问:适配器测试为什么需要契约样例? 因为类型相同仍可能单位、精度或错误语义不同;契约样例能证明转换后的行为而不只是编译通过。
十、加强记忆
适配器、装饰器、代理、外观看起来都像“包一层”,但目的不同。适配器解决接口不兼容,装饰器解决功能增强,代理解决访问控制,外观解决子系统复杂。面试时抓住设计意图,就不会被相似结构绕进去。