Автор оригинала: Krzysztof Woyke.
1. Обзор
В этом кратком руководстве мы рассмотрим шаблон Нулевого объекта, особый случай шаблона Стратегии . Мы опишем его назначение и когда нам действительно следует рассмотреть возможность его использования.
Как обычно, мы также приведем простой пример кода.
2. Шаблон Нулевого Объекта
В большинстве объектно-ориентированных языков программирования нам не разрешается использовать ссылку null . Вот почему мы часто вынуждены писать null проверки:
Command cmd = getCommand(); if (cmd != null) { cmd.execute(); }
Иногда, если количество таких операторов if становится большим, код может стать уродливым, трудночитаемым и подверженным ошибкам. Именно тогда шаблон Нулевого объекта может пригодиться.
Цель шаблона нулевого объекта состоит в том, чтобы свести к минимуму такого рода проверку null . Вместо этого мы можем идентифицировать поведение null и инкапсулировать его в тип, ожидаемый клиентским кодом. Чаще всего такая нейтральная логика очень проста – ничего не делать. Таким образом, нам больше не нужно иметь дело со специальной обработкой ссылок null .
Мы просто можем относиться к нулевым объектам так же, как мы относимся к любому другому экземпляру данного типа, который на самом деле содержит более сложную бизнес-логику. Следовательно, клиентский код остается более чистым.
Поскольку нулевые объекты не должны иметь никакого состояния, нет необходимости создавать идентичные экземпляры несколько раз. Таким образом, мы часто реализуем нулевые объекты как синглеты .
3. UML-диаграмма шаблона нулевого объекта
Давайте посмотрим на рисунок визуально:
Как мы видим, мы можем выделить следующих участников:
- Клиенту требуется экземпляр Абстрактного объекта
- 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(Iterablemessages) { for (Message msg : messages) { Router router = RouterFactory.getRouterForMessage(msg); router.route(msg); } } }
Как мы видим, мы обрабатываем все объекты Маршрутизатора одинаково, независимо от того, какая реализация возвращается фабрикой Маршрутизатора. Это позволяет нам сохранять наш код чистым и читаемым.
5. Когда следует использовать шаблон Нулевого объекта
Мы должны использовать шаблон нулевого объекта, когда Клиент в противном случае проверил бы null только для того, чтобы пропустить выполнение или выполнить действие по умолчанию. В таких случаях мы можем инкапсулировать нейтральную логику в нулевой объект и вернуть ее клиенту вместо значения null . Таким образом, коду клиента больше не нужно знать, является ли данный экземпляр нулевым или нет.
Такой подход следует общим объектно-ориентированным принципам, таким как Скажи-не-Спрашивай .
Чтобы лучше понять, когда мы должны использовать шаблон нулевого объекта, давайте представим, что нам нужно реализовать CustomerDAO интерфейс, определенный следующим образом:
public interface CustomerDao { CollectionfindByNameAndLastname(String name, String lastname); Customer getById(Long id); }
Большинство разработчиков вернули бы Коллекции.Пустой список() из Найти имя и фамилию() в случае, если ни один из клиентов не соответствует предоставленным критериям поиска. Это очень хороший пример следования шаблону Нулевых объектов.
В отличие от этого, get По идентификатору() должен вернуть клиенту указанный идентификатор. Кто-то, вызывающий этот метод, ожидает получить конкретную сущность клиента. В случае, если такого клиента не существует, мы должны явно вернуть null , чтобы сообщить, что с предоставленным идентификатором что-то не так.
Как и во всех других шаблонах, нам нужно рассмотреть наш конкретный вариант использования, прежде чем слепо реализовывать шаблон нулевого объекта . В противном случае мы можем непреднамеренно внести некоторые ошибки в наш код, которые будет трудно найти.
6. Заключение
В этой статье мы узнали, что такое шаблон нулевого объекта и когда мы можем его использовать. Мы также реализовали простой пример шаблона проектирования.
Как обычно, все примеры кода доступны на GitHub .