Vzor nulového objektu - Null object pattern
v objektově orientovaný programování, a nulový objekt je objekt bez odkazované hodnoty nebo s definovaným neutrálním ("null") chováním. Objekt null návrhový vzor popisuje použití takových objektů a jejich chování (nebo jejich nedostatek). Poprvé byla zveřejněna v Vzorové jazyky návrhu programu knižní série.[1]
Motivace
Ve většině objektově orientovaných jazyků, jako je Jáva nebo C#, Reference možná nula. Tyto odkazy je třeba zkontrolovat, aby bylo zajištěno, že nejsou null před vyvoláním jakékoli metody, protože metody obvykle nelze vyvolat na nulové odkazy.
The Jazyk Objective-C používá jiný přístup k tomuto problému a při odesílání zprávy nedělá nic nula
; pokud se očekává návratová hodnota, nula
(pro objekty), 0 (pro číselné hodnoty), NE
(pro BOOL
values), nebo struktura (pro typy struktur) se všemi jejími členy inicializovanými na nula
/0/NE
/ je vrácena nula inicializovaná struktura.[2]
Popis
Místo použití a nulový odkaz k vyjádření nepřítomnosti objektu (například neexistujícího zákazníka) se používá objekt, který implementuje očekávané rozhraní, ale jehož tělo metody je prázdné. Výhodou tohoto přístupu oproti fungující výchozí implementaci je, že nulový objekt je velmi předvídatelný a nemá žádné vedlejší účinky: má nic.
Funkce může například načíst seznam souborů ve složce a provést u každého nějakou akci. V případě prázdné složky může být jednou odpovědí vyvolání výjimky nebo vrácení nulové reference místo seznamu. Kód, který očekává seznam, tedy musí před pokračováním ověřit, zda jej ve skutečnosti má, což může komplikovat design.
Pokud místo toho vrátíte prázdný objekt (tj. Prázdný seznam), není nutné ověřovat, zda je návratová hodnota ve skutečnosti seznam. Volací funkce může jednoduše iterovat seznam jako obvykle, což ve skutečnosti nedělá nic. Je však stále možné zkontrolovat, zda je návratová hodnota null objekt (prázdný seznam), a v případě potřeby reagovat odlišně.
Vzor objektu null lze také použít jako útržek pro testování, pokud určitá funkce, jako je databáze, není pro testování k dispozici.
Příklad
Vzhledem k tomu, binární strom, s touto strukturou uzlu:
třída uzel {uzel vlevo uzel vpravo}
Jeden může implementovat proceduru velikosti stromu rekurzivně:
funkce tree_size (node) {return 1 + tree_size (node.left) + tree_size (node.right)}
Vzhledem k tomu, že podřízené uzly nemusí existovat, je třeba upravit postup přidáním kontroly neexistence nebo nuly:
funkce tree_size (node) {set sum = 1 -li node.left existuje {suma = suma + velikost stromu (node.left)} -li node.right existuje {součet = součet + velikost stromu (node.right)} návratová suma}
Díky tomu je postup komplikovanější smícháním hraničních kontrol s normální logikou a jeho čtení je těžší. Pomocí vzoru nulového objektu lze vytvořit speciální verzi procedury, ale pouze pro nulové uzly:
funkce tree_size (node) {return 1 + tree_size (node.left) + tree_size (node.right)}
funkce tree_size (null_node) {návrat 0}
To odděluje normální logiku od zpracování zvláštních případů a usnadňuje pochopení kódu.
Vztah k jiným vzorům
Lze jej považovat za zvláštní případ Stavový vzor a Strategický vzor.
Není to vzor z Designové vzory, ale je zmíněn v Martina Fowlera Refaktorování[3] a Refaktorování vzorů Joshua Kerievského[4] jako Vložte prázdný objekt refaktorování.
Kapitola 17 z Robert Cecil Martin Agilní vývoj softwaru: Zásady, vzorce a postupy[5] je věnován vzoru.
Alternativy
Od C # 6.0 je možné použít znak „?“. operátor (aka null-podmíněný operátor ), který jednoduše vyhodnotí na null, pokud je jeho levý operand null.
// kompilovat jako konzolová aplikace, vyžaduje C # 6.0 nebo vyššípoužitím Systém;jmenný prostor ConsoleApplication2{ třída Program { statický prázdnota Hlavní(tětiva[] args) { tětiva str = "test"; Řídicí panel.WriteLine(str?.Délka); Řídicí panel.ReadKey(); } }}// Výstup bude:// 4
Metody rozšíření a Null splynutí
V některých Microsoft .NET jazyky, Metody rozšíření lze použít k provedení takzvaného „nulového splynutí“. Důvodem je, že metody rozšíření lze volat na nulové hodnoty, jako by se to týkalo „vyvolání metody instance“, zatímco ve skutečnosti jsou metody rozšíření statické. Lze provést metody rozšíření ke kontrole nulových hodnot, čímž uvolníte kód, který je používá, od toho, že to někdy budete muset udělat. Následující příklad používá znak C# Nulový spojovací operátor zaručit bezchybné vyvolání, kde by také mohlo použít všednější, pokud ... pak ... jinak. Následující příklad funguje pouze v případě, že se nestaráte o existenci hodnoty null nebo pokud s nulovým a prázdným řetězcem zacházíte stejně. Předpoklad nemusí platit v jiných aplikacích.
// kompilovat jako konzolová aplikace, vyžaduje C # 3.0 nebo vyššípoužitím Systém;použitím System.Linq;jmenný prostor MyExtensionWithExample { veřejnost statický třída StringExtensions { veřejnost statický int SafeGetLength(tento tětiva valueOrNull) { vrátit se (valueOrNull ?? tětiva.Prázdný).Délka; } } veřejnost statický třída Program { // definovat některé řetězce statický pouze ke čtení tětiva[] struny = Nový [] { „Pane X.“, „Katrien Duck“, nula, „Q“ }; // zapíše celkovou délku všech řetězců v poli veřejnost statický prázdnota Hlavní(tětiva[] args) { var dotaz = z text v struny vybrat text.SafeGetLength(); // zde není třeba provádět žádné kontroly Řídicí panel.WriteLine(dotaz.Součet()); } }}// Výstup bude:// 18
V různých jazycích
tento článek případně obsahuje původní výzkum.červen 2013) (Zjistěte, jak a kdy odstranit tuto zprávu šablony) ( |
C ++
Jazyk se staticky zadanými odkazy na objekty ilustruje, jak se objekt null stává složitějším vzorem:
#zahrnout <iostream>třída Zvíře { veřejnost: virtuální ~Zvíře() = výchozí; virtuální prázdnota MakeSound() konst = 0;};třída Pes : veřejnost Zvíře { veřejnost: virtuální prázdnota MakeSound() konst přepsat { std::cout << "tkanina!" << std::konec; }};třída NullZvíře : veřejnost Zvíře { veřejnost: virtuální prázdnota MakeSound() konst přepsat {}};
Tady je myšlenka, že existují situace, kdy ukazatel nebo odkaz na Zvíře
objekt je vyžadován, ale není k dispozici žádný vhodný objekt. Null reference je v C ++ vyhovujícím standardu nemožná. Nula Zvíře*
ukazatel je možný a může být užitečný jako zástupný symbol, ale nelze jej použít k přímému odeslání: a-> MakeSound ()
je nedefinované chování, pokud A
je nulový ukazatel.
Vzor nulového objektu řeší tento problém poskytnutím speciálu NullZvíře
třída, kterou lze vytvořit instancí vázanou na Zvíře
ukazatel nebo odkaz.
Pro každou hierarchii tříd, která má mít null objekt, musí být vytvořena speciální null třída, protože a NullZvíře
je k ničemu, když to, co je potřeba, je nulový objekt s ohledem na některé Widget
základní třída, která nesouvisí s Zvíře
hierarchie.
Všimněte si, že NEMÁTE vůbec žádnou třídu null je důležitá vlastnost, na rozdíl od jazyků, kde „cokoli je odkaz“ (např. Java a C #). V C ++ může design funkce nebo metody explicitně uvádět, zda je null povolena nebo ne.
// Funkce, která vyžaduje | Zvíře | instance a nepřijme null.prázdnota Dělej něco(konst Zvíře& zvíře) { // | zvíře | zde nemusí být nikdy neplatné.}// Funkce, která může přijmout | Zvíře | instance nebo null.prázdnota Dělej něco(konst Zvíře* zvíře) { // | zvíře | může být null.}
C#
C # je jazyk, ve kterém lze vzor null objektu správně implementovat. Tento příklad ukazuje zvířecí objekty, které zobrazují zvuky, a instanci NullAnimal použitou místo klíčového slova C # null. Objekt null poskytuje konzistentní chování a brání výjimce nulového odkazu za běhu, která by nastala, kdyby bylo místo toho použito klíčové slovo C # null.
/ * Implementace nulového objektu: */použitím Systém;// Rozhraní Animal je klíčem ke kompatibilitě pro implementace Animal níže.rozhraní Zvíře{ prázdnota MakeSound();}// Zvíře je základní případ.abstraktní třída Zvíře : Zvíře{ // Sdílená instance, kterou lze použít pro srovnání veřejnost statický pouze ke čtení Zvíře Nula = Nový NullZvíře(); // Null Case: tato třída NullAnimal by měla být použita místo klíčového slova C # null. soukromé třída NullZvíře : Zvíře { veřejnost přepsat prázdnota MakeSound() { // Účelně neposkytuje žádné chování. } } veřejnost abstraktní prázdnota MakeSound();}// Pes je skutečné zvíře.třída Pes : Zvíře{ veřejnost prázdnota MakeSound() { Řídicí panel.WriteLine("Tkanina!"); }}/* ========================= * Zjednodušující příklad použití v hlavním vstupním bodě. */statický třída Program{ statický prázdnota Hlavní() { Zvíře Pes = Nový Pes(); Pes.MakeSound(); // výstupy „Haf!“ / * Namísto použití C # null použijte instanci Animal.Null. * Tento příklad je zjednodušující, ale vyjadřuje myšlenku, že pokud se použije instance Animal.Null, pak program * nikdy nezažije .NET System.NullReferenceException za běhu, na rozdíl od toho, kdyby byly použity C # null. */ Zvíře neznámý = Zvíře.Nula; // << nahrazuje: IAnimal unknown = null; neznámý.MakeSound(); // nic nevydá, ale nevyvolá runtime výjimku }}
Pokec
Podle principu Smalltalk všechno je objekt, nepřítomnost objektu je sama modelována objektem, tzv nula
. Například v GNU Smalltalk třída nula
je Nedefinovaný objekt
, přímý potomek Objekt
.
Může se vrátit jakákoli operace, která nevrátí rozumný objekt pro svůj účel nula
místo toho se vyhnete speciálnímu případu vracení „žádného objektu“. Výhodou této metody je jednoduchost (není třeba speciální případ) oproti klasickému přístupu „null“ nebo „no object“ nebo „null reference“. Obzvláště užitečné zprávy, které se mají používat nula
jsou není
nebo ifNil:
, díky nimž je praktické a bezpečné zacházet s možnými odkazy na nula
v programech Smalltalk.
Společný Lisp
V Lispu mohou funkce elegantně přijmout speciální objekt nula
, což snižuje rozsah testování speciálních případů v kódu aplikace. Například i když nula
je atom a nemá žádná pole, funkce auto
a cdr
přijmout nula
a stačí jej vrátit, což je velmi užitečné a výsledkem je kratší kód.
Od té doby nula
je prázdný seznam v Lispu, situace popsaná v úvodu výše neexistuje. Kód, který se vrací nula
vrací to, co je ve skutečnosti prázdný seznam (a nic podobného nulovému odkazu na typ seznamu), takže volající nemusí testovat hodnotu, aby zjistil, zda má seznam.
Při zpracování více hodnot je také podporován vzor objektu null. Pokud se program pokusí extrahovat hodnotu z výrazu, který nevrací žádné hodnoty, jedná se o chování objektu null nula
je nahrazen (seznam (hodnoty))
se vrací (nula)
(jednoprvkový seznam obsahující nulu). The (hodnoty)
výraz nevrací vůbec žádné hodnoty, ale od volání funkce na seznam
potřebuje snížit svůj argumentový výraz na hodnotu, objekt null se automaticky nahradí.
CLOS
V objektu Common Lisp nula
je jedinou instancí speciální třídy nula
. To znamená, že metodu lze specializovat na nula
třídy, čímž implementuje nulový návrhový vzor. Což znamená, že je v podstatě zabudováno do objektového systému:
;; prázdná třída psů(defclass Pes () ());; předmět psa vydá štěkáním zvuk: haf! je vytištěn na standardní výstup;; když se volá (make-sound x), pokud x je instancí třídy psa.(defmethod udělat zvuk ((obj Pes)) (formát t "haf! ~%"));; povolit (make-sound nil) pracovat prostřednictvím specializace na nulovou třídu.;; neškodné prázdné tělo: žádný nevydává žádný zvuk.(defmethod udělat zvuk ((obj nula)))
Třída nula
je podtřída symbol
třída, protože nula
je symbol nula
také představuje prázdný seznam, nula
je podtřída seznam
třída taky. Parametry metod se specializují na symbol
nebo seznam
tedy vezme a nula
argument. Samozřejmě, a nula
specializaci lze stále definovat, což je konkrétnější shoda nula
.
Systém
Na rozdíl od Common Lisp a mnoha dialektů Lisp nemá dialekt schématu nulovou hodnotu, která funguje tímto způsobem; funkce auto
a cdr
nemusí být použito na prázdný seznam; Kód aplikace systému proto musí používat prázdný?
nebo pár?
funkce predikátu, aby se této situaci vyhnuli, a to i v situacích, kdy by velmi podobný Lisp díky chování uživatele nemusel rozlišovat prázdné a neprázdné případy nula
.
Rubín
v kachní typ jazyky jako Rubín, jazyková dědičnost není nutná k zajištění očekávaného chování.
třída Pes def zvuk "kůra" koneckonec třída NilAnimal def zvuk(*); koneckonecdef get_animal(zvíře=NilAnimal.Nový) zvířekonecget_animal(Pes.Nový).zvuk => "kůra"get_animal.zvuk => nula
Pokouší se přímo opičí náplast NilClass místo poskytnutí explicitní implementace poskytuje více neočekávaných vedlejších účinků než výhod.
JavaScript
v kachní typ jazyky jako JavaScript, jazyková dědičnost není nutná k zajištění očekávaného chování.
třída Pes { zvuk() { vrátit se 'kůra'; }}třída NullZvíře { zvuk() { vrátit se nula; }}funkce getAnimal(typ) { vrátit se typ === 'Pes' ? Nový Pes() : Nový NullZvíře();}['Pes', nula].mapa((zvíře) => getAnimal(zvíře).zvuk());// Vrací ["štěkat", null]
Jáva
veřejnost rozhraní Zvíře { prázdnota makeSound() ;}veřejnost třída Pes nářadí Zvíře { veřejnost prázdnota makeSound() { Systém.ven.tisk("tkanina!"); }}veřejnost třída NullZvíře nářadí Zvíře { veřejnost prázdnota makeSound() { // ticho ... }}
Tento kód ilustruje variantu výše uvedeného příkladu C ++ používající jazyk Java. Stejně jako v C ++ lze null třídu vytvořit v situacích, kdy je odkaz na Zvíře
objekt je vyžadován, ale není k dispozici žádný vhodný objekt. Nula Zvíře
objekt je možný (Zvíře myAnimal = null;
) a mohou být užitečné jako zástupný symbol, ale nelze je použít pro volání metody. V tomto příkladu myAnimal.makeSound ();
vyvolá NullPointerException. Proto může být nezbytný další kód pro testování objektů null.
Vzor nulového objektu řeší tento problém poskytnutím speciálu NullZvíře
třída, kterou lze vytvořit jako objekt typu Zvíře
. Stejně jako v C ++ a souvisejících jazycích musí být tato speciální třída null vytvořena pro každou hierarchii tříd, která potřebuje objekt null, protože NullZvíře
je k ničemu, když je potřeba nulový objekt, který neimplementuje Zvíře
rozhraní.
PHP
rozhraní Zvíře{ veřejnost funkce makeSound();}třída Pes nářadí Zvíře{ veřejnost funkce makeSound() { echo "Tkanina.."; }}třída Kočka nářadí Zvíře{ veřejnost funkce makeSound() { echo "Meowww .."; }}třída NullZvíře nářadí Zvíře{ veřejnost funkce makeSound() { // ticho ... }}$ animalType = 'slon';přepínač($ animalType) { případ 'Pes': $ zvíře = Nový Pes(); přestávka; případ 'kočka': $ zvíře = Nový Kočka(); přestávka; výchozí: $ zvíře = Nový NullZvíře(); přestávka;}$ zvíře->makeSound(); // .. nulové zvíře nevydává žádný zvuk
Visual Basic .NET
Následující implementace vzoru objektu null ukazuje konkrétní třídu poskytující odpovídající objekt null ve statickém poli Prázdný
. Tento přístup se často používá v .NET Framework (Řetězec. Prázdný
, EventArgs.Empty
, Guid. Prázdné
, atd.).
Veřejnost Třída Zvíře Veřejnost Sdílené Pouze ke čtení Prázdný Tak jako Zvíře = Nový Zvíře prázdné() Veřejnost Přeliditelné Sub MakeSound() Řídicí panel.WriteLine("Tkanina!") Konec SubKonec TřídaPříteli Nezděděno Třída Zvíře prázdné Dědí Zvíře Veřejnost Přepíše Sub MakeSound() ' Konec SubKonec Třída
Kritika
Tento vzor by měl být používán opatrně, protože může způsobit, že se chyby / chyby objeví jako normální spuštění programu.[6]
Je třeba dbát na to, aby se tento vzor neimplementoval, jen aby se zabránilo nulovým kontrolám a aby byl kód čitelnější, protože těžší čitelný kód se může jen přesunout na jiné místo a být méně standardní - například když se musí v případě objektu provést jiná logika za předpokladu, že je skutečně nulový objekt. Společným vzorem ve většině jazyků s referenčními typy je porovnání odkazu na jednu hodnotu označovanou jako null nebo nil. Také existuje další potřeba testování, že žádný kód nikde nikdy nepřiřadí null místo null objektu, protože ve většině případů a jazycích se statickým typováním nejde o chybu kompilátoru, pokud je null objekt referenčního typu, i když by určitě vedou k chybám za běhu v částech kódu, kde byl použit vzor, aby se zabránilo nulovým kontrolám. Kromě toho ve většině jazyků a za předpokladu, že může existovat mnoho objektů null (tj. Objekt null je referenční typ, ale neimplementuje singletonový vzor jedním nebo jiným způsobem), kontrola objektu null namísto hodnoty null nebo nil zavádí režii, stejně jako vzor singleton pravděpodobně sám o sobě po získání odkazu singleton.
Viz také
Reference
- ^ Woolf, Bobby (1998). "Nulový objekt". In Martin, Robert; Riehle, Dirk; Buschmann, Frank (eds.). Vzorové jazyky návrhu programu 3. Addison-Wesley.
- ^ „Práce s objekty (Práce s nulou)“. Knihovna vývojářů pro iOS. Apple, Inc. 2012-12-13. Citováno 2014-05-19.
- ^ Fowler, Martin (1999). Refaktorování. Vylepšení designu stávajícího kódu. Addison-Wesley. ISBN 0-201-48567-2.
- ^ Kerievsky, Joshua (2004). Refaktorování vzorů. Addison-Wesley. ISBN 0-321-21335-1.
- ^ Martin, Robert (2002). Agilní vývoj softwaru: Zásady, vzorce a postupy. Pearson Education. ISBN 0-13-597444-5.
- ^ Fowler, Martin (1999). Refaktorování str. 216