Перейти к основному содержимому

Введение в шаблон нулевого объекта

· 4 мин. чтения

1. Обзор

В этом кратком руководстве мы рассмотрим паттерн Null Object, частный случай паттерна стратегии . Мы опишем его цель и когда мы действительно должны рассмотреть его использование.

Как обычно, мы также приведем простой пример кода.

2. Шаблон нулевого объекта

В большинстве объектно-ориентированных языков программирования нам не разрешено использовать нулевую ссылку. Вот почему нам часто приходится писать нулевые проверки:

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

Иногда, если количество таких операторов if становится большим, код может стать уродливым, трудным для чтения и подверженным ошибкам. Вот когда может пригодиться шаблон Null Object.

Цель шаблона Null Object состоит в том, чтобы свести к минимуму такую проверку нулей . Вместо этого мы можем идентифицировать нулевое поведение и инкапсулировать его в тип, ожидаемый клиентским кодом. Чаще всего такая нейтральная логика очень проста – ничего не делать. Таким образом, нам больше не нужно иметь дело со специальной обработкой нулевых ссылок.

Мы просто можем обращаться с нулевыми объектами так же, как с любым другим экземпляром данного типа, который на самом деле содержит более сложную бизнес-логику. Следовательно, клиентский код остается чище.

Поскольку нулевые объекты не должны иметь никакого состояния, нет необходимости создавать идентичные экземпляры несколько раз. Таким образом, мы часто будем реализовывать нулевые объекты как синглтоны .

3. UML-диаграмма шаблона нулевого объекта

Посмотрим на узор визуально:

./3ac4a90c91c8a9097a2576011308a446.jpg

Как видим, мы можем выделить следующих участников:

  • Клиенту требуется экземпляр AbstractObject
  • AbstractObject определяет контракт, ожидаемый Клиентом , — он также может содержать общую логику для реализующих классов.
  • RealObject реализует AbstractObject и обеспечивает реальное поведение
  • NullObject реализует AbstractObject и обеспечивает нейтральное поведение .

4. Реализация

Теперь, когда у нас есть четкое представление о теории, давайте рассмотрим пример.

Представьте, что у нас есть приложение маршрутизатора сообщений. Каждому сообщению должен быть назначен действительный приоритет. Наша система должна направлять сообщения с высоким приоритетом на SMS-шлюз, тогда как сообщения со средним приоритетом должны направляться в очередь JMS.

Однако время от времени в наше приложение могут приходить сообщения с «неопределенным» или пустым приоритетом . Такие сообщения должны быть исключены из дальнейшей обработки.

Во-первых, мы создадим интерфейс маршрутизатора :

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

Далее давайте создадим две реализации вышеуказанного интерфейса — одну, отвечающую за маршрутизацию к 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
}
}

Наконец, давайте реализуем наш нулевой объект:

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

Теперь мы готовы собрать все части вместе. Давайте посмотрим, как может выглядеть пример клиентского кода:

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 Object, давайте представим, что нам нужно реализовать интерфейс CustomerDao , определенный следующим образом:

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

Большинство разработчиков вернут Collections.emptyList() из findByNameAndLastname() в случае, если ни один из клиентов не соответствует предоставленным критериям поиска. Это очень хороший пример использования шаблона Null Object.

Напротив, get ById() должен возвращать клиента с заданным идентификатором. Кто-то, вызывающий этот метод, ожидает получить конкретную сущность клиента. Если такого клиента не существует, мы должны явно вернуть null , чтобы сигнализировать о том, что с предоставленным идентификатором что-то не так.

Как и в случае со всеми другими шаблонами, нам нужно рассмотреть наш конкретный вариант использования, прежде чем слепо внедрять шаблон Null Object . В противном случае мы можем непреднамеренно внести некоторые ошибки в наш код, которые будет трудно найти.

6. Заключение

В этой статье мы узнали, что такое шаблон нулевого объекта и когда мы можем его использовать. Мы также реализовали простой пример шаблона проектирования.

Как обычно, все образцы кода доступны на GitHub .