Шаблон нулевого объекта
В объектно-ориентированном программировании нулевой объект — это объект без ссылочного значения или с определенным нейтральным ( 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
На разных языках
[ редактировать ]![]() | Эта статья , возможно, содержит оригинальные исследования . ( июнь 2013 г. ) |
С++
[ редактировать ]Язык со статически типизированными ссылками на объекты иллюстрирует, как нулевой объект становится более сложным шаблоном:
#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
[ редактировать ]В с утиной типизацией, языках таких как 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
интерфейс.
PHP
[ редактировать ]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.
См. также
[ редактировать ]Ссылки
[ редактировать ]- ^ Кюне, Томас (1996). «Пустое значение». Материалы Первой Международной конференции по объектно-ориентированным технологиям, Белые объектно-ориентированные ночи 1996 (WOON'96), Санкт-Петербург, Россия .
- ^ Вульф, Бобби (1998). «Нулевой объект». В Мартине, Роберте; Риле, Дирк; Бушманн, Франк (ред.). Языки шаблонов проектирования программ 3 . Аддисон-Уэсли.
- ^ «Работа с Объектами (Работа с нулем)» . Библиотека разработчиков iOS . Apple, Inc. 13 декабря 2012 г. Проверено 19 мая 2014 г.
- ^ Фаулер, Мартин (1999). Рефакторинг. Улучшение дизайна существующего кода . Аддисон-Уэсли. ISBN 0-201-48567-2 .
- ^ Кериевский, Джошуа (2004). Рефакторинг в шаблоны . Аддисон-Уэсли. ISBN 0-321-21335-1 .
- ^ Мартин, Роберт (2002). Гибкая разработка программного обеспечения: принципы, закономерности и практики . Пирсон Образование. ISBN 0-13-597444-5 .
- ^ Фаулер, Мартин (1999). Рефакторинг, с. 216.