封装与信息隐藏是什么
Parnas 在 1972 年提出:模块划分的依据应当是「隐藏可能变化的设计决策」,而不是按处理步骤切分。封装的核心是模块通过接口承诺能力,把实现选择藏起来——存储方式、算法、协议、数据格式,这些都是将来可能替换的东西。只把字段改成 private 但协作方式照旧,并不算封装。
基本信息
| 经典文献 | Parnas(1972)《On the Criteria To Be Used in Decomposing Systems into Modules》 |
|---|---|
| 隐藏对象 | 易变的实现决策:存储、算法、协议、格式 |
| 手段 | 模块接口、私有可见性、不提供后门访问器 |
| 检验 | 替换实现时,有多少调用方需要改 |
落地要点
- 对外只暴露稳定视图:快照、查询方法与意图明确的操作
- 不提供为测试而设的后门访问器,改用行为断言的测试
- 集合与结构不直接外泄:返回只读视图或副本
- 实现细节不进接口签名:参数与返回值里不出现存储层类型
- 隐藏变化点:第三方协议、缓存策略、并发模型都应内聚在实现内
- 模块划分依据变化原因,而不是「功能看起来相近」
隐藏点示例
隐藏点示例
// 暴露实现细节:外部能直接改结构,校验与通知全部被绕过
public List<Row> rows;
// 信息隐藏:只给出稳定视图与带语义的操作
private final List<Row> rows = new ArrayList<>();
public Snapshot snapshot() {
return Snapshot.of(rows); // 稳定视图,调用方读不到内部结构
}
public void apply(Edit edit) {
validate(edit); // 校验点收在模块内
edit.applyTo(rows);
events.publish(new RowsChanged());
}隐藏什么与暴露什么
| 对象 | 处理 | 理由 |
|---|---|---|
| 存储结构 | 隐藏 | 可能换成另一种容器或外部存储 |
| 算法与策略 | 隐藏 | 实现可替换且无需通知调用方 |
| 业务不变量 | 暴露为行为方法 | 调用方需要以受控方式触发 |
| 只读数据视图 | 暴露 | 读取是稳定需求 |
| 配置开关 | 收敛到实现内 | 外泄会让调用方依赖实现选择 |
封装的判据可以量化:假设要把实现换掉,有多少调用方需要修改。答案是「几乎不用改」才算封装成功;如果换个存储要改几十处读取代码,那么字段虽然私有,信息依然是公开的。
为测试暴露内部状态是一种隐性侵蚀:测试从此绑定实现细节,重构会因测试而受阻。用行为断言替代状态断言,测试反而更稳定,也更能反映真实需求。