Открыть сервисСервис

Null Object Pattern

Null Object Pattern (с англ. — «шаблон «нулевой объект»») — это структурный шаблон проектирования, используемый в объектно-ориентированном программировании. Он предназначен для замены проверок на null (или None, nil и т. п.) вызовом методов объекта-заглушки, который реализует тот же интерфейс, что и реальный объект, но не выполняет никаких действий или возвращает нейтральные значения. Основная цель шаблона — упростить код, устранив условные конструкции вида if (obj != null) { obj.do(); } и заменив их полиморфным вызовом метода.

История и происхождение

Концепция нулевого объекта была впервые формально описана в 1996 году в книге «Pattern Languages of Program Design 3» под редакцией Роберта Мартина, Дирка Риле и Фрэнка Бушмана. Авторами соответствующего раздела выступили Бобби Вульф (Bobby Woolf) и Джон Влиссидес (John Vlissides). Идея возникла как реакция на частые ошибки, связанные с разыменованием нулевых ссылок (NullPointerException в Java, NullReferenceException в C# и т. д.), и как способ сделать код более устойчивым и читаемым. Шаблон получил широкое распространение в языках со статической типизацией, но также применим в динамически типизированных языках (Python, JavaScript).

Основная идея и принцип работы

Вместо того чтобы возвращать null из метода или передавать null в качестве аргумента, создаётся специальный класс, который реализует тот же интерфейс, что и ожидаемый объект, но все его методы либо пусты, либо возвращают «безопасные» значения по умолчанию (например, 0, пустую строку, пустой список). Клиентский код вызывает методы этого объекта, не проверяя его на null. Таким образом, условные операторы заменяются полиморфизмом.

Пример (псевдокод)

Без использования шаблона: `` class Customer { String name; Order[] orders; void printOrders() { if (orders != null) { for (Order o : orders) { System.out.println(o); } } else { System.out.println("No orders"); } } } ``

С использованием Null Object Pattern: ``` interface OrderList { void printAll(); }

class RealOrderList implements OrderList { Order[] orders; void printAll() { for (Order o : orders) { System.out.println(o); } } }

class NullOrderList implements OrderList { void printAll() { System.out.println("No orders"); } }

class Customer { String name; OrderList orders; // может быть как RealOrderList, так и NullOrderList void printOrders() { orders.printAll(); // без проверки на null } } ```

Классификация и виды

Null Object Pattern обычно не имеет строгих подтипов, но выделяют несколько вариантов его реализации:

  • Пассивный нулевой объект (Passive Null Object): Методы объекта не выполняют никаких действий. Возвращаемые значения — нейтральные (0, false, пустая строка, пустой массив). Это наиболее распространённый вариант.
  • Активный нулевой объект (Active Null Object): Методы объекта могут выполнять некоторые действия, например, записывать в лог факт обращения к нулевому объекту, генерировать исключение (если это нужно для отладки) или возвращать заранее заданное значение-заглушку.
  • Синглтон-нулевой объект (Singleton Null Object): Поскольку нулевой объект не имеет состояния, его часто реализуют как синглтон (одиночку), чтобы не создавать множество экземпляров одного и того же класса-заглушки.

Применение и значение

Null Object Pattern применяется в следующих ситуациях:

  • Упрощение кода: Устранение множества проверок на null делает код более линейным, читаемым и менее подверженным ошибкам, связанным с забытыми проверками.
  • Повышение устойчивости: Программа продолжает работать, даже если данные отсутствуют, вместо того чтобы аварийно завершаться с исключением.
  • Реализация стратегии по умолчанию: Шаблон может служить реализацией паттерна «Стратегия» (Strategy), где нулевой объект представляет собой стратегию, которая ничего не делает.
  • Композитная структура: В паттерне «Компоновщик» (Composite) нулевой объект может выступать в роли листа, который не содержит дочерних элементов.
  • Работа с коллекциями и базами данных: При запросе к базе данных, который не возвращает результатов, вместо null может быть возвращён пустой список или объект-заглушка, реализующий интерфейс результата.

Критика и ограничения

Несмотря на полезность, Null Object Pattern имеет ряд недостатков:

  • Сокрытие ошибок: Если null является результатом настоящей ошибки (например, сбой в работе базы данных), использование нулевого объекта может скрыть эту проблему, затруднив отладку. В таких случаях лучше явно выбросить исключение.
  • Усложнение иерархии классов: Для каждого интерфейса, который может принимать null, необходимо создавать отдельный класс-заглушку. Это может привести к разрастанию кодовой базы.
  • Неприменимость во всех случаях: Шаблон неэффективен, если null имеет семантическое значение, отличное от «отсутствия данных» (например, «данные ещё не загружены» или «операция запрещена»). В таких случаях проверка на null может быть более уместной.
  • Нарушение принципа подстановки Барбары Лисков (LSP): В некоторых реализациях нулевой объект может вести себя неожиданно для клиента, который ожидает реального объекта (например, методы могут не менять состояние системы). Это может считаться нарушением LSP, если клиент полагается на побочные эффекты.

Интересные факты

  • В языке программирования Java существует встроенная реализация нулевого объекта для потоков ввода-вывода — InputStream.nullInputStream() (начиная с Java 11), который возвращает пустой поток.
  • В C# для аналогичных целей часто используется Task.CompletedTask или Enumerable.Empty<T>().
  • В функциональных языках программирования (Haskell, Scala, Kotlin) вместо Null Object Pattern часто используется тип Option (или Maybe), который явно представляет наличие или отсутствие значения, что является альтернативным, более типобезопасным подходом.
  • Паттерн активно используется в фреймворках и библиотеках. Например, в Spring Framework (Россия, компания VMware) часто применяются пустые реализации бинов (No-op beans) для упрощения конфигурации.

Источники

  • Woolf, Bobby. «Null Object». Pattern Languages of Program Design 3. Addison-Wesley, 1998.
  • Gamma, Erich, et al. Design Patterns: Elements of Reusable Object-Oriented Software. Addison-Wesley, 1994.
  • Fowler, Martin. Refactoring: Improving the Design of Existing Code. Addison-Wesley, 1999.
  • Официальная документация Java SE 11 (java.io.InputStream.nullInputStream).
Заметили ошибку или не согласны с информацией в статье? Напишите нам support@bfometr.ru