1. 概述

在这篇简短的文章中,我们来聊一聊空对象模式(Null Object Pattern),它是策略模式的一个特例。我们将说明它的用途,以及在什么场景下值得使用它。

照例,我们会提供一个简单的代码示例帮助理解。

2. 空对象模式

在大多数面向对象语言中,我们不能直接使用 null 引用执行方法调用,因此经常需要做 null 检查:

Command cmd = getCommand();
if (cmd != null) {
    cmd.execute();
}

有时候,如果这类 if 判断太多,代码就会变得丑陋、难以阅读,还容易出错。这时候,空对象模式就能派上用场了。

空对象模式的核心目的:减少 null 检查。

我们可以通过识别“空行为”,并将其封装到客户端期望的类型中来实现这一点。很多时候,这种“空行为”非常简单——就是“什么都不做”。这样一来,我们就不再需要对 null 做特殊处理。

我们可以像对待任何其他实例一样对待空对象。客户端代码因此变得更简洁、更清晰。

由于空对象通常没有状态,没必要多次创建相同的实例。因此,我们常常将空对象实现为单例

3. 空对象模式的 UML 图

来看一下这个模式的结构图:

NOP

从图中可以看到以下参与者:

  • Client:需要一个 AbstractObject 实例
  • AbstractObject:定义 Client 期望的接口,可能包含实现类共享的逻辑
  • RealObject:具体实现,提供真实业务逻辑
  • NullObject:实现相同接口,但提供“空行为”(如什么都不做)

4. 实现示例

理论讲完,来看个实际例子。

假设我们有一个消息路由系统。每条消息都应有明确的优先级。系统会根据优先级将高优先级消息路由到 SMS 网关,中优先级消息路由到 JMS 队列。

⚠️ 但偶尔也会出现优先级未定义或为空的消息,这些消息应被丢弃,不再处理。

首先,我们定义一个 Router 接口:

public interface Router {
    void route(Message msg);
}

然后,创建两个具体实现类:

public class SmsRouter implements Router {
    @Override
    public void route(Message msg) {
        // 具体实现
    }
}
public class JmsRouter implements Router {
    @Override
    public void route(Message msg) {
        // 具体实现
    }
}

最后,实现空对象

public class NullRouter implements Router {
    @Override
    public void route(Message msg) {
        // 啥也不干
    }
}

现在,我们把所有部分组合起来,看看客户端代码会是什么样子:

public class RoutingHandler {
    public void handle(Iterable<Message> messages) {
        for (Message msg : messages) {
            Router router = RouterFactory.getRouterForMessage(msg);
            router.route(msg);
        }
    }
}

✅ 可以看到,我们对所有 Router 对象都一视同仁,不管 RouterFactory 返回的是哪个实现类。这样代码更干净,也更容易维护。

5. 使用场景

当客户端仅仅是为了跳过执行或执行默认操作而检查 null 时,就应该考虑使用空对象模式。

在这种情况下,我们可以将“空行为”封装到一个空对象中,并将其返回给客户端,而不是返回 null。这样客户端代码就不再需要关心对象是否为 null

这种做法符合面向对象的基本原则,比如 Tell-Don’t-Ask

为了更好地理解何时使用该模式,来看个例子:

public interface CustomerDao {
    Collection<Customer> findByNameAndLastname(String name, String lastname);
    Customer getById(Long id);
}

大多数开发者会在 findByNameAndLastname() 中**找不到匹配项时返回 Collections.emptyList()**,这正是空对象模式的一个典型应用。

❌ 但 getById() 方法不同。调用者期望获取特定的客户实体。如果找不到该客户,应显式返回 null 来表示 ID 有问题。

⚠️ 所以,使用空对象模式前,一定要结合具体业务场景判断。否则,可能会引入难以排查的 bug。

6. 小结

本文我们了解了空对象模式是什么,以及它适用于哪些场景,并通过一个简单示例进行了实现。

所有代码示例均可在 GitHub 上找到。


原始标题:Introduction to the Null Object Pattern