Nul objekt mønster - Null object pattern

I objektorienteret computerprogrammering er et null-objekt et objekt uden nogen refereret værdi eller med defineret neutral ("null") opførsel. Null objekt mønster beskriver anvendelser af sådanne genstande og deres adfærd (eller mangel på samme). Det blev først offentliggjort i Mønster Sprog af Program Design bogserie .

Motivering

I de fleste objektorienterede sprog, såsom Java eller C # , kan referencer være nul . Disse referencer skal kontrolleres for at sikre, at de ikke er null, før der påberåbes nogen metoder , fordi metoder typisk ikke kan påberåbes på null-referencer.

Den Objective-C-sprog tager en anden tilgang til dette problem og gør ingenting, når du sender en besked til nil; hvis der forventes en returværdi, nil(for objekter), 0 (for numeriske værdier), NO(for BOOLværdier) eller en struktur (for strukturtyper) med alle dens medlemmer initialiseret til null/ 0 / NO/ nul initialiseret struktur returneres.

Beskrivelse

I stedet for at bruge en null-reference til at formidle fravær af et objekt (for eksempel en ikke-eksisterende kunde) bruger man et objekt, der implementerer den forventede grænseflade , men hvis metodekropp er tom. Fordelen ved denne tilgang i forhold til en fungerende standardimplementering er, at et null-objekt er meget forudsigeligt og ikke har nogen bivirkninger: det gør intet .

For eksempel kan en funktion hente en liste over filer i en mappe og udføre nogle handlinger på hver. I tilfælde af en tom mappe kan et svar være at kaste en undtagelse eller returnere en null-reference snarere end en liste. Koden, der forventer en liste, skal således kontrollere, at den faktisk har en, inden den fortsætter, hvilket kan komplicere designet.

Ved at returnere et null-objekt (dvs. en tom liste) i stedet er der ikke behov for at kontrollere, at returværdien faktisk er en liste. Opkaldsfunktionen kan simpelthen gentage listen som normal og effektivt ikke gøre noget. Det er dog stadig muligt at kontrollere, om returværdien er et null-objekt (en tom liste) og reagere forskelligt, hvis det ønskes.

Nul-objektmønsteret kan også bruges til at fungere som en stub til test, hvis en bestemt funktion såsom en database ikke er tilgængelig til test.

Eksempel

Givet et binært træ med denne nodestruktur:

class node {
    node left
    node right
}

Man kan implementere en træstørrelsesprocedure rekursivt:

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

Da underordnede noder muligvis ikke eksisterer, skal man ændre proceduren ved at tilføje ikke-eksistens eller nul kontrol:

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
}

Dette gør proceduren imidlertid mere kompliceret ved at blande grænsekontrol med normal logik, og det bliver sværere at læse. Ved hjælp af null-objektmønsteret kan man oprette en speciel version af proceduren, men kun for null-noder:

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

Dette adskiller normal logik fra speciel sagsbehandling og gør koden lettere at forstå.

Forhold til andre mønstre

Det kan betragtes som et specielt tilfælde af statsmønsteret og strategimønsteret .

Det er ikke et mønster fra Design Patterns , men er nævnt i Martin Fowlers Refactoring og Joshua Kerievsky s Refactoring Til Mønstre som Insert Null Objekt refactoring .

Kapitel 17 i Robert Cecil Martins Agile Software Development: Principles, Patterns and Practices er dedikeret til mønsteret.

Alternativer

Fra C # 6.0 er det muligt at bruge "?." operator (aka null-conditional operator ), som simpelthen evalueres til null, hvis dens venstre operand er 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

Udvidelsesmetoder og Null coalescing

På nogle Microsoft .NET- sprog kan udvidelsesmetoder bruges til at udføre det, der kaldes 'null coalescing'. Dette skyldes, at udvidelsesmetoder kan kaldes på nulværdier, som om det vedrører en 'instansmetodeopkald', mens udvidelsesmetoder faktisk er statiske. Udvidelsesmetoder kan foretages for at kontrollere nulværdier og derved frigøre kode, der bruger dem fra nogensinde at skulle gøre det. Bemærk, at eksemplet nedenfor bruger C # Null coalescing-operatøren til at garantere fejlfri påkaldelse, hvor den også kunne have brugt en mere verdslig, hvis ... så ... ellers. Følgende eksempel fungerer kun, når du ikke er ligeglad med eksistensen af ​​null, eller hvis du behandler null og tom streng den samme. Antagelsen gælder muligvis ikke i andre applikationer.

// 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

På forskellige sprog

C ++

Et sprog med statisk typede referencer til objekter illustrerer, hvordan null-objektet bliver et mere kompliceret mønster:

#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 {}
};

Her er tanken, at der er situationer, hvor der kræves en pointer eller henvisning til et Animalobjekt, men der er ikke noget passende objekt tilgængeligt. En null-reference er umulig i standardkonform C ++. En nul Animal*pointer er mulig og kan være nyttig som pladsholder, men kan ikke bruges til direkte afsendelse: a->MakeSound()er udefineret adfærd, hvis aer en nul pointer.

Nul-objektmønsteret løser dette problem ved at tilvejebringe en speciel NullAnimalklasse, som kan instantieres bundet til en Animalmarkør eller reference.

Den specielle nulklasse skal oprettes for hvert klassehierarki, der skal have et nulobjekt, da a NullAnimalikke nytter, når det, der er behov for, er et nulobjekt med hensyn til en Widgetbaseklasse, der ikke er relateret til Animalhierarkiet.

Bemærk, at IKKE at have en null-klasse overhovedet er en vigtig funktion i modsætning til sprog, hvor "alt er en reference" (f.eks. Java og C #). I C ++ kan design af en funktion eller metode udtrykkeligt angive, om null er tilladt eller ej.

// 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 #

C # er et sprog, hvor null-objektmønsteret kan implementeres korrekt. Dette eksempel viser dyreobjekter, der viser lyde, og en NullAnimal-forekomst, der bruges i stedet for nøgleordet C # null. Nul-objektet giver ensartet adfærd og forhindrer en undtagelse for referencetidsnullereference, der ville opstå, hvis C #-nøgleordet blev brugt i stedet.

/* 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        
	}
}

Småsnak

Efter Smalltalk-princippet er alt et objekt , fraværet af et objekt er i sig selv modelleret af et objekt, kaldet nil. I GNU Smalltalk for eksempel klassen af niler UndefinedObjecten direkte efterkommer af Object.

Enhver operation, der ikke returnerer en fornuftig genstand til sit formål, kan vende tilbage i nilstedet for således at undgå det specielle tilfælde med at returnere "intet objekt", der ikke understøttes af Smalltalk-designere. Denne metode har fordelen ved enkelhed (intet behov for et specielt tilfælde) i forhold til den klassiske "null" eller "intet objekt" eller "null reference" tilgang. Særligt nyttige beskeder, der skal bruges med niler isNil, ifNil:eller ifNotNil:,, som gør det praktisk og sikker at håndtere eventuelle referencer til nili Smalltalk programmer.

Almindelig Lisp

I Lisp kan funktioner yndefuldt acceptere det specielle objekt nil, hvilket reducerer mængden af ​​special case-test i applikationskode. For eksempel, selv om niler et atom, og har ingen marker, funktionerne carog cdracceptere nilog bare returnere den, hvilket er meget nyttigt og resulterer i kortere kode.

Siden nil er den tomme liste i Lisp, eksisterer den situation, der er beskrevet i indledningen ovenfor, ikke. Kode, der returnerer nil, returnerer det, der faktisk er den tomme liste (og ikke noget, der ligner en nullhenvisning til en listetype), så den, der ringer op, behøver ikke at teste værdien for at se, om den har en liste eller ej.

Nul-objektmønsteret understøttes også i behandling af flere værdier. Hvis programmet forsøger at udtrække en værdi fra et udtryk, der ikke returnerer nogen værdier, er opførelsen, at nulobjektet nilerstattes. Således (list (values))returnerer (nil)(en én-element liste indeholdende nul). Det (values)udtryk returnerer ingen værdier overhovedet, men da funktionen kald til listbehov for at reducere sit argument udtryk til en værdi, der er null objekt automatisk erstattes.

LUKKET

I Common Lisp er objektet nilden eneste forekomst af specialklassen null. Hvad dette betyder er, at en metode kan specialiseres i nullklassen og derved implementere null-designmønsteret. Det vil sige, det er i det væsentlige indbygget i objektsystemet:

;; 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)))

Klassen nuller en underklasse af symbolklassen, fordi den niler et symbol. Da nilogså repræsenterer den tomme liste, nuller også en underklasse af listklassen. Metodeparametre specialiseret til symboleller listvil således tage et nilargument. Selvfølgelig nullkan der stadig defineres en specialisering, som er et mere specifikt match for nil.

Ordning

I modsætning til Common Lisp og mange dialekter af Lisp har ordningsdialekten ikke en nulværdi, der fungerer på denne måde; funktionerne carog cdrkan ikke anvendes på en tom liste; Skemaapplikationskode skal derfor bruge empty?eller pair?predikatfunktionerne til at sidestepe denne situation, selv i situationer hvor meget lignende Lisp ikke behøver at skelne mellem de tomme og ikke-tomme tilfælde takket være adfærden nil.

Rubin

and-typede sprog som Ruby er sprogarv ikke nødvendigt for at give forventet adfærd.

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

Forsøg på direkte monkey-patch af NilClass i stedet for eksplicit implementering giver mere uventede bivirkninger end fordele.

JavaScript

I andetypede sprog som JavaScript er sprogarv ikke nødvendigt for at give forventet adfærd.

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]

Java

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...
	}
}

Denne kode illustrerer en variation af C ++ eksemplet ovenfor ved hjælp af Java-sproget. Som med C ++ kan en null-klasse instantieres i situationer, hvor der kræves en henvisning til et Animalobjekt, men der er ikke noget passende objekt tilgængeligt. Et null- Animalobjekt er muligt ( Animal myAnimal = null;) og kan være nyttigt som pladsholder, men kan ikke bruges til at kalde en metode. I dette eksempel myAnimal.makeSound();kaster en NullPointerException. Derfor kan det være nødvendigt med yderligere kode for at teste for null-objekter.

Nulobjektmønsteret løser dette problem ved at tilvejebringe en speciel NullAnimalklasse, der kan instantieres som et objekt af typen Animal. Som med C ++ og relaterede sprog skal der oprettes den særlige null-klasse for hvert klassehierarki, der har brug for et null-objekt, da a NullAnimalikke nytter, når der er behov for et null-objekt, der ikke implementerer Animalgrænsefladen.

PHP

interface Animal
{
    public function makeSound();
}

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

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

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

$animalType = 'elephant';
switch($animalType) {
    case 'dog':
        $animal = new Dog();
        break;
    case 'cat':
        $animal = new Cat();
        break;
    default:
        $animal = new NullAnimal();
        break;
}
$animal->makeSound(); // ..the null animal makes no sound

Visual Basic .NET

Følgende implementering af nulobjektmønster demonstrerer den konkrete klasse, der giver sit tilsvarende nulobjekt i et statisk felt Empty. Denne fremgangsmåde anvendes ofte i .NET Framework ( String.Empty, EventArgs.Empty, Guid.Empty, etc.).

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

Kritik

Dette mønster skal bruges omhyggeligt, da det kan få fejl / fejl til at fremstå som normal programudførelse.

Der skal udvises forsigtighed ikke at implementere dette mønster bare for at undgå nulkontrol og gøre koden mere læselig, da den sværere at læse kode måske bare flytter til et andet sted og er mindre standard - som når en anden logik skal udføres, hvis objektet forudsat er faktisk det null objekt. Det almindelige mønster på de fleste sprog med referencetyper er at sammenligne en henvisning til en enkelt værdi kaldet null eller nul. Der er også yderligere behov for at teste, at ingen kode overalt nogensinde tildeler null i stedet for null-objektet, for i de fleste tilfælde og sprog med statisk skrivning er dette ikke en compiler-fejl, hvis null-objektet er af en referencetype, selvom det ville helt sikkert føre til fejl ved kørselstid i dele af koden, hvor mønsteret blev brugt til at undgå nulkontrol. Derudover kan der på de fleste sprog og under forudsætning af, at der er mange null-objekter (dvs. null-objektet er en referencetype, men implementerer ikke singleton-mønsteret på en eller anden måde) og kontrollerer for null-objektet i stedet for for null- eller nulværdien introducerer overhead, ligesom singleton-mønsteret sandsynligvis selv ved opnåelse af singleton-referencen.

Se også

Referencer

eksterne links