Шаблон пустого объекта - Null object pattern

В объектно-ориентированный компьютерное программирование, а нулевой объект - это объект без ссылочного значения или с определенным нейтральным («нулевым») поведением. Нулевой объект шаблон дизайна описывает использование таких объектов и их поведение (или его отсутствие). Впервые он был опубликован в Шаблонные языки разработки программ цикл книг.[1]

Мотивация

В большинстве объектно-ориентированных языков, таких как Ява или же C #, Рекомендации может быть ноль. Эти ссылки необходимо проверить, чтобы убедиться, что они не равны нулю, прежде чем вызывать какие-либо методы, потому что методы обычно не могут быть вызваны для пустых ссылок.

В Язык Objective-C использует другой подход к этой проблеме и ничего не делает при отправке сообщения ноль; если ожидается возвращаемое значение, ноль (для объектов), 0 (для числовых значений), НЕТ (за BOOL значения) или структура (для типов структур) со всеми ее членами, инициализированными для ноль/0/НЕТ/ возвращается структура с нулевой инициализацией.[2]

Описание

Вместо использования пустая ссылка чтобы передать отсутствие объекта (например, несуществующего клиента), используется объект, который реализует ожидаемый интерфейс, но тело метода которого пусто. Преимущество этого подхода перед рабочей реализацией по умолчанию состоит в том, что нулевой объект очень предсказуем и не имеет побочных эффектов: он делает ничего.

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

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

Шаблон нулевого объекта также можно использовать в качестве заглушки для тестирования, если определенная функция, такая как база данных, недоступна для тестирования.

Пример

Учитывая двоичное дерево, с этой структурой узла:

учебный класс узел {узел слева узел справа}

Можно рекурсивно реализовать процедуру размера дерева:

функция tree_size (узел) {return 1 + tree_size (node.left) + tree_size (node.right)}

Поскольку дочерние узлы могут не существовать, необходимо изменить процедуру, добавив проверки отсутствия или нулевые проверки:

функция tree_size (узел) {установить сумму = 1 если node.left существует {sum = sum + tree_size (node.left)} если node.right существует {sum = sum + tree_size (node.right)} return sum}

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

функция tree_size (узел) {return 1 + tree_size (node.left) + tree_size (node.right)}
функция tree_size (null_node) {return 0}

Это отделяет обычную логику от обработки особых случаев и упрощает понимание кода.

Отношение к другим паттернам

Его можно рассматривать как частный случай Государственный образец и Шаблон стратегии.

Это не узор из Шаблоны проектирования, но упоминается в Мартина Фаулера Рефакторинг[3] и рефакторинг Джошуа Кериевского к шаблонам[4] как Вставить нулевой объект рефакторинг.

Глава 17 Роберт Сесил Мартин Гибкая разработка программного обеспечения: принципы, шаблоны и практики[5] посвящен выкройке.

Альтернативы

Начиная с C # 6.0 можно использовать знак «?». оператор (он же нулевой условный оператор ), который будет просто оценивать как null, если его левый операнд равен нулю.

// компилировать как консольное приложение, требуется C # 6.0 или вышес помощью Система;пространство имен ConsoleApplication2{    учебный класс Программа    {        статический пустота Главный(нить[] аргументы)        {            нить ул = "тест";             Консоль.WriteLine(ул?.Длина);            Консоль.ReadKey();        }    }}// Результат будет:// 4

Методы расширения и объединение нулей

В некоторых Microsoft .NET языки, Методы расширения может использоваться для выполнения так называемого «нулевого объединения». Это связано с тем, что методы расширения могут вызываться для значений NULL, как если бы это касается «вызова метода экземпляра», тогда как на самом деле методы расширения являются статическими. Можно заставить методы расширения проверять нулевые значения, тем самым освобождая код, который их использует, от необходимости делать это. Обратите внимание, что в приведенном ниже примере используется C # Оператор объединения с нулевым значением чтобы гарантировать безошибочный вызов, где также можно было бы использовать более приземленное if ... then ... else. Следующий пример работает только в том случае, если вас не волнует существование null или вы относитесь к нулевой и пустой строке одинаково. Это предположение может не выполняться в других приложениях.

// компилировать как консольное приложение, требуется C # 3.0 или вышес помощью Система;с помощью System.Linq;пространство имен MyExtensionWithExample {    общественный статический учебный класс StringExtensions {         общественный статический int SafeGetLength(это нить valueOrNull) {             возвращаться (valueOrNull ?? нить.Пустой).Длина;         }    }    общественный статический учебный класс Программа {        // определяем несколько строк        статический только чтение нить[] струны = новый [] { «Мистер X.», "Катриен Дак", ноль, "Q" };        // записываем общую длину всех строк в массиве        общественный статический пустота Главный(нить[] аргументы) {            вар запрос = из текст в струны Выбрать текст.SafeGetLength(); // здесь не нужно делать никаких проверок            Консоль.WriteLine(запрос.Сумма());        }    }}// Результат будет:// 18

На разных языках

C ++

Язык со статически типизированными ссылками на объекты иллюстрирует, как нулевой объект становится более сложным шаблоном:

#включают <iostream>учебный класс Животное { общественный:  виртуальный ~Животное() = дефолт;  виртуальный пустота MakeSound() const = 0;};учебный класс Собака : общественный Животное { общественный:  виртуальный пустота MakeSound() const отменять { стандартное::cout << "гав!" << стандартное::конец; }};учебный класс NullAnimal : общественный Животное { общественный:  виртуальный пустота MakeSound() const отменять {}};

Здесь идея состоит в том, что бывают ситуации, когда указатель или ссылка на Животное объект требуется, но нет подходящего объекта. Пустая ссылка невозможна в стандартном C ++. Нуль Животное * указатель возможен и может быть полезен в качестве заполнителя, но не может использоваться для прямой отправки: a-> MakeSound () неопределенное поведение, если а - нулевой указатель.

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

Специальный нулевой класс должен быть создан для каждой иерархии классов, которая должна иметь нулевой объект, поскольку NullAnimal бесполезен, когда нужен нулевой объект в отношении некоторых Виджет базовый класс, не связанный с Животное иерархия.

Обратите внимание, что ОТСУТСТВИЕ нулевого класса вообще является важной особенностью, в отличие от языков, где «все является ссылкой» (например, Java и C #). В C ++ дизайн функции или метода может явно указывать, разрешено значение null или нет.

// Функция, для которой требуется | Animal | экземпляр и не примет значение null.пустота Сделай что-нибудь(const Животное& животное) {  // | животное | здесь может никогда не быть нулевым.}// Функция, которая может принимать | Животное | instance или null.пустота Сделай что-нибудь(const Животное* животное) {  // | животное | может быть нулевым.}

C #

C # - это язык, на котором может быть правильно реализован шаблон нулевого объекта. В этом примере показаны объекты-животные, отображающие звуки, и экземпляр NullAnimal, используемый вместо ключевого слова C # null. Нулевой объект обеспечивает согласованное поведение и предотвращает исключение нулевой ссылки во время выполнения, которое могло бы возникнуть, если бы вместо него было использовано ключевое слово C # null.

/ * Реализация шаблона нулевого объекта: */с помощью Система;// Интерфейс Animal является ключом к совместимости для реализаций Animal ниже.интерфейс Яживотное{	пустота MakeSound();}// Животное - это базовый случай.Абстрактные учебный класс Животное : Яживотное{	// Общий экземпляр, который можно использовать для сравнения	общественный статический только чтение Яживотное Ноль = новый NullAnimal();		// Случай Null: этот класс NullAnimal следует использовать вместо ключевого слова C # null.	частный учебный класс NullAnimal : Животное	{		общественный отменять пустота MakeSound()		{			// Целенаправленно не обеспечивает никакого поведения.		}	}	общественный Абстрактные пустота MakeSound();}// Собака - настоящее животное.учебный класс Собака : Яживотное{	общественный пустота MakeSound()	{		Консоль.WriteLine("Гав!");	}}/* ========================= * Упрощенный пример использования в главной точке входа. */статический учебный класс Программа{	статический пустота Главный()	{		Яживотное собака = новый Собака();		собака.MakeSound(); // выводит "Гав!"		/ * Вместо использования C # null используйте экземпляр Animal.Null.         * Этот пример упрощен, но передает идею о том, что если используется экземпляр Animal.Null, то программа         * никогда не будет возникать исключение .NET System.NullReferenceException во время выполнения, в отличие от использования C # null.         */		Яживотное неизвестный = Животное.Ноль;  // << заменяет: IAnimal unknown = null;		неизвестный.MakeSound(); // ничего не выводит, но не генерирует исключение времени выполнения 	}}

Болтовня

Следуя принципу Smalltalk, все является объектом, отсутствие объекта само моделируется объектом, называемым ноль. Например, в GNU Smalltalk класс ноль является UndefinedObject, прямой потомок Объект.

Любая операция, которая не может вернуть разумный объект для своей цели, может вернуть ноль вместо этого, избегая особого случая возврата «нет объекта». Этот метод имеет преимущество простоты (отсутствие необходимости в особом случае) по сравнению с классическим подходом «нулевой», «без объекта» или «нулевой ссылки». Особенно полезные сообщения для использования с ноль находятся isNil или же ifNil:, которые делают практичным и безопасным рассмотрение возможных ссылок на ноль в программах Smalltalk.

Common Lisp

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

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

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

ЗАКРЫТЬ

В Common Lisp объект ноль это единственный экземпляр специального класса ноль. Это означает, что метод может быть специализирован для ноль class, тем самым реализуя шаблон проектирования null. То есть, по сути, он встроен в объектную систему:

;; пустой класс собак(defclass собака () ());; объект собаки издает звук лаем: гав! выводится на стандартный вывод;; when (make-sound x) вызывается, если x является экземпляром класса dog.(defmethod издавать звук ((объект собака))  (формат т "гав! ~%"));; allow (make-sound nil) работать через специализацию нулевого класса.;; безобидное пустое тело: nil не издает ни звука.(defmethod издавать звук ((объект ноль)))

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

Схема

В отличие от Common Lisp и многих диалектов Lisp, диалект Scheme не имеет значения nil, которое работает таким образом; функции машина и CDR не может применяться к пустому списку; Поэтому код приложения схемы должен использовать пустой? или же пара? функции предиката, чтобы обойти эту ситуацию, даже в ситуациях, когда очень похожему Лиспу не нужно было бы различать пустые и непустые регистры благодаря поведению ноль.

Рубин

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

учебный класс Собака  def звук    "лаять"  конецконец учебный класс NilAnimal  def звук(*); конецконецdef get_animal(животное=NilAnimal.новый)  животноеконецget_animal(Собака.новый).звук => "лаять"get_animal.звук => ноль

Попытки напрямую обезьяна NilClass вместо предоставления явных реализаций дает больше неожиданных побочных эффектов, чем преимуществ.

JavaScript

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

учебный класс Собака {  звук() {    возвращаться 'лаять';  }}учебный класс NullAnimal {  звук() {    возвращаться ноль;  }}функция getAnimal(тип) {  возвращаться тип === 'собака' ? новый Собака() : новый NullAnimal();}['собака', ноль].карта((животное) => getAnimal(животное).звук());// Возвращает ["лай", ноль]

Ява

общественный интерфейс Животное {	пустота makeSound() ;}общественный учебный класс Собака орудия Животное {	общественный пустота makeSound() {		Система.из.println("гав!");	}}общественный учебный класс NullAnimal орудия Животное {	общественный пустота makeSound() {                // тишина...	}}

Этот код иллюстрирует вариант примера C ++, приведенного выше, с использованием языка Java. Как и в C ++, экземпляр нулевого класса может быть создан в ситуациях, когда ссылка на Животное объект требуется, но нет подходящего объекта. Нуль Животное объект возможен (Животное myAnimal = null;) и может быть полезен в качестве заполнителя, но не может использоваться для вызова метода. В этом примере myAnimal.makeSound (); вызовет исключение NullPointerException. Следовательно, для проверки нулевых объектов может потребоваться дополнительный код.

Шаблон нулевого объекта решает эту проблему, предоставляя специальный NullAnimal класс, который может быть создан как объект типа Животное. Как и в случае с C ++ и родственными языками, этот специальный нулевой класс должен быть создан для каждой иерархии классов, которая нуждается в нулевом объекте, поскольку NullAnimal бесполезен, когда нужен нулевой объект, который не реализует Животное интерфейс.

PHP

интерфейс Животное{    общественный функция makeSound();}учебный класс Собака орудия Животное{    общественный функция makeSound()    {         эхо "Гав ..";     }}учебный класс Кот орудия Животное{    общественный функция makeSound()    {         эхо "Мяууууу ..";     }}учебный класс NullAnimal орудия Животное{    общественный функция makeSound()    {         // тишина...    }}$ animalType = 'слон';выключатель($ animalType) {    дело 'собака':        $ животное = новый Собака();        перемена;    дело 'Кот':        $ животное = новый Кот();        перемена;    дефолт:        $ животное = новый NullAnimal();        перемена;}$ животное->makeSound(); // .. нулевое животное не издает звука

Visual Basic .NET

Следующая реализация шаблона нулевого объекта демонстрирует конкретный класс, предоставляющий соответствующий нулевой объект в статическом поле. Пустой. Этот подход часто используется в .NET Framework (String.Empty, EventArgs.Empty, Guid.Empty, так далее.).

Общественные Учебный класс Животное    Общественные Общий Только для чтения Пустой В качестве Животное = Новый ЖивотноеПустой()    Общественные Переопределяемый Sub MakeSound()        Консоль.WriteLine("Гав!")    Конец SubКонец Учебный классДруг NotInheritable Учебный класс ЖивотноеПустой    Наследует Животное    Общественные Отменяет Sub MakeSound()        '     Конец SubКонец Учебный класс

Критика

Этот шаблон следует использовать осторожно, поскольку он может привести к появлению ошибок / ошибок как при нормальном выполнении программы.[6]

Следует проявлять осторожность, чтобы не реализовывать этот шаблон только для того, чтобы избежать нулевых проверок и сделать код более читаемым, поскольку более сложный для чтения код может просто переместиться в другое место и быть менее стандартным - например, когда должна выполняться другая логика в случае, если объект предоставлен действительно нулевой объект. Обычным шаблоном для большинства языков со ссылочными типами является сравнение ссылки с одним значением, называемым null или nil. Кроме того, существует дополнительная потребность в проверке того, что ни один код нигде никогда не присваивает null вместо нулевого объекта, потому что в большинстве случаев и в языках со статической типизацией это не ошибка компилятора, если нулевой объект имеет ссылочный тип, хотя это было бы безусловно, приводят к ошибкам во время выполнения в тех частях кода, где использовался шаблон, чтобы избежать нулевых проверок. Кроме того, на большинстве языков и при условии, что может быть много нулевых объектов (т.е. нулевой объект является ссылочным типом, но не реализует одноэлементный образец тем или иным способом) проверка нулевого объекта вместо нулевого или нулевого значения приводит к накладным расходам, как и шаблон singleton, вероятно, сам при получении ссылки singleton.

Смотрите также

Рекомендации

  1. ^ Вульф, Бобби (1998). «Нулевой объект». В Мартине, Роберт; Риле, Дирк; Бушманн, Франк (ред.). Языки шаблонов проектирования программ 3. Эддисон-Уэсли.
  2. ^ «Работа с объектами (Работа с nil)». Библиотека разработчика iOS. Apple, Inc. 13 декабря 2012 г.. Получено 2014-05-19.
  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

внешняя ссылка