1. 概述
在这篇简短的文章中,我们来聊一聊空对象模式(Null Object Pattern),它是策略模式的一个特例。我们将说明它的用途,以及在什么场景下值得使用它。
照例,我们会提供一个简单的代码示例帮助理解。
2. 空对象模式
在大多数面向对象语言中,我们不能直接使用 null
引用执行方法调用,因此经常需要做 null
检查:
Command cmd = getCommand();
if (cmd != null) {
cmd.execute();
}
有时候,如果这类 if
判断太多,代码就会变得丑陋、难以阅读,还容易出错。这时候,空对象模式就能派上用场了。
✅ 空对象模式的核心目的:减少 null
检查。
我们可以通过识别“空行为”,并将其封装到客户端期望的类型中来实现这一点。很多时候,这种“空行为”非常简单——就是“什么都不做”。这样一来,我们就不再需要对 null
做特殊处理。
我们可以像对待任何其他实例一样对待空对象。客户端代码因此变得更简洁、更清晰。
由于空对象通常没有状态,没必要多次创建相同的实例。因此,我们常常将空对象实现为单例。
3. 空对象模式的 UML 图
来看一下这个模式的结构图:
从图中可以看到以下参与者:
- 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 上找到。