空对象模式解决什么
空对象模式用一个实现同一接口、行为为空或提供默认值的对象,替代返回空引用。调用方不必层层判空,默认语义集中在一处定义,新增调用点时也不会因为漏判而出现空引用异常。它与引入特例的重构手法一脉相承:把「没有这个对象时怎么办」从每个调用点收拢到一个类型里。
基本信息
| 意图 | 以空行为对象替代空引用判断 |
|---|---|
| 收益 | 分支减少,默认行为集中定义并易于统一修改 |
| 适用前提 | 「没有这个对象」本身是语义合理的情况 |
| 不适用 | 调用方需要区分「没有」与「有但值为空」时 |
典型使用场景
- 日志器未配置:提供一个什么都不做的实现,调用方不必在每次记录前判断
- 可选依赖未启用:用空实现替代,主流程代码保持单一形态
- 配置项缺失时的默认策略:返回默认策略对象,而不是让调用方处理空值
- 事件监听:没有订阅者时用空监听器,避免每次发布事件都要判空
- 缓存禁用:用空缓存实现替代判空逻辑,便于在不同环境切换
空实现形态
空实现形态
interface Logger { void log(String msg); }
// 空实现:什么都不做,但满足接口契约
class NopLogger implements Logger {
public void log(String msg) { }
}
// 调用方无需再写判空分支
logger.log("started"); // logger 永不为空适用边界对照
| 情形 | 建议 | 原因 |
|---|---|---|
| 缺少可选依赖 | 使用空对象 | 没有该依赖是正常状态,默认行为明确 |
| 查询确实可能无结果 | 不用空对象 | 调用方需要区分无结果与有结果 |
| 错误被静默吞掉 | 不使用,改为抛出或上报 | 用空对象掩盖真实故障会导致问题难被发现 |
| 空对象需要携带状态 | 谨慎,避免共享可变实例 | 共享的空实例被修改会影响所有使用者 |
| 返回值参与计算 | 明确默认值语义 | 空对象返回的默认值要能被调用方理解 |
空对象的实现应当无状态或不可变:一个共享的空实例若带有可变字段,任何一处写入都会影响所有引用它的地方,问题表现与全局变量类似。
不要把空对象当作错误兜底:该失败的流程被静默走完,缺陷会推迟到更晚的阶段暴露,定位成本显著上升。判断标准是「这种缺失是否属于正常业务状态」。
落地检查清单
- 空实现满足接口契约,调用方无需为它写特殊分支
- 空实例无状态或不可变
- 缺失是否属于正常状态在文档中写明
- 该失败的场景仍然失败,没有被空对象吞掉