Из Википедии, бесплатной энциклопедии
Перейти к навигации Перейти к поиску

В объектно-ориентированном программировании , А объект нуля является объектом, без ссылочного значения или с определенным нейтральным ( «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

На разных языках [ править ]

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 класс nilis 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 вместо нулевого объекта, потому что в большинстве случаев и в языках со статической типизацией это не ошибка компилятора, если нулевой объект имеет ссылочный тип, хотя это было бы безусловно, приведет к ошибкам во время выполнения в тех частях кода, где использовался шаблон, чтобы избежать нулевых проверок. Вдобавок ко всему, на большинстве языков и при условии, что может быть много нулевых объектов (т.е. нулевой объект является ссылочным типом, но не реализуетодноэлементный шаблон тем или иным способом), проверка нулевого объекта вместо нулевого или нулевого значения приводит к накладным расходам, как и шаблон одноэлементного объекта, вероятно, сам при получении ссылки на одноэлемент.

См. Также [ править ]

  • Обнуляемый тип
  • Тип опциона

Ссылки [ править ]

  1. ^ Вульф, Бобби (1998). «Нулевой объект». В Мартине, Роберт; Риле, Дирк; Бушманн, Франк (ред.). Языки шаблонов проектирования программ 3 . Эддисон-Уэсли.
  2. ^ «Работа с объектами (Работа с nil)» . Библиотека разработчика iOS . Apple, Inc. 2012-12-13 . Проверено 19 мая 2014 .
  3. ^ Фаулер, Мартин (1999). Рефакторинг. Улучшение дизайна существующего кода . Эддисон-Уэсли. ISBN 0-201-48567-2.
  4. ^ Кериевский, Джошуа (2004). Рефакторинг под шаблоны . Эддисон-Уэсли. ISBN 0-321-21335-1.
  5. ^ Мартин, Роберт (2002). Гибкая разработка программного обеспечения: принципы, шаблоны и практики . Pearson Education. ISBN 0-13-597444-5.
  6. ^ Фаулер, Мартин (1999). Рефакторинг с. 216

Внешние ссылки [ править ]

  • Отчет Джеффри Уокера о шаблоне нулевого объекта
  • Описание особого случая Мартином Фаулером, немного более общий шаблон
  • Повторение шаблона нулевого объекта
  • Введение рефакторинга нулевого объекта
  • SourceMaking Учебник
  • Шаблон пустого объекта в Swift