首页/开发/代码质量与评审速查/空对象模式 Null Object(行为型惯用法)
设计原则与模式62 条

空对象模式 Null Object(行为型惯用法)

用实现同一接口的「空行为对象」替代空引用:调用方无需层层判空,默认行为集中定义;与《重构》的 Introduce Special Case 一脉相承。

空对象模式解决什么

空对象模式用一个实现同一接口、行为为空或提供默认值的对象,替代返回空引用。调用方不必层层判空,默认语义集中在一处定义,新增调用点时也不会因为漏判而出现空引用异常。它与引入特例的重构手法一脉相承:把「没有这个对象时怎么办」从每个调用点收拢到一个类型里。

基本信息

意图以空行为对象替代空引用判断
收益分支减少,默认行为集中定义并易于统一修改
适用前提「没有这个对象」本身是语义合理的情况
不适用调用方需要区分「没有」与「有但值为空」时

典型使用场景

  • 日志器未配置:提供一个什么都不做的实现,调用方不必在每次记录前判断
  • 可选依赖未启用:用空实现替代,主流程代码保持单一形态
  • 配置项缺失时的默认策略:返回默认策略对象,而不是让调用方处理空值
  • 事件监听:没有订阅者时用空监听器,避免每次发布事件都要判空
  • 缓存禁用:用空缓存实现替代判空逻辑,便于在不同环境切换

空实现形态

空实现形态
interface Logger { void log(String msg); }

// 空实现:什么都不做,但满足接口契约
class NopLogger implements Logger {
    public void log(String msg) { }
}

// 调用方无需再写判空分支
logger.log("started");   // logger 永不为空

适用边界对照

情形建议原因
缺少可选依赖使用空对象没有该依赖是正常状态,默认行为明确
查询确实可能无结果不用空对象调用方需要区分无结果与有结果
错误被静默吞掉不使用,改为抛出或上报用空对象掩盖真实故障会导致问题难被发现
空对象需要携带状态谨慎,避免共享可变实例共享的空实例被修改会影响所有使用者
返回值参与计算明确默认值语义空对象返回的默认值要能被调用方理解
空对象的实现应当无状态或不可变:一个共享的空实例若带有可变字段,任何一处写入都会影响所有引用它的地方,问题表现与全局变量类似。
不要把空对象当作错误兜底:该失败的流程被静默走完,缺陷会推迟到更晚的阶段暴露,定位成本显著上升。判断标准是「这种缺失是否属于正常业务状态」。

落地检查清单

  • 空实现满足接口契约,调用方无需为它写特殊分支
  • 空实例无状态或不可变
  • 缺失是否属于正常状态在文档中写明
  • 该失败的场景仍然失败,没有被空对象吞掉

易错点

  • 用空对象掩盖真实错误:该报错的分支被静默兜底,问题在更晚阶段以更严重的形式暴露
  • 空对象携带可变状态:共享的空实例被意外修改,行为随调用顺序变化
  • 在需要区分无结果与有结果的场景使用空对象:调用方无法判断到底有没有数据,业务逻辑出错
  • 空实现与真实实现行为差异过大:测试通过但线上行为不同,问题定位困难

数据来源与参考

  • 《重构》第 2 版(Introduce Special Case / Null Object 传统)与《设计模式》相关系谱
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌