Introduction to the Null Object Pattern – 空对象模式介绍

最后修改: 2019年 3月 7日

中文/混合/英文(键盘快捷键:t)

1. Overview

1.概述

In this quick tutorial, we’ll have a look at the Null Object Pattern, a special case of the Strategy Pattern. We’ll describe its purpose and when we should actually consider using it.

在这个快速教程中,我们将看看空对象模式,这是策略模式的一个特殊情况。我们将描述它的用途,以及什么时候我们应该真正考虑使用它。

As usual, we’ll also provide a simple code example.

像往常一样,我们还将提供一个简单的代码例子。

2. Null Object Pattern

2.空对象模式

In most object-oriented programming languages, we’re not allowed to use a null reference. That’s why we’re often forced to write null checks:

在大多数面向对象的编程语言中,我们不允许使用null引用。这就是为什么我们经常被迫写null检查。

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

Sometimes, if the number of such if statements get high, the code may become ugly, hard to read and error-prone. This is when the Null Object Pattern may come in handy.

有时,如果这种if语句的数量很高,代码可能会变得很难看,难以阅读和容易出错。这时,Null对象模式可能会派上用场。

The intent of the Null Object Pattern is to minimize that kind of null check. Instead, we can identify the null behavior and encapsulate it in the type expected by the client code. More often then not, such neutral logic is very simple – do nothing. This way we no longer need to deal with special handling of null references.

空对象模式的目的是将这种检查降到最低。相反,我们可以识别空行为,并将其封装在客户代码所期望的类型中。更多时候,这种中性逻辑非常简单–什么都不做。这样,我们就不再需要处理对null引用的特殊处理。

We simply may treat null objects the same way we treat any other instance of a given type that actually contains some more sophisticated business logic. Consequently, the client code stays cleaner.

我们可以简单地对待空对象,就像对待任何其他实际包含一些更复杂业务逻辑的给定类型的实例一样。因此,客户端的代码保持了清洁。

As null objects should not have any state, there’s no need to create identical instances multiple times. Thus, we’ll often implement null objects as singletons.

由于空对象不应该有任何状态,所以没有必要多次创建相同的实例。因此,我们经常将实现空对象作为singletons

3. Null Object Pattern’s UML Diagram

3.空对象模式的UML图示

Let’s look at the pattern visually:

让我们直观地看一下这个模式。

NOP

As we can see, we can identify the following participants:

我们可以看到,我们可以确定以下参与者。

  • Client requires an instance of AbstractObject
  • AbstractObject defines the contract Client expects – it may also contain shared logic for the implementing classes
  • RealObject implements AbstractObject and provides real behavior
  • NullObject implements AbstractObject and provides neutral behavior

4. Implementation

4.实施

Now that we have a clear idea of the theory, let’s look at an example.

现在我们对这个理论有了一个清晰的概念,让我们看看一个例子。

Imagine we have a message router application. Each message should have a valid priority assigned. Our system is supposed to route high priority messages to an SMS gateway whereas messages with medium priority should be routed to a JMS queue.

想象一下,我们有一个消息路由器应用。每条消息都应该有一个有效的优先级分配。我们的系统应该将高优先级的消息路由到SMS网关,而中等优先级的消息应该被路由到JMS队列。

From time to time, however, messages with “undefined” or empty priority might come to our application. Such messages should be discarded from further processing.

然而,不时地,带有 “未定义 “或空优先级的消息可能会来到我们的应用程序。这样的消息应该从进一步处理中被丢弃。

First, we’ll create the Router interface:

首先,我们将创建Router接口。

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

Next, let’s create two implementations of the above interface – the one responsible for routing to an SMS gateway and the one that will route the messages to JMS queue:

接下来,让我们创建上述接口的两个实现–一个负责路由到SMS网关,另一个负责将消息路由到JMS队列。

public class SmsRouter implements Router {
    @Override
    public void route(Message msg) {
        // implementation details
    }
}
public class JmsRouter implements Router {
    @Override
    public void route(Message msg) {
        // implementation details
    }
}

Finally, let’s implement our null object:

最后,我们来实现我们的空对象:

public class NullRouter implements Router {
    @Override
    public void route(Message msg) {
        // do nothing
    }
}

We’re now ready to put all the pieces together. Let’s see how the example client code may look like:

我们现在已经准备好把所有的部件放在一起。让我们看看客户端代码的例子是什么样子的。

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

As we can see, we treat all Router objects the same way, no matter what implementation is returned by the RouterFactory. This allows us to keep our code clean and readable.

正如我们所看到的,我们以同样的方式对待所有Router对象,无论RouterFactory返回何种实现。这使我们能够保持代码的简洁和可读性。

5. When to Use the Null Object Pattern

5.什么时候使用空对象模式

We should use the Null Object Pattern when a Client would otherwise check for null just to skip execution or perform a default action. In such cases, we may encapsulate the neutral logic within a null object and return that to the client instead of the null value. This way client’s code no longer needs to be aware if a given instance is null or not.

客户端检查null以跳过执行或执行默认操作时,我们应该使用Null对象模式。在这种情况下,我们可以将中性逻辑封装在一个null对象中,并将其返回给客户端而不是null值。这样,客户端的代码就不再需要知道某个实例是否为null

Such an approach follows general object-oriented principles, like Tell-Don’t-Ask.

这样的方法遵循一般的面向对象的原则,如Tell-Don’t-Ask>。

To better understand when we should use the Null Object Pattern, let’s imagine we have to implement CustomerDao interface defined as follows:

为了更好地理解什么时候我们应该使用空对象模式,让我们设想一下,我们必须实现CustomerDao接口,定义如下。

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

Most of the developers would return Collections.emptyList() from findByNameAndLastname() in case none of the customers matches the provided search criteria. This is a very good example of following the Null Object Pattern.

大多数开发者会从findByNameAndLastname()返回Collections.emptyList(),以防没有一个客户符合提供的搜索条件。这是一个遵循Null对象模式的很好的例子。

In contrast, the getById() should return the customer with the given id. Someone calling this method expects to get the specific customer entity. In case no such customer exists we should explicitly return null to signal there is something wrong with the provided id. 

相反,getById()应该返回给定id的客户。调用这个方法的人希望得到特定的客户实体。如果没有这样的客户存在,我们应该明确地返回null,以示所提供的id有问题。

As with all other patterns, we need to consider our specific use case before blindly implementing the Null Object Pattern. Otherwise, we may unintentionally introduce some bugs in our code that will be hard to find.

和其他所有的模式一样,在盲目地实现Null对象模式之前,我们需要考虑我们的具体用例。否则,我们可能会无意中在代码中引入一些难以发现的错误。

6. Conclusion

6.结语

In this article, we learned what the Null Object Pattern is and when we may use it. We also implemented a simple example of the design pattern.

在这篇文章中,我们了解了什么是Null对象模式,以及什么时候可以使用它。我们还实现了该设计模式的一个简单例子。

As usual, all the code samples are available over on GitHub.

像往常一样,所有的代码样本都可以在GitHub上找到。