Возможно ли создать NullObject для каждого класса? (с инструментом, конечно)
NullObjectPattern предназначен для "безопасного" (нейтрального) поведения.
Идея состоит в том, чтобы создать объект, который ничего не делает (но также не генерирует исключение NullPointerException)
Например, класс, определенный как:
class Employee {
private String name;
private int age;
public String getName(){ return name; }
public int getAge() { return age; }
}
Вызвать исключение NullPointerException в этом коде:
class Other {
Employee oscar;
String theName = oscar.getName(); // NPE
}
Что говорит NOP, вы можете иметь такой объект:
class NullEmployee extends Employee {
public static final Employee instance = new NullEmployee();
public String getName(){ return ""; }
public int getAge() { return 0; }
}
А затем используйте его в качестве значения по умолчанию.
Employee oscar = NullEmployee.instance;
Проблема возникает, когда вам нужно повторить код для каждого класса, который вы создаете, тогда решение будет иметь инструмент для его создания.
Было бы целесообразно / разумно / полезно создать такой инструмент или использовать его (если он существует)?
Возможно, при использовании AOP или DI значение по умолчанию может использоваться автоматически.
6 ответов
Для меня паттерн Null Object выглядит как плацебо. Реальный объект и нулевой объект могут иметь совершенно разные значения, но действовать очень похоже. Как и плацебо, нулевой объект заставит вас поверить, что в этом нет ничего плохого, но что-то может быть очень неправильно.
Я думаю, что это хорошая практика, чтобы терпеть неудачу рано и часто терпеть неудачу В конце концов, вы захотите провести различие между реальным объектом и нулевым объектом где-нибудь, и в этот момент это не будет отличаться от проверки на null
указатель.
Из статьи Википедии:
Преимущество этого подхода перед рабочей реализацией по умолчанию состоит в том, что Null Object очень предсказуем и не имеет побочных эффектов: он ничего не делает.
Это также не будет указывать на какие-либо проблемы. Подумайте, что произойдет, когда нулевой объект пройдет весь путь через ваше приложение. Затем в какой-то момент ваше приложение ожидает от объекта определенного поведения, которое реализация нулевого объекта не может обеспечить. В этот момент ваше приложение может аварийно завершить работу или войти в недопустимое состояние. Вам будет очень трудно проследить происхождение нулевого объекта. null
Указатель вызвал бы исключение в самом начале, обратив ваше внимание непосредственно на источник проблемы.
Единственный пример, который дает статья в Википедии, - это пустая коллекция вместо null
, Это очень хорошая практика, но паршивый пример шаблона нулевого объекта, потому что он имеет дело с коллекцией объектов, а не с одним экземпляром.
Короче говоря, я уверен, что возможно создать реализации нулевых объектов для всех ваших классов, но я настоятельно рекомендую против этого.
Я не уверен, что это хорошая идея. "NullObject" может быть полезен и в некоторых случаях имеет смысл, но иметь это для каждого класса - излишнее. Тем более, что это может потенциально затруднить диагностику некоторой ошибки (или пробелов в анализе).
Кроме того, как насчет библиотек третьей части, которые возвращают новые объекты? Не могли бы вы поставить какой-то "интерфейс" перед ними, чтобы в случае, если они возвращают ноль, вы заменили аппетитную разновидность nullObject?
Вы упоминаете, что пытаетесь автоматизировать это - не потребует ли в некоторых случаях фактического проектного решения для возврата подходящего значения? Т.е. предположим, что у вас есть объект Image, и он возвращает "ImageType" (перечисление для.gif,.jpg и т. Д.)
В этом случае ImageNullObject должен вернуть... "IMAGETYPE.GIF"? "IMAGETYPE.UNKNOWN"? Ноль?
Я думаю, вы просто умоляете опустить ошибки в слой.
Когда приложение запрашивает объект, и он является нулевым, вам нужно иметь дело с этим условием, а не просто надеяться, что оно в порядке. Если объект имеет значение null и вы пытаетесь его использовать, вы получите ошибки, так зачем же принудительно проверять всю эту ошибку на остальной части кода?
Я хотел бы, чтобы компилятор позволил определить поведение нулевого объекта со статически определенным типом. Например, если 'Foo' имеет класс 'bar':
Класс Бар Dim Value как целое число Функция Boz() как целое число Возвращаемое значение End Sub Sub Nothing.Bar() как целое число Возврат -9 End Sub Конечный класс
попытка вычислить Foo.Bar() выдаст Foo.Value, если Foo не равен нулю, или -9. Можно достичь некоторой такой функциональности, используя методы расширения, но это кажется немного странным.
За исключением тестирования, я обычно называю этот тип концептуального кодирования для Slop. Это противоположность отказоустойчивости, вы откладываете любой шанс найти состояние, для которого вы не кодировали.
Главное здесь - почему ваш объект может возвращать значение, которого нет в контракте метода? Когда вы разрабатывали и кодировали контракт метода, вы или говорили, что он может или не может вернуть ноль, верно? Так что, если вы можете вернуть ноль, это важно. Если вы не можете вернуть ноль, это важно.
Кроме того, если ссылка на объект, которую вы используете, может быть нулевой, то это значимое значение, и вы никогда не должны погружаться в код, который может получить доступ к методам этого объекта.
Вдобавок ко всему, используйте окончательные / постоянные / инвариантные переменные при каждом удобном случае и (по крайней мере, для ОО-языков изолируйте свои данные за конструктором, который может обеспечить правильное состояние). Если неизменный объект правильно устанавливает свои переменные, эти переменные не могут возвращать недопустимые значения.
За исключением экстренных патчей и тестирования, я, честно говоря, не вижу никаких оправданий для такого рода кодирования, кроме "Я не понимаю код и думаю, что он работает неправильно, но я не хочу пытаться понять его"-- ИМО, который не является оправданием для инженера.
Я думаю, что было бы сложно автоматически генерировать функциональные нулевые объекты. (Конечно, вы могли бы создать оболочки, которые затем пошли бы и заполнили саму реализацию).
В качестве быстрого примера - что такое нулевая функциональность для метода, который возвращает int
? Должен ли он возвращать -1, 0 или, возможно, какое-то другое значение? Как насчет того, что возвращает строку - опять же null
или же ""
правильный выбор? Для методов, возвращающих какой-то другой класс - если это один из ваших собственных классов, то, вероятно, вы могли бы использовать для этого класса объект Null, но как бы вы получили к нему доступ? Возможно, вы могли бы заставить каждый написанный вами класс реализовать некоторый интерфейс с аксессором для нулевого объекта, но стоит ли это того?
Даже с методами, которые возвращают void
, это не всегда тот случай, когда нулевая функциональность просто возвращается. Некоторым классам, например, может потребоваться вызвать метод для родительского / делегатского объекта, когда они сделали свое дело (что в этом случае было бы невозможным), чтобы сохранить инварианты класса.
Таким образом, в основном - даже для реализации "нулевого" поведения, вы должны быть в состоянии вывести, что это за поведение, и я ожидаю, что оно слишком продвинуто для того, чтобы инструмент мог сам себя сделать. Возможно, вы могли бы аннотировать все ваши методы чем-то вроде @NullReturns(-1)
и иметь инструмент, подобрать их и сформировать реализацию нулевого объекта, но даже это не будет охватывать каждую ситуацию, и вы также можете написать объект напрямую, а не записывать всю его функциональность в аннотациях.