信息隐藏的划分依据
Parnas 在 1972 年提出:模块划分的依据不该是处理步骤,而应是“哪些决策可能发生变化”。每个模块把一类易变决策藏起来,只对外暴露稳定的抽象与契约。这样当存储格式、算法实现或外部接口发生变化时,改动被限制在模块内部。它与“隐藏数据”不是一回事——被隐藏的对象是易变的设计决策。
基本信息
| 划分依据 | 哪些决策可能变化(存储格式、算法、外部系统) |
|---|---|
| 暴露内容 | 稳定的抽象与契约,不暴露实现细节 |
| 收益 | 局部修改不影响其他模块,支持并行开发 |
| 与封装的区别 | 封装关注访问控制,信息隐藏关注隔离变化点 |
典型使用场景
- 替换存储实现:仓储接口隔离数据库细节,业务逻辑不感知 SQL 方言
- 接入第三方系统:防腐层与适配器隔离外部模型与内部领域模型
- 算法可替换:策略接口按配置选择实现,切换不需要改调用方
- 部署拓扑变化:配置注入替代硬编码,单机与集群部署共用代码
- 多端接入:BFF 与适配层隔离端侧差异,核心逻辑保持单一实现
变化点识别示例
| 可能变化的决策 | 隐藏方式 |
|---|---|
| 存储格式 | 仓储接口后替换实现 |
| 第三方接口 | 防腐层与适配器 |
| 算法实现 | 策略接口按配置选择 |
| 部署拓扑 | 配置注入而非硬编码 |
接口设计对照
接口设计对照
暴露实现细节:
getOrders(sqlWhere: string, orderBy: string)
# 调用方必须了解表结构与字段名,索引调整即影响调用方
隐藏变化点:
findOrdersByStatus(status, page): Order[]
findOrdersByCustomer(customerId, timeRange): Order[]
# 调用方表达业务意图,存储方案在实现里演进使用要点
- 模块接口暴露内部数据结构,实现变化即破坏调用方,属于典型的隐藏对象选错
- 按变化点而不是按处理步骤划分模块,否则变化时仍要跨模块改
- 接口按调用方需求设计:只为实际用到的能力开方法,避免万能查询接口
- 留一条“变化发生时的验证路径”:接口契约要有测试,替换实现时能立刻发现破坏
信息隐藏与“隐藏数据”不同:隐藏的对象是易变的设计决策;把数据藏起来而暴露了变化点,等于没做信息隐藏。
过度抽象同样是成本:只有真的存在变化预期时才引入抽象层,为想象中的变化预留接口会带来无价值的间接层与维护负担。