Jump to content

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

(Перенаправлено с нулевого объекта )

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

Мотивация

[ редактировать ]

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

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

Описание

[ редактировать ]

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

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

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

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

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

class node {
    node left
    node right
}

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

function tree_size(node) {
    return 1 + tree_size(node.left) + tree_size(node.right)
}

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

function tree_size(node) {
    set sum = 1
    if node.left exists {
        sum = sum + tree_size(node.left)
    }
    if node.right exists {
        sum = sum + tree_size(node.right)
    }
    return sum
}

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

function tree_size(node) {
    return 1 + tree_size(node.left) + tree_size(node.right)
}
function tree_size(null_node) {
    return 0
}

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

Связь с другими шаблонами

[ редактировать ]

Его можно рассматривать как частный случай шаблона «Состояние» и шаблона «Стратегия» .

Это не шаблон из Design Patterns , но он упоминается в книге Мартина Фаулера «Рефакторинг». [ 4 ] и «Рефакторинг по шаблонам» Джошуа Кериевского [ 5 ] как Insert Null Object рефакторинг .

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

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

[ редактировать ]

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

// compile as Console Application, requires C# 6.0 or higher
using System;

namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            string str = "test"; 
            Console.WriteLine(str?.Length);
            Console.ReadKey();
        }
    }
}
// The output will be:
// 4

Методы расширения и объединение Null

[ редактировать ]

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

// compile as Console Application, requires C# 3.0 or higher
using System;
using System.Linq;
namespace MyExtensionWithExample {
    public static class StringExtensions { 
        public static int SafeGetLength(this string valueOrNull) { 
            return (valueOrNull ?? string.Empty).Length; 
        }
    }
    public static class Program {
        // define some strings
        static readonly string[] strings = new [] { "Mr X.", "Katrien Duck", null, "Q" };
        // write the total length of all the strings in the array
        public static void Main(string[] args) {
            var query = from text in strings select text.SafeGetLength(); // no need to do any checks here
            Console.WriteLine(query.Sum());
        }
    }
}
// The output will be:
// 18

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

[ редактировать ]

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

#include <iostream>

class Animal {
 public:
  virtual ~Animal() = default;

  virtual void 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 указатель или ссылка.

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

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

// Function which requires an |Animal| instance, and will not accept null.
void DoSomething(const Animal& animal) {
  // |animal| may never be null here.
}

// Function which may accept an |Animal| instance or null.
void DoSomething(const Animal* animal) {
  // |animal| may be null.
}

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

/* Null object pattern implementation:
 */
using System;

// Animal interface is the key to compatibility for Animal implementations below.
interface IAnimal
{
	void MakeSound();
}

// Animal is the base case.
abstract class Animal : IAnimal
{
	// A shared instance that can be used for comparisons
	public static readonly IAnimal Null = new NullAnimal();
	
	// The Null Case: this NullAnimal class should be used in place of C# null keyword.
	private class NullAnimal : Animal
	{
		public override void MakeSound()
		{
			// Purposefully provides no behaviour.
		}
	}
	public abstract void MakeSound();
}

// Dog is a real animal.
class Dog : Animal
{
	public override void MakeSound()
	{
		Console.WriteLine("Woof!");
	}
}

/* =========================
 * Simplistic usage example in a Main entry point.
 */
static class Program
{
	static void Main()
	{
		IAnimal dog = new Dog();
		dog.MakeSound(); // outputs "Woof!"

		/* Instead of using C# null, use the Animal.Null instance.
         * This example is simplistic but conveys the idea that if the Animal.Null instance is used then the program
         * will never experience a .NET System.NullReferenceException at runtime, unlike if C# null were used.
         */
		IAnimal unknown = Animal.Null;  //<< replaces: IAnimal unknown = null;
		unknown.MakeSound(); // outputs nothing, but does not throw a runtime exception        
	}
}

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

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

Общий Лисп

[ редактировать ]

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

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

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

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

;; empty dog class

(defclass dog () ())

;; a dog object makes a sound by barking: woof! is printed on standard output
;; when (make-sound x) is called, if x is an instance of the dog class.

(defmethod make-sound ((obj dog))
  (format t "woof!~%"))

;; allow (make-sound nil) to work via specialization to null class.
;; innocuous empty body: nil makes no sound.
(defmethod make-sound ((obj null)))

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

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

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

class Dog
  def sound
    "bark"
  end
end
 
class NilAnimal
  def sound(*); end
end

def get_animal(animal=NilAnimal.new)
  animal
end

get_animal(Dog.new).sound
 => "bark"
get_animal.sound
 => nil

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

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

class Dog {
  sound() {
    return 'bark';
  }
}

class NullAnimal {
  sound() {
    return null;
  }
}

function getAnimal(type) {
  return type === 'dog' ? new Dog() : new NullAnimal();
}

['dog', null].map((animal) => getAnimal(animal).sound());
// Returns ["bark", null]
public interface Animal {
	void makeSound();
}

public class Dog implements Animal {
	public void makeSound() {
		System.out.println("woof!");
	}
}

public class NullAnimal implements Animal {
	public void makeSound() {
        // silence...
	}
}

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

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

interface Animal
{
    public function makeSound();
}

class Dog implements Animal
{
    public function makeSound()
    {
        echo "Woof...\n";
    }
}

class Cat implements Animal
{
    public function makeSound()
    {
        echo "Meowww...\n";
    }
}

class NullAnimal implements Animal
{
    public function makeSound()
    {
        // silence...
    }
}

$animalType = 'elephant';

function makeAnimalFromAnimalType(string $animalType): Animal
{
    switch ($animalType) {
        case 'dog':
            return new Dog();
        case 'cat':
            return new Cat();
        default:
            return new NullAnimal();
    }
}

makeAnimalFromAnimalType($animalType)->makeSound(); // ..the null animal makes no sound

function animalMakeSound(Animal $animal): void
{
    $animal->makeSound();
}

foreach ([
             makeAnimalFromAnimalType('dog'),
             makeAnimalFromAnimalType('NullAnimal'),
             makeAnimalFromAnimalType('cat'),
         ] as $animal) {
    // That's also reduce null handling code
    animalMakeSound($animal);
}

Визуальный Бейсик .NET

[ редактировать ]

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

Public Class Animal
    Public Shared ReadOnly Empty As Animal = New AnimalEmpty()

    Public Overridable Sub MakeSound()
        Console.WriteLine("Woof!")
    End Sub
End Class

Friend NotInheritable Class AnimalEmpty
    Inherits Animal

    Public Overrides Sub MakeSound()
        ' 
    End Sub
End Class

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

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

См. также

[ редактировать ]
  1. ^ Кюне, Томас (1996). «Пустое значение». Материалы Первой Международной конференции по объектно-ориентированным технологиям, Белые объектно-ориентированные ночи 1996 (WOON'96), Санкт-Петербург, Россия .
  2. ^ Вульф, Бобби (1998). «Нулевой объект». В Мартине, Роберте; Риле, Дирк; Бушманн, Франк (ред.). Языки шаблонов проектирования программ 3 . Аддисон-Уэсли.
  3. ^ «Работа с Объектами (Работа с нулем)» . Библиотека разработчиков iOS . Apple, Inc. 13 декабря 2012 г. Проверено 19 мая 2014 г.
  4. ^ Фаулер, Мартин (1999). Рефакторинг. Улучшение дизайна существующего кода . Аддисон-Уэсли. ISBN  0-201-48567-2 .
  5. ^ Кериевский, Джошуа (2004). Рефакторинг в шаблоны . Аддисон-Уэсли. ISBN  0-321-21335-1 .
  6. ^ Мартин, Роберт (2002). Гибкая разработка программного обеспечения: принципы, закономерности и практики . Пирсон Образование. ISBN  0-13-597444-5 .
  7. ^ Фаулер, Мартин (1999). Рефакторинг, с. 216.
[ редактировать ]
Arc.Ask3.Ru: конец переведенного документа.
Arc.Ask3.Ru
Номер скриншота №: 317a6ee768777bb3639aa851f89a697b__1723045860
URL1:https://arc.ask3.ru/arc/aa/31/7b/317a6ee768777bb3639aa851f89a697b.html
Заголовок, (Title) документа по адресу, URL1:
Null object pattern - Wikipedia
Данный printscreen веб страницы (снимок веб страницы, скриншот веб страницы), визуально-программная копия документа расположенного по адресу URL1 и сохраненная в файл, имеет: квалифицированную, усовершенствованную (подтверждены: метки времени, валидность сертификата), открепленную ЭЦП (приложена к данному файлу), что может быть использовано для подтверждения содержания и факта существования документа в этот момент времени. Права на данный скриншот принадлежат администрации Ask3.ru, использование в качестве доказательства только с письменного разрешения правообладателя скриншота. Администрация Ask3.ru не несет ответственности за информацию размещенную на данном скриншоте. Права на прочие зарегистрированные элементы любого права, изображенные на снимках принадлежат их владельцам. Качество перевода предоставляется как есть. Любые претензии, иски не могут быть предъявлены. Если вы не согласны с любым пунктом перечисленным выше, вы не можете использовать данный сайт и информация размещенную на нем (сайте/странице), немедленно покиньте данный сайт. В случае нарушения любого пункта перечисленного выше, штраф 55! (Пятьдесят пять факториал, Денежную единицу (имеющую самостоятельную стоимость) можете выбрать самостоятельно, выплаичвается товарами в течение 7 дней с момента нарушения.)