外观模式和封装有什么关系?
简化版
外观模式是封装思想在子系统层面的体现。普通封装隐藏对象内部细节,外观模式隐藏一组子系统的复杂调用细节,对外暴露更简单、更稳定的接口。
详细版
封装通常发生在类或模块内部:
对象隐藏字段和实现细节
外观模式发生在更粗的层级:
Facade 隐藏多个子系统的调用细节
它们目标一致:降低使用方理解成本,减少变化影响。
外观模式可以看成一种结构化封装:
- 封装调用顺序;
- 封装子系统依赖;
- 封装参数转换;
- 封装结果聚合;
- 封装异常处理;
- 封装内部架构变化。
但封装不等于把所有细节都藏起来。好的外观接口要暴露恰当抽象,不能让接口语义变得模糊。
完整版教学
一、封装的核心目的
封装不是简单把字段设成 private。
它真正的目的包括:
- 隐藏实现细节;
- 暴露稳定接口;
- 降低使用成本;
- 控制变化影响范围;
- 保护内部一致性。
外观模式正是在更大粒度上实现这些目标。
二、外观模式封装的是子系统复杂性
例如:
orderFacade.createOrder(request);
这个方法背后可能有多个子系统调用。
客户端不需要知道:
- 先校验用户还是先查商品;
- 优惠券怎么冻结;
- 库存怎么锁定;
- 订单事件怎么发;
- 异常怎么转换。
这些复杂性被 Facade 封装起来。
三、外观模式能稳定对外接口
内部服务可能重构:
StockService -> InventoryService
CouponService 拆成 CouponQueryService + CouponFreezeService
只要 Facade 对外接口不变,客户端就不需要跟着改。
这就是封装带来的变化隔离。
四、过度封装也会有问题
如果 Facade 把所有东西都藏起来,调用方没有必要的控制能力,接口就会变得难用。
典型表现:
- 一个方法参数越来越多;
- 一个接口支持太多场景;
- 内部 if/else 越来越复杂;
- 调用方想绕过 Facade。
好的封装不是藏得越多越好,而是抽象层次合适。
五、外观接口要按用例设计
外观接口最好表达业务用例:
createOrder()
cancelOrder()
queryUserProfile()
exportReport()
不要设计成模糊接口:
process()
handle()
execute()
清晰的用例接口才是好的封装。
六、常见误区与追问
封装隐藏的是实现决策,外观模式则针对一组子系统提供更易用的入口。客户端完成1次下单原本要调用库存、价格、订单3个接口,Facade 可暴露 placeOrder(),但并不意味着三个子系统从此不可直接使用。稳定的是用例契约,内部调用顺序可以演进。
| 检查维度 | 判定依据 |
|---|---|
| 普通封装 | 保护单个对象内部状态与实现 |
| 外观封装 | 简化多个子系统的协作方式 |
记忆钩子:封装关注“里面别乱碰”,外观关注“外面更好用”。
- 误区:外观就是给方法换一个短名字。 它应吸收调用顺序、依赖组合和边界处理,而非简单别名。
- 追问:外观是否违反信息透明? 它只提供更高层抽象,必要时仍可开放底层高级接口。
- 误区:封装越彻底越好。 过度隐藏会阻碍高级场景和问题诊断,应按受众设计层级化 API。
- 追问:外观接口为何更稳定? 调用方依赖用例语义,不再感知子系统重构和调用顺序变化。
- 追问:怎样测试封装边界? 从公开入口验证结果,并确保客户端代码不需要直接拼接内部步骤。
七、加强记忆
外观模式是封装思想在子系统层面的应用:把多个子系统的复杂调用藏在一个简单入口后面。它不是把一切都藏起来,而是暴露恰当抽象,让调用方简单、内部变化可控。