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

Кажется, растет сообщество людей, говорящих, что вы никогда не должны возвращать null и вместо этого всегда должны использовать шаблон Null Object. Я вижу полезность NOP при использовании коллекции/карты/массива или вызове логических функций, таких как isAuthenticated(), который показан здесь.

Я не нашел ничего, что было бы полностью убедительным. Потерпите меня, пока я пытаюсь привести в порядок свои мысли.

Насколько я понимаю, вместо возврата нулевого объекта вы возвращаете действительный объект, который был «обнулен».

Так, например, клиент сделал бы вызов, чтобы получить объект:

Car car = getCar();

Если вы не используете NOP, вам нужно будет проверить, является ли объект, возвращаемый из getCar(), нулевым, прежде чем вызывать какие-либо методы для него:

if (car != null){
    color = car.getColor();
    doScreenStuff(color);
   }

Используя NOP, вместо того, чтобы getCar() возвращал null, теперь он возвращает объект, который был фактически «обнулен». Так что теперь нам больше не нужно делать if (car != null) и можно просто запросить цвет. Итак, я предполагаю, что наш «обнуленный» объект вернет «none», когда мы вызовем цвет.

Как это помогает? Кажется, что продвижение вперед и вызов методов для пустого объекта причиняет столько же боли, сколько и простая проверка нуля. Теперь, когда пришло время отображать информацию, нам нужно проверить, что цвет не равен «none», что высота не равна 0 или любым другим значениям, которые у вас есть. Таким образом, вместо того, чтобы в начале обработки проверять, является ли автомобиль нулевым, вы впоследствии проверяете, является ли объект автомобиля реальным автомобилем или его заменой. т.е. мы не хотим отображать кучу пустых объектов, поэтому нам нужен какой-то способ отфильтровать все наши пустые объекты.

Эта фильтрация является дополнительным шагом, как и вызов if (car != null). Единственная разница в том, что при проверке null мы можем остановить обработку, как только обнаружим, что объект car имеет значение null, сгенерировав исключение, тогда как при NOP мы вызываем методы для пустого объекта и продолжаем пыхтеть, пока не придет время отображаем объект, и в этот момент мы отфильтровываем пустые места. Кроме того, вам нужно знать значения, возвращаемые вашим пустым объектом. т.е. возвращает ли getColor() «нет» или «пусто».

Очевидно, должно быть что-то, что я упускаю из виду. Заранее спасибо.


person TigerBear    schedule 08.06.2015    source источник


Ответы (6)


Ответ Мэтта Патнэма точен, и я его поддерживаю. Я бы добавил вот что: понятие «нулевой объект», когда вы его анализируете, похоже, сводится к математическому понятию < сильный>моноид. Вы можете думать об этом так: моноид — это тип, который имеет обе эти вещи:

  1. "Добавление", "суммирование" или аналогичная операция, которая должна быть ассоциативной: a.op(b).op(c) совпадает с a.op(b.op(c)).
  2. "Пустое", "нулевое" или "нулевое" значение, которое действует как нейтральный элемент или элемент идентификации операции.

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

Теперь проблема, с которой вы сталкиваетесь в своем примере Car, заключается в том, что Car на самом деле не является моноидом; нет понятия «пустая машина» или «нейтральная машина», и на самом деле нет разумной операции, которую вы могли бы использовать для объединения двух Car в один.

Таким образом, вы правильно получаете рекомендацию использовать что-то вроде Java 8 Optional< /а>. И фишка в том, что независимо от типа T, Optional<T> — это моноид:

  1. The monoid's "combine" operation is "pick the first value if it's not empty, otherwise pick the second value":
    • x || empty = x
    • empty || x = x
  2. Нейтральный элемент — Optional.empty(), потому что Optional.empty().orElse(anything) — это то же самое, что и anything.

Таким образом, Optional<T> — это оболочка, которая добавляет нулевой объект к типам, таким как Car, у которых его нет. Метод Optional<T>.orElse(T value), который представляет собой слегка переработанную версию моноида "выбрать первое значение, отличное от empty".

person Luis Casillas    schedule 08.06.2015

Шаблон нулевого объекта имеет смысл только тогда, когда есть разумное функциональное значение для нулевого объекта. Цель состоит не в том, чтобы отложить null, как вы описали, а в том, чтобы полностью исключить идею null, представляя небытие или пустоту с помощью фактического фрагмента данных, который все еще функционирует. Например, естественный случай дыр в древовидной структуре, как описано в статье Википедии.

Нулевая машина не имеет смысла. В этом случае кажется более подходящим, чтобы getCar() возвращал Optional<Car>.

person MattPutnam    schedule 08.06.2015

Если вы не видите в этом смысла, то, вероятно, эта парадигма вам не подходит. Вся идея объектно-ориентированного программирования состоит в том, чтобы упростить ВАС. Не попадайте в ловушку, думая, что вам нужно перенять чью-то сложную систему, основанную на шаблонах. Часто требуется значительный объем работы, чтобы изучить различные шаблоны и эффективно их использовать, поэтому лучше дорасти до них, а не пытаться заставить себя их использовать.

Что касается этого конкретного шаблона, то он предполагает определенный стиль программирования, который может вам не подойти. Я бы никогда не использовал его сам, потому что я возвращаю нули как допустимые значения (отсутствующие данные), которые обрабатываются по-разному в каждом случае, поэтому «централизованная обработка» не имеет для меня смысла. Когда я возвращаю логические значения, я использую примитивы.

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

person Tyler Durden    schedule 08.06.2015
comment
Я думаю ты прав. Я всегда слишком беспокоюсь об использовании неправильного шаблона при программировании. В этом случае, кажется, есть люди, которые предполагают, что вы делаете что-то совершенно неправильно, если возвращаете нули: yegor256.com/2014/05/13/why-null-is-bad.html - person TigerBear; 09.06.2015

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

Если многие сервисы используют CarFactory, намного проще, чтобы Carfactory обрабатывал нулевой результат, чем каждый отдельный сервис. Кроме того, он гарантирует, что каждый нулевой результат обрабатывается одинаково, будь то ничегонеделание или определенная логика. Недостатком является то, что если это не обрабатывается правильно, это может привести к некоторым временным запутанным ошибкам (особенно потому, что исключения с нулевым указателем кричат ​​​​громко и гордо).

Я действительно больше не использую его. Существуют альтернативы использованию нулевых проверок, например использование Java 8 Optional. Есть люди, которые за и против этого, и это ни в коем случае не замена шаблону нулевого объекта.

String result = Optional.ofNullable(someInteger)
   .map(Integer::valueOf)
   .map(i -> i + 1)
   .map(Object::toString)
   .orElse("number not present");

System.out.println(result);
person Derek_M    schedule 08.06.2015
comment
Шаблон нулевого объекта и необязательный объект не служат одной и той же цели. Необязательный используется для указания того, что значение может присутствовать или не присутствовать. Шаблон нулевого объекта просто кодирует тот факт, что может быть объект «ничего там», который все еще имеет полезное поведение. - person Cubic; 08.06.2015
comment
Извините, возможно, я не ясно выразился, я говорил, что альтернатива проверкам на нуль является необязательной, и тогда это ни в коем случае не является заменой шаблона нулевого объекта для шаблона нулевого объекта. Я не использовал шаблон нулевого объекта некоторое время, потому что у меня не было недавнего варианта использования, но я ни в коем случае не предлагаю, чтобы необязательные параметры заменяли шаблон нулевого объекта. - person Derek_M; 08.06.2015

Шаблон проектирования NULL заполняет ОТСУТСТВИЕ объекта поведением ПО УМОЛЧАНИЮ и должен использоваться только тогда, когда один объект взаимодействует с другим.

Шаблон проектирования NULL не предназначен для замены обработки исключений NULL. Это одно из побочных преимуществ шаблона проектирования NULL, но цель состоит в том, чтобы обеспечить поведение по умолчанию.

Например, рассмотрим приведенный ниже пример псевдокода. Это простой класс Customer, который делегирует расчет скидки объекту скидки.

class Customer {
    IDiscount dis = new NormalDiscount();
    public double CalculateDiscount() {
        return dis.Calculate();
    }
    // Code removed for simplicity
}

Теперь предположим, что мы хотим создать клиентов, которые просрочили платежи. Таким образом, мы наследуем от вышеприведенного класса, так как многие свойства и поведение одинаковы, но нам не нужен расчет скидки, потому что клиент по умолчанию не имеет права на скидки. Итак, мы делаем объект скидки нулевым, но это проблема, потому что теперь расчет скидки будет давать сбой из-за объекта скидки NULL.

class DefaultedCustomer : Customer {
    IDiscount dis = null;
    public double CalculateDiscount() {
        return dis.Calculate(); <---- This will crash in parent
    }
    // Code removed for simplicity
}

Таким образом, один из способов, с помощью которого разработчики решают эту проблему, заключается в том, что я проверяю значения NULL и возвращаю ноль. Если закрыть глаза и подумать логически, то это на самом деле бизнес-сценарий, где нет скидок для дефолтного клиента.

Таким образом, вместо того, чтобы исправлять это технически, проверяя NULL, мы можем создать класс DEFAULT DISCOUNT BEHAVIOR, как показано ниже, который возвращает нулевую скидку и использует то же самое для клиента по умолчанию. Это более чисто, чем проверка NULL.

public class DefaultDiscount : IDiscount {
    public void Calculate() {
        return 0;
    }
}

Нулевые значения необходимы, но для обработки исключений, а не для исправления отсутствия объекта в сотрудничестве, как показано выше. Шаблон проектирования NULL не имеет смысла, если нет совместной работы.

Из-за шаблона проектирования NULL проверки NULL избегаются, но это скорее последующий эффект и побочное преимущество, а не основное намерение.

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

person Shivprasad Koirala    schedule 22.11.2015

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

При возврате автомобиля вы должны проверить if(car != null). Точно так же при доступе к необязательным вы ДОЛЖНЫ проверить if(car.isPresent()), затем car.get().

Выполнение get() без проверки присутствия неприемлемо, и это можно легко идентифицировать и избежать использования конфигурации checkstyle для выдачи ошибок.

Вы можете задать вопрос. Какое преимущество мы получаем из-за этого, если мы проверяем его присутствие в обоих шаблонах?

Ответ заключается в том, чтобы знать, что вы должны сделать проверку, прежде чем использовать его. Если вы строго следуете практике возврата Optional везде, где он может быть обнуляемым, вам не нужно проверять наличие нулей там, где он не является необязательным.

Он действует как программный способ документирования методов, который внутренне помогает обеспечить его проверку с помощью стилей проверки.

person Harish    schedule 04.05.2016
comment
Необязательный — это не то же самое, что шаблон нулевого объекта (en.wikipedia.org/wiki/Null_Object_pattern). - person Rodrigo Ruiz; 02.12.2016
comment
Использовать Optional только для isPresent() и get() — это все равно что разогнаться до 20 миль в час на своем новеньком Lamborghini. Лучшее, что можно сделать в этой ситуации, это getCar().ifPresent(car -> doScreenStuff(car.getColor()) ) - person Michael; 09.05.2017