空对象模式-Null Object Pattern
序言
写 Java 代码时,"空指针异常"(NullPointerException)几乎是每个人最熟悉的噩梦。为了防御它,我们养成了到处写 if (xxx != null) 的习惯:
Logger logger = createLogger();
if (logger != null) {
logger.info("系统启动");
}
如果 createLogger() 因为配置缺失返回了 null,这段代码只能"静默跳过"日志——而如果没有判空,直接崩溃。无论是哪种,null 都让调用方被迫额外处理"什么都没有"这个分支,代码被 if-else 塞满。
空对象模式(Null Object Pattern)给出了一种完全不同的思路:与其返回 null,不如返回一个"什么都不做"的对象。调用方不再需要判空——调用一个什么都不做的 NullLogger.info(),效果等同于什么都没发生,但代码干干净净。
定义
空对象模式并不是 GoF 23 种经典设计模 式之一,而是由 Bobby Woolf 在《Pattern Languages of Program Design》中提出的行为型模式:
The intent of a Null Object is to encapsulate the absence of an object by providing a substitutable alternative that offers suitable default do-nothing behavior.
空对象的意图是:通过提供一个可替换的、具有合适"空操作"默认行为的对象,来封装"对象的缺失"。
两个关键点:
- 可替换:空对象必须实现与真实对象相同的接口,客户端可以无缝使用;
- 空操作:空对象的方法实现是"什么都不做"(或提供安全的默认值),而不是抛异常。
结构
让我们从一个问题开始
假设我们的系统有一个日志接口,生产环境使用文件日志,但某些环境(如测试、无日志需求的环境)不需要日志。不用空对象模式:
public class LoggerFactory {
public static Logger getLogger(boolean loggingEnabled) {
if (loggingEnabled) {
return new FileLogger();
}
return null; // 不启用日志就返回 null
}
}
于是调用方必须判空:
Logger logger = LoggerFactory.getLogger(config.isLoggingEnabled());
// 到处都是判空
if (logger != null) {
logger.info("系统启动完成");
}
if (logger != null) {
logger.warn("配置项缺失");
}
问题显而易见:
- 每个调用点都要判空,代码冗余且容易遗漏(漏掉一处就崩);
null的含义模糊:是"没日志"还是"出错了"?无法区分;- 新同事接手代码,不知道"哪些地方可能返回 null",空指针风险潜伏。