В объектно-ориентированном программировании , А объект нуля является объектом, без ссылочного значения или с определенным нейтральным ( «NULL») поведением. Шаблон проектирования нулевого объекта описывает использование таких объектов и их поведение (или его отсутствие). Он был впервые опубликован в Образца Языки программы дизайна серии книг . [1]
Мотивация [ править ]
В большинстве объектно-ориентированных языков, таких как Java или C # , ссылки могут быть нулевыми . Эти ссылки необходимо проверить, чтобы убедиться, что они не равны нулю, прежде чем вызывать какие-либо методы , потому что методы обычно не могут быть вызваны для пустых ссылок.
Язык Objective-C использует другой подход к этой проблеме и ничего не делает при отправке сообщения nil
; если ожидается возвращаемое значение, nil
(для объектов), 0 (для числовых значений), NO
(для BOOL
значений) или структура (для типов структур) со всеми ее членами, инициализированными как null
/ 0 / NO
/ структура с нулевой инициализацией. [2]
Описание [ править ]
Вместо использования нулевой ссылки для обозначения отсутствия объекта (например, несуществующего клиента) используется объект, который реализует ожидаемый интерфейс, но тело метода которого пусто. Преимущество этого подхода перед рабочей реализацией по умолчанию состоит в том, что нулевой объект очень предсказуем и не имеет побочных эффектов: он ничего не делает .
Например, функция может получить список файлов в папке и выполнить какое-либо действие над каждым из них. В случае пустой папки одним ответом может быть создание исключения или возврат пустой ссылки, а не списка. Таким образом, код, который ожидает список, должен проверить, что он действительно есть, прежде чем продолжить, что может усложнить дизайн.
Вместо этого, возвращая нулевой объект (т. Е. Пустой список), нет необходимости проверять, действительно ли возвращаемое значение является списком. Вызывающая функция может просто перебирать список как обычно, фактически ничего не делая. Однако по-прежнему можно проверить, является ли возвращаемое значение нулевым объектом (пустой список), и при желании отреагировать по-другому.
Шаблон нулевого объекта также можно использовать в качестве заглушки для тестирования, если определенная функция, такая как база данных, недоступна для тестирования.
Пример [ править ]
Учитывая двоичное дерево , с этой структурой узла:
class node { узел слева узел справа}
Можно рекурсивно реализовать процедуру размера дерева:
function tree_size (node) { вернуть 1 + tree_size (node.left) + tree_size (node.right)}
Поскольку дочерние узлы могут не существовать, необходимо изменить процедуру, добавив проверки отсутствия или нулевые проверки:
function tree_size (node) { установить сумму = 1 if node.left существует { сумма = сумма + tree_size (node.left) } if node.right существует { сумма = сумма + tree_size (node.right) } сумма возврата}
Это, однако, усложняет процедуру, смешивая проверки границ с нормальной логикой, и ее становится труднее читать. Используя шаблон нулевого объекта, можно создать специальную версию процедуры, но только для нулевых узлов:
function tree_size (node) { вернуть 1 + tree_size (node.left) + tree_size (node.right)}
function tree_size (null_node) { возврат 0}
Это отделяет обычную логику от обработки особых случаев и упрощает понимание кода.
Связь с другими паттернами [ править ]
Его можно рассматривать как частный случай государственного образца и шаблон стратегии .
Это не шаблон из шаблонов проектирования , но он упоминается в работах Мартина Фаулера « Рефакторинг» [3] и Джошуа Кериевского «Рефакторинг в шаблоны» [4] как рефакторинг « Вставить нулевой объект» .
Этому шаблону посвящена глава 17 книги Роберта Сесила Мартина « Гибкая разработка программного обеспечения: принципы, шаблоны и практики» [5] .
Альтернативы [ править ]
Начиная с C # 6.0 можно использовать знак «?». оператор (он же условный оператор с нулевым значением ), который просто будет оценивать значение NULL, если его левый операнд равен нулю.
// компилировать как консольное приложение, требуется C # 6.0 или выше using System ;пространство имен ConsoleApplication2 { класс Program { static void Main ( string [] args ) { string str = "test" ; Консоль . WriteLine ( стр. ?. Длина ); Консоль . ReadKey (); } } } // Результатом будет: // 4
Методы расширения и объединение Null [ править ]
В некоторых Microsoft .NET языков, методы расширения могут быть использованы для выполнения того, что называется "нулевым коалесцирующий. Это связано с тем, что методы расширения могут быть вызваны для значений NULL, как если бы это касается «вызова метода экземпляра», в то время как на самом деле методы расширения являются статическими. Можно заставить методы расширения проверять нулевые значения, тем самым освобождая код, который их использует, от необходимости делать это. Обратите внимание, что в приведенном ниже примере используется оператор объединения C # Null, чтобы гарантировать безошибочный вызов, где он также мог бы использовать более приземленное if ... then ... else. Следующий пример работает только в том случае, если вас не волнует существование null или вы относитесь к нулевой и пустой строкам одинаково. Это предположение может не выполняться в других приложениях.
// компилировать как консольное приложение, требуется C # 3.0 или выше using System ; using System.Linq ; Пространство имен MyExtensionWithExample { общественный статический класс StringExtensions { публичный статический INT SafeGetLength ( эта строка valueOrNull ) { возвращение ( valueOrNull ?? строка . Слейте ). Длина ; } } открытый статический класс Program { // определить некоторые строки static readonly string [] strings = new [] { "Мистер X." , "Катриен Дак" , null , "Q" }; // записываем общую длину всех строк в массиве public static void Main ( string [] args ) { var query = from text in strings select text . SafeGetLength (); // здесь не нужно делать никаких проверок Console . WriteLine (запрос . Сумма ()); } } } // Результат будет: // 18
На разных языках [ править ]
В этом разделе не процитировать любые источники . Июнь 2013 г. ) ( Узнайте, как и когда удалить этот шаблон сообщения ) ( |
Эта статья, возможно, содержит оригинальные исследования . Июнь 2013 г. ) ( Узнайте, как и когда удалить этот шаблон сообщения ) ( |
C ++ [ править ]
Язык со статически типизированными ссылками на объекты иллюстрирует, как нулевой объект становится более сложным шаблоном:
#include <iostream>класс Animal { общедоступный : виртуальный ~ Animal () = по умолчанию ; виртуальная пустота MakeSound () const = 0 ; };class Dog : public Animal { public : virtual void MakeSound () const override { std :: cout << "woof!" << std :: endl ; } };class NullAnimal : public Animal { public : virtual void MakeSound () const override {} };
Здесь идея состоит в том, что бывают ситуации, когда требуется указатель или ссылка на Animal
объект, но подходящего объекта нет. Пустая ссылка невозможна в стандартном C ++. Нулевой Animal*
указатель возможен и может быть полезен в качестве заполнителя, но не может использоваться для прямой отправки: a->MakeSound()
это неопределенное поведение, если a
это нулевой указатель.
Шаблон нулевого объекта решает эту проблему, предоставляя специальный NullAnimal
класс, экземпляр которого может быть привязан к Animal
указателю или ссылке.
Специальный нулевой класс должен быть создан для каждой иерархии классов, которая должна иметь нулевой объект, поскольку a NullAnimal
бесполезен, когда нужен нулевой объект по отношению к некоторому Widget
базовому классу, не связанному с Animal
иерархией.
Обратите внимание, что ОТСУТСТВИЕ нулевого класса вообще является важной особенностью, в отличие от языков, где «все является ссылкой» (например, Java и C #). В C ++ при разработке функции или метода может быть явно указано, разрешено значение null или нет.
// Функция, для которой требуется | Animal | экземпляр и не примет значение null. void DoSomething ( const Animal & animal ) { // | animal | животное | здесь может никогда не быть нулевым. }// Функция, которая может принимать | Животное | экземпляр или ноль. void DoSomething ( const Animal * animal ) { // | animal | животное | может быть нулевым. }
C # [ править ]
C # - это язык, на котором можно правильно реализовать шаблон нулевого объекта. В этом примере показаны объекты-животные, отображающие звуки, и экземпляр NullAnimal, используемый вместо ключевого слова C # null. Нулевой объект обеспечивает согласованное поведение и предотвращает исключение нулевой ссылки во время выполнения, которое могло бы возникнуть, если бы вместо него было использовано ключевое слово C # null.
/ * Реализация шаблона нулевого объекта: * / using System ;// Интерфейс Animal является ключом к совместимости для реализаций Animal ниже. интерфейс IAnimal { void MakeSound (); }// Животное - это базовый случай. абстрактный класс Animal : IAnimal { // Общий экземпляр, который можно использовать для сравнений public static readonly IAnimal Null = new NullAnimal ();// Случай Null: этот класс NullAnimal следует использовать вместо ключевого слова C # null. частный класс NullAnimal : Animal { public override void MakeSound () { // Целенаправленно не обеспечивает никакого поведения. } } public abstract void MakeSound (); }// Собака - настоящее животное. class Dog : IAnimal { public void MakeSound () { Console . WriteLine ( «Гав!» ); } }/ * ========================= * Упрощенный пример использования в главной точке входа. * / статический класс Program { static void Main () { IAnimal dog = new Dog (); собака . MakeSound (); // выводит "Гав!"/ * Вместо использования C # null используйте экземпляр Animal.Null. * Этот пример упрощен, но передает идею о том, что если используется экземпляр Animal.Null, то программа * никогда не будет испытывать исключение .NET System.NullReferenceException во время выполнения, в отличие от случая использования C # null. * / IAnimal unknown = Животное . Null ; // << заменяет: IAnimal unknown = null; неизвестно . MakeSound (); // ничего не выводит, но не генерирует исключение времени выполнения } }
Smalltalk [ править ]
Следуя принципу Smalltalk, все является объектом , отсутствие объекта моделируется объектом, вызываемым nil
. Например, в GNU Smalltalk класс nil
is UndefinedObject
является прямым потомком Object
.
Любая операция, которая не может вернуть разумный объект для своей цели, может nil
вместо этого вернуться , таким образом избегая особого случая возврата «нет объекта», не поддерживаемого разработчиками Smalltalk. Этот метод имеет преимущество простоты (отсутствие необходимости в особом случае) по сравнению с классическим подходом «нулевой», «без объекта» или «нулевой ссылки». Особенно полезные сообщения , которые будут использоваться nil
являются isNil
, ifNil:
или ifNotNil:
,, которые делают его практичным и безопасным , чтобы иметь дело с возможными ссылками на nil
в программах Smalltalk.
Common Lisp [ править ]
В Лиспе функции могут корректно принимать специальный объект nil
, что сокращает количество специальных тестов в коде приложения. Например, хотя nil
это атом и не имеет полей, функции car
и cdr
принимают nil
и просто возвращают его, что очень полезно и приводит к сокращению кода.
Так как nil
это пустой список в Лиспе, ситуация , описанная во введении выше , не существует. Код, который возвращает nil
, возвращает то, что на самом деле является пустым списком (а не чем-либо, напоминающим пустую ссылку на тип списка), поэтому вызывающему абоненту не нужно проверять значение, чтобы увидеть, есть ли у него список.
Шаблон нулевого объекта также поддерживается при обработке нескольких значений. Если программа пытается извлечь значение из выражения, которое не возвращает значений, поведение заключается в nil
замене нулевого объекта . Таким образом (list (values))
возвращается (nil)
(одноэлементный список, содержащий ноль). (values)
Выражение не возвращает значений на всех, но так как вызов функции list
необходимо уменьшить свое выражение аргумента к значению, объект нуля автоматически замещен.
ЗАКРЫТЬ [ править ]
В Common Lisp объект nil
является единственным экземпляром специального класса null
. Это означает, что метод может быть специализирован для null
класса, тем самым реализуя шаблон проектирования null. То есть, по сути, он встроен в объектную систему:
;; пустой класс собак( defclass собака () ());; объект собаки издает звук своим лаем: гав! выводится на стандартный вывод ;; when (make-sound x) вызывается, если x является экземпляром класса dog.( defmethod make-sound (( obj dog )) ( формат t "гав! ~%" ));; allow (make-sound nil) работать через специализацию на нулевой класс. ;; безобидное пустое тело: nil не издает ни звука. ( defmethod make-sound (( obj null )))
Класс null
является подклассом symbol
класса, потому что nil
является символом. Поскольку nil
также представляет собой пустой список, он также null
является подклассом list
класса. Параметры методов специализируются на аргументе symbol
или list
, таким образом, принимают nil
аргумент. Конечно, null
можно определить более конкретную специализацию nil
.
Схема [ править ]
В отличие от Common Lisp и многих диалектов Lisp, диалект Scheme не имеет значения nil, которое работает таким образом; функции car
и cdr
не могут применяться к пустому списку; Поэтому код приложения схемы должен использовать функции предиката empty?
или, pair?
чтобы обойти эту ситуацию, даже в ситуациях, когда очень похожему Лиспу не нужно различать пустые и непустые регистры благодаря поведению nil
.
Руби [ править ]
В языках с утиным типом, таких как Ruby , наследование языка не обязательно для обеспечения ожидаемого поведения.
класс Собака def звук "лай" конец конец class NilAnimal def sound ( * ); конец конецdef get_animal ( animal = NilAnimal . new ) животное конецget_animal ( Дог . новинка ) . звук => "лай" get_animal . звук => ноль
Попытки напрямую исправить NilClass вместо предоставления явных реализаций дают больше неожиданных побочных эффектов, чем преимуществ.
JavaScript [ править ]
В языках с утиным типом, таких как JavaScript , наследование языка не обязательно для обеспечения ожидаемого поведения.
class Dog { звук () { return 'лай' ; } }class NullAnimal { звук () { return null ; } }функция getAnimal ( тип ) { возвращаемый тип === 'собака' ? new Dog () : новый NullAnimal (); }[ 'собака' , ноль ]. карта (( животное ) => getAnimal ( животное ). звук ()); // Возвращает ["лай", ноль]
Java [ править ]
открытый интерфейс Animal { void makeSound () ; }открытый класс Dog реализует Animal { public void makeSound () { System . из . println ( "гав!" ); } }открытый класс NullAnimal реализует Animal { public void makeSound () { // тишина ... } }
Этот код иллюстрирует вариант приведенного выше примера C ++ с использованием языка Java. Как и в C ++, экземпляр нулевого класса может быть создан в ситуациях, когда требуется ссылка на Animal
объект, но нет подходящего доступного объекта. Нулевой Animal
объект возможен ( Animal myAnimal = null;
) и может быть полезен в качестве заполнителя, но не может использоваться для вызова метода. В этом примере myAnimal.makeSound();
будет выброшено исключение NullPointerException. Следовательно, для проверки нулевых объектов может потребоваться дополнительный код.
Шаблон нулевого объекта решает эту проблему, предоставляя специальный NullAnimal
класс, который может быть создан как объект типа Animal
. Как и в случае с C ++ и родственными языками, этот специальный нулевой класс должен быть создан для каждой иерархии классов, которой нужен нулевой объект, поскольку a NullAnimal
бесполезен, когда нужен нулевой объект, который не реализует Animal
интерфейс.
PHP [ править ]
интерфейс Animal { общедоступная функция makeSound (); }класс Dog реализует Animal { общедоступную функцию makeSound () { echo "Woof .." ; } }класс Cat реализует Animal { общедоступную функцию makeSound () { echo "Meowww .." ; } }класс NullAnimal реализует Animal { public function makeSound () { // тишина ... } }$ animalType = 'слон' ; switch ( $ animalType ) { case 'собака' : $ animal = new Dog (); перерыв ; case 'кошка' : $ animal = new Cat (); перерыв ; по умолчанию : $ animal = new NullAnimal (); перерыв ; } $ animal -> makeSound (); // .. нулевое животное не издает звука
Visual Basic .NET [ править ]
Следующая реализация шаблона нулевого объекта демонстрирует конкретный класс, предоставляющий соответствующий ему нулевой объект в статическом поле Empty
. Такой подход часто используется в .NET Framework ( String.Empty
, EventArgs.Empty
, Guid.Empty
и т.д.).
Открытый класс Animal Public Shared ReadOnly Слейте As Animal = Новый AnimalEmpty () Открытый Overridable Sub MakeSound () консоли . WriteLine ( "Гав!" ) End Sub End ClassДруг NotInheritable Класс AnimalEmpty Inherits животных Public Overrides Sub MakeSound () ' End Sub End Class
Критика [ править ]
Этот шаблон следует использовать осторожно, поскольку он может привести к появлению ошибок / ошибок как при нормальном выполнении программы. [6]
Следует проявлять осторожность, чтобы не реализовывать этот шаблон только для того, чтобы избежать нулевых проверок и сделать код более читаемым, поскольку более сложный для чтения код может просто переместиться в другое место и быть менее стандартным, например, когда должна выполняться другая логика в случае, если объект предоставлен действительно нулевой объект. Обычным шаблоном для большинства языков со ссылочными типами является сравнение ссылки с одним значением, называемым null или nil. Кроме того, существует дополнительная потребность в проверке того, что ни один код нигде никогда не присваивает null вместо нулевого объекта, потому что в большинстве случаев и в языках со статической типизацией это не ошибка компилятора, если нулевой объект имеет ссылочный тип, хотя это было бы безусловно, приведет к ошибкам во время выполнения в тех частях кода, где использовался шаблон, чтобы избежать нулевых проверок. Вдобавок ко всему, на большинстве языков и при условии, что может быть много нулевых объектов (т.е. нулевой объект является ссылочным типом, но не реализуетодноэлементный шаблон тем или иным способом), проверка нулевого объекта вместо нулевого или нулевого значения приводит к накладным расходам, как и шаблон одноэлементного объекта, вероятно, сам при получении ссылки на одноэлемент.
См. Также [ править ]
- Обнуляемый тип
- Тип опциона
Ссылки [ править ]
- ^ Вульф, Бобби (1998). «Нулевой объект». В Мартине, Роберт; Риле, Дирк; Бушманн, Франк (ред.). Языки шаблонов проектирования программ 3 . Эддисон-Уэсли.
- ^ «Работа с объектами (Работа с nil)» . Библиотека разработчика iOS . Apple, Inc. 2012-12-13 . Проверено 19 мая 2014 .
- ^ Фаулер, Мартин (1999). Рефакторинг. Улучшение дизайна существующего кода . Эддисон-Уэсли. ISBN 0-201-48567-2.
- ^ Кериевский, Джошуа (2004). Рефакторинг под шаблоны . Эддисон-Уэсли. ISBN 0-321-21335-1.
- ^ Мартин, Роберт (2002). Гибкая разработка программного обеспечения: принципы, шаблоны и практики . Pearson Education. ISBN 0-13-597444-5.
- ^ Фаулер, Мартин (1999). Рефакторинг с. 216
Внешние ссылки [ править ]
- Отчет Джеффри Уокера о шаблоне нулевого объекта
- Описание особого случая Мартином Фаулером, немного более общий шаблон
- Повторение шаблона нулевого объекта
- Введение рефакторинга нулевого объекта
- SourceMaking Учебник
- Шаблон пустого объекта в Swift