1. Обзор
В этом кратком руководстве мы рассмотрим паттерн Null Object, частный случай паттерна стратегии . Мы опишем его цель и когда мы действительно должны рассмотреть его использование.
Как обычно, мы также приведем простой пример кода.
2. Шаблон нулевого объекта
В большинстве объектно-ориентированных языков программирования нам не разрешено использовать нулевую
ссылку. Вот почему нам часто приходится писать нулевые
проверки:
Command cmd = getCommand();
if (cmd != null) {
cmd.execute();
}
Иногда, если количество таких операторов if
становится большим, код может стать уродливым, трудным для чтения и подверженным ошибкам. Вот когда может пригодиться шаблон Null Object.
Цель шаблона Null Object состоит в том, чтобы свести к минимуму такую проверку нулей
. Вместо этого мы можем идентифицировать нулевое поведение и инкапсулировать его в тип, ожидаемый клиентским кодом. Чаще всего такая нейтральная логика очень проста – ничего не делать. Таким образом, нам больше не нужно иметь дело со специальной обработкой нулевых
ссылок.
Мы просто можем обращаться с нулевыми объектами так же, как с любым другим экземпляром данного типа, который на самом деле содержит более сложную бизнес-логику. Следовательно, клиентский код остается чище.
Поскольку нулевые объекты не должны иметь никакого состояния, нет необходимости создавать идентичные экземпляры несколько раз. Таким образом, мы часто будем реализовывать нулевые объекты как синглтоны .
3. UML-диаграмма шаблона нулевого объекта
Посмотрим на узор визуально:
Как видим, мы можем выделить следующих участников:
Клиенту
требуется экземпляр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 .