Problém inicializace objektů v OOP aplikacích v PHP. Nalezení řešení pomocí Registry, Factory Method, Service Locator a Dependency Injection vzorů. Vzory OOP s příklady a popisy Autoritativní registro php

Problém inicializace objektů v OOP aplikacích v PHP. Nalezení řešení pomocí Registry, Factory Method, Service Locator a Dependency Injection vzorů

Jen tak se stává, že programátoři konsolidují úspěšná řešení ve formě návrhových vzorů. Existuje spousta literatury o vzorech. Kniha Gang of Four „Design Patterns“ od Ericha Gammy, Richarda Helma, Ralpha Johnsona a Johna Vlissidese“ a možná „Patterns of Enterprise Application Architecture“ od Martina Fowlera jsou jistě považovány za klasiku. To nejlepší, co jsem četl s příklady v PHP - toto. Náhodou je celá tato literatura poměrně složitá pro lidi, kteří právě začali ovládat OOP. Napadlo mě tedy představit některé vzory, které považuji za nejužitečnější, ve značně zjednodušené formě. slovy, tento článek je mým prvním pokusem o interpretaci návrhových vzorů ve stylu KISS.
Dnes si povíme o tom, jaké problémy mohou nastat při inicializaci objektů v OOP aplikaci a jak můžete k řešení těchto problémů použít některé oblíbené návrhové vzory.

Příklad

Moderní OOP aplikace pracuje s desítkami, stovkami a někdy i tisíci objektů. Nuže, podívejme se blíže na to, jak jsou tyto objekty inicializovány v našich aplikacích. Inicializace objektů je jediným aspektem, který nás v tomto článku zajímá, a proto jsem se rozhodl vynechat veškerou implementaci „navíc“.
Řekněme, že jsme vytvořili super-duper užitečnou třídu, která může odeslat požadavek GET na konkrétní URI a vrátit HTML z odpovědi serveru. Aby naše třída nevypadala příliš jednoduše, nechejte ji také zkontrolovat výsledek a pokud server odpoví „špatně“, vyvolá výjimku.

Class Grabber ( veřejná funkce get($url) (/** vrátí HTML kód nebo vyvolá výjimku */) )

Vytvořme další třídu, jejíž objekty budou zodpovědné za filtrování přijatého HTML. Metoda filtru bere jako argumenty kód HTML a selektor CSS a vrací pole prvků nalezených pro daný selektor.

Třída HtmlExtractor ( filtr veřejné funkce ($html, $selector) (/** vrátí pole filtrovaných prvků */) )

Nyní si představte, že potřebujeme získat výsledky vyhledávání na Google pro daná klíčová slova. K tomu si představíme další třídu, která bude využívat třídu Grabber k odeslání požadavku a třídu HtmlExtractor k extrahování potřebného obsahu. Bude také obsahovat logiku pro konstrukci URI, selektor pro filtrování přijatého HTML a zpracování získaných výsledků.

Třída GoogleFinder ( private $grabber; private $filter; veřejná funkce __construct() ( $this->grabber = new Grabber(); $this->filter = new HtmlExtractor(); ) veřejná funkce find($searchString) ( /* * vrátí pole založených výsledků */) )

Všimli jste si, že inicializace objektů Grabber a HtmlExtractor je v konstruktoru třídy GoogleFinder? Zamysleme se nad tím, jak dobré je toto rozhodnutí.
Samozřejmě napevno zakódovat vytváření objektů v konstruktoru není dobrý nápad. A právě proto. Za prvé, nebudeme moci snadno přepsat třídu Grabber v testovacím prostředí, abychom se vyhnuli odeslání skutečného požadavku. Abychom byli spravedliví, stojí za to říci, že to lze provést pomocí rozhraní Reflection API. Tito. technická možnost existuje, ale není to zdaleka nejpohodlnější a nejzřejmější způsob.
Za druhé, stejný problém nastane, pokud budeme chtít znovu použít logiku GoogleFinder s jinými implementacemi Grabber a HtmlExtractor. Vytváření závislostí je pevně zakódováno v konstruktoru třídy. A v nejlepším případě budeme moci zdědit GoogleFinder a přepsat jeho konstruktor. A to i tehdy, pouze pokud je rozsah vlastností grabberu a filtru chráněný nebo veřejný.
Poslední bod, pokaždé, když vytvoříme nový objekt GoogleFinder, vytvoří se v paměti nový pár objektů závislostí, i když můžeme celkem snadno použít jeden objekt Grabber a jeden objekt HtmlExtractor v několika objektech GoogleFinder.
Myslím, že už chápete, že inicializaci závislostí je třeba přesunout mimo třídu. Můžeme požadovat, aby již připravené závislosti byly předány konstruktoru třídy GoogleFinder.

Třída GoogleFinder ( private $grabber; private $filter; public function __construct(Grabber $grabber, HtmlExtractor $filter) ( $this->grabber = $grabber; $this->filter = $filter; ) veřejná funkce find($searchString) ( /** vrátí pole založených výsledků */) )

Pokud chceme dát ostatním vývojářům možnost přidávat a používat vlastní implementace Grabber a HtmlExtractor, pak bychom měli zvážit zavedení rozhraní pro ně. V tomto případě je to nejen užitečné, ale také nutné. Domnívám se, že pokud v projektu používáme pouze jednu implementaci a neočekáváme, že v budoucnu vytvoříme nové, měli bychom odmítnout vytvoření rozhraní. Je lepší jednat podle situace a provést jednoduchý refaktoring, když je to skutečně potřeba.
Nyní máme všechny potřebné třídy a můžeme použít třídu GoogleFinder v ovladači.

Class Controller ( public function action() ( /* Some things */ $finder = new GoogleFinder(new Grabber(), new HtmlExtractor()); $results = $finder->

Shrňme si průběžné výsledky. Napsali jsme velmi málo kódu a na první pohled jsme neudělali nic špatného. Ale... co když potřebujeme použít objekt jako GoogleFinder na jiném místě? Jeho vytvoření budeme muset duplikovat. V našem příkladu je to jen jeden řádek a problém není tak nápadný. V praxi může být inicializace objektů poměrně složitá a může trvat až 10 řádků nebo i více. Objevují se i další problémy typické pro duplikaci kódu. Pokud během procesu refaktoringu potřebujete změnit název použité třídy nebo logiku inicializace objektu, budete muset ručně změnit všechna místa. Myslím, že víš jak to chodí :)
Obvykle se s pevným kódem pracuje jednoduše. V konfiguraci jsou obvykle zahrnuty duplicitní hodnoty. To vám umožní měnit hodnoty centrálně na všech místech, kde se používají.

Šablona registru.

Rozhodli jsme se tedy přesunout vytváření objektů do konfigurace. Pojďme to udělat.

$registry = new ArrayObject(); $registry["grabber"] = new Grabber(); $registry["filtr"] = new HtmlExtractor(); $registry["google_finder"] = nový GoogleFinder($registry["grabber"], $registry["filtr"]);
Vše, co musíme udělat, je předat náš ArrayObject řadiči a problém je vyřešen.

Řadič třídy ( soukromý $registry; veřejná funkce __construct(ArrayObject $registry) ( $this->registry = $registry; ) veřejná funkce action() ( /* Některé věci */ $results = $this->registry["google_finder" ]->find("hledat řetězec"); /* Udělejte něco s výsledky */ ) )

Myšlenku registru můžeme dále rozvíjet. Zdědit ArrayObject, zapouzdřit vytváření objektů uvnitř nové třídy, zakázat přidávání nových objektů po inicializaci atd. Daný kód ale dle mého názoru plně objasňuje, co je to šablona Registry. Tento vzorec není generativní, ale určitým způsobem řeší naše problémy. Registr je pouze kontejner, do kterého můžeme ukládat objekty a předávat je v rámci aplikace. Aby byly objekty dostupné, musíme je nejprve vytvořit a zaregistrovat v tomto kontejneru. Podívejme se na výhody a nevýhody tohoto přístupu.
Na první pohled jsme dosáhli svého. Přestali jsme pevně kódovat názvy tříd a vytvářet objekty na jednom místě. Objekty vytváříme v jediné kopii, která zaručuje jejich opětovné použití. Pokud se změní logika vytváření objektů, bude potřeba upravit pouze jedno místo v aplikaci. Jako bonus jsme dostali možnost centrálně spravovat objekty v Registru. Snadno získáme seznam všech dostupných objektů a provedeme s nimi nějaké manipulace. Pojďme se nyní podívat na to, co se nám na této šabloně nemusí líbit.
Nejprve musíme objekt vytvořit před jeho registrací v registru. V souladu s tím existuje vysoká pravděpodobnost vytvoření „nepotřebných objektů“, tzn. ty, které budou vytvořeny v paměti, ale nebudou použity v aplikaci. Ano, objekty můžeme do Registru přidávat dynamicky, tzn. vytvářet pouze ty objekty, které jsou potřeba ke zpracování konkrétního požadavku. Tak či onak to budeme muset ovládat ručně. Proto bude časem velmi obtížné jej udržovat.
Za druhé, máme novou závislost na ovladači. Ano, můžeme přijímat objekty prostřednictvím statické metody v registru, takže registr nemusíme předávat konstruktoru. Ale podle mého názoru byste to neměli dělat. Statické metody jsou ještě těsnější spojení než vytváření závislostí uvnitř objektu a potíže s testováním (na toto téma).
Za třetí, rozhraní ovladače nám neříká nic o tom, jaké objekty používá. Můžeme získat jakýkoli objekt dostupný v registru v řadiči. Bude pro nás těžké říci, které objekty ovladač používá, dokud nezkontrolujeme celý jeho zdrojový kód.

Tovární metoda

Náš největší problém s registrem je, že objekt musí být inicializován, než k němu bude možné přistupovat. Místo inicializace objektu v konfiguraci můžeme oddělit logiku pro vytváření objektů do jiné třídy, kterou můžeme „požádat“ o vytvoření objektu, který potřebujeme. Třídy, které jsou zodpovědné za vytváření objektů, se nazývají továrny. A návrhový vzor se nazývá Factory Method. Podívejme se na příklad továrny.

Class Factory ( veřejná funkce getGoogleFinder() ( return new GoogleFinder($this->getGrabber(), $this->getHtmlExtractor()); ) soukromá funkce getGrabber() ( return new Grabber(); ) soukromá funkce getHtmlExtractor() ( vrátit nový HtmlFiletr(); ) )

Zpravidla se vyrábějí továrny, které jsou odpovědné za vytvoření jednoho typu objektu. Někdy může továrna vytvořit skupinu souvisejících objektů. Můžeme použít ukládání do mezipaměti do vlastnosti, abychom se vyhnuli opětovnému vytváření objektů.

Class Factory ( soukromý $finder; veřejná funkce getGoogleFinder() ( if (null === $this->finder) ( $this->finder = new GoogleFinder($this->getGrabber(), $this->getHtmlExtractor() ); ) vrátit $this->finder; ) )

Můžeme parametrizovat tovární metodu a delegovat inicializaci na jiné továrny v závislosti na vstupním parametru. Toto již bude šablona Abstract Factory.
Pokud potřebujeme modularizovat aplikaci, můžeme požadovat, aby každý modul poskytoval své vlastní továrny. Téma továren můžeme dále rozvíjet, ale myslím, že podstata této šablony je jasná. Podívejme se, jak budeme používat továrnu v ovladači.

Řadič třídy ( private $factory; veřejná funkce __construct(Factory $factory) ( $this->factory = $factory; ) veřejná funkce action() ( /* Některé věci */ $results = $this->factory->getGoogleFinder( )->find("hledat řetězec"); /* Udělejte něco s výsledky */ ) )

Mezi výhody tohoto přístupu patří jeho jednoduchost. Naše objekty jsou vytvořeny explicitně a vaše IDE vás snadno dovede na místo, kde se to stane. Vyřešili jsme také problém s registrem, takže objekty v paměti budou vytvořeny pouze tehdy, když o to "požádáme" továrnu. Ale ještě jsme se nerozhodli, jak dodat potřebné továrny kontrolérům. Zde je několik možností. Můžete použít statické metody. Můžeme nechat regulátory, aby samy vytvořily potřebné továrny a zrušit všechny naše pokusy zbavit se copy-paste. Můžete vytvořit továrnu továren a pouze to předat ovladači. Ale získávání objektů do ovladače bude trochu komplikovanější a budete muset spravovat závislosti mezi továrnami. Navíc není úplně jasné, co dělat, pokud chceme moduly používat v naší aplikaci, jak registrovat továrny modulů, jak spravovat spojení mezi továrnami z různých modulů. Obecně jsme ztratili hlavní výhodu továrny - explicitní vytváření objektů. A stále jsme nevyřešili problém „implicitního“ rozhraní ovladače.

Lokátor služeb

Šablona Service Locator umožňuje vyřešit chybějící fragmentaci továren a řídit vytváření objektů automaticky a centrálně. Pokud se nad tím zamyslíme, můžeme zavést další vrstvu abstrakce, která bude zodpovědná za vytváření objektů v naší aplikaci a správu vztahů mezi těmito objekty. Aby tato vrstva mohla vytvářet objekty za nás, budeme jí muset dát znalosti, jak to udělat.
Podmínky vzoru lokátoru služeb:
  • Služba je hotový objekt, který lze získat z kontejneru.
  • Definice služby – logika inicializace služby.
  • Kontejner (Service Container) je centrální objekt, který ukládá všechny popisy a na jejich základě může vytvářet služby.
Každý modul může zaregistrovat své popisy služeb. Abychom získali nějakou službu z kontejneru, budeme si ji muset vyžádat klíčem. Možností implementace Service Locatoru je mnoho, v nejjednodušší verzi můžeme použít ArrayObject jako kontejner a uzávěrku jako popis služeb.

Class ServiceContainer rozšiřuje ArrayObject ( veřejná funkce get($key) ( if (is_callable($this[$key])) ( return call_user_func($this[$key]); ) vyvolá new \RuntimeException("Nelze najít definici služby pod klíč [ $key ]"); ))

Poté bude registrace definic vypadat takto:

$container = new ServiceContainer(); $container["grabber"] = funkce () ( return new Grabber(); ); $container["html_filter"] = funkce () ( return new HtmlExtractor(); ); $container["google_finder"] = function() use ($container) ( return new GoogleFinder($container->get("grabber"), $container->get("html_filter")); );

A použití v ovladači je takto:

Řadič třídy ( private $container; veřejná funkce __construct(ServiceContainer $container) ( $this->container = $container; ) veřejná funkce action() ( /* Něco */ $results = $this->container->get( "google_finder")->find("hledací řetězec"); /* Udělejte něco s výsledky */ ) )

Servisní kontejner může být velmi jednoduchý nebo může být velmi složitý. Například Symfony Service Container poskytuje spoustu funkcí: parametry, rozsahy služeb, vyhledávání služeb podle značek, aliasů, soukromých služeb, možnost provádět změny v kontejneru po přidání všech služeb (průchody kompilátoru) a mnoho dalšího. DIExtraBundle dále rozšiřuje možnosti standardní implementace.
Ale vraťme se k našemu příkladu. Jak můžete vidět, Service Locator nejenže řeší všechny stejné problémy jako předchozí šablony, ale také usnadňuje používání modulů s vlastními definicemi služeb.
Navíc jsme na rámcové úrovni získali další úroveň abstrakce. Konkrétně změnou metody ServiceContainer::get můžeme například objekt nahradit proxy. A rozsah použití proxy objektů je omezen pouze představivostí vývojáře. Zde můžete implementovat paradigma AOP, LazyLoading atd.
Ale většina vývojářů stále považuje Service Locator za anti-vzor. Protože teoreticky můžeme mít tolik tzv Třídy Container Aware (tj. třídy, které obsahují odkaz na kontejner). Například náš Controller, uvnitř kterého můžeme získat jakoukoli službu.
Podívejme se, proč je to špatné.
Nejprve opět testování. Namísto vytváření maket pouze pro třídy používané v testech, budete muset zesměšnit celý kontejner nebo použít skutečný kontejner. První možnost vám nevyhovuje, protože... musíte v testech napsat spoustu zbytečného kódu, za druhé, protože je to v rozporu se zásadami jednotkového testování a může vést k dodatečným nákladům na údržbu testů.
Za druhé, bude pro nás obtížné refaktorovat. Změnou jakékoli služby (nebo ServiceDefinition) v kontejneru budeme nuceni zkontrolovat také všechny závislé služby. A tento problém nelze vyřešit pomocí IDE. Najít taková místa v celé aplikaci nebude tak snadné. Kromě závislých služeb budete muset zkontrolovat také všechna místa, kde se refaktorovaná služba z kontejneru získává.
No a třetím důvodem je, že nekontrolované vytahování služeb z kontejneru dříve nebo později povede k nepořádku v kódu a zbytečným zmatkům. To se těžko vysvětluje, jen budete muset trávit více a více času, abyste pochopili, jak ta či ona služba funguje, jinými slovy, můžete plně pochopit, co dělá nebo jak třída funguje, pouze když si přečtete celý její zdrojový kód.

Injekce závislosti

Co dalšího můžete udělat pro omezení použití kontejneru v aplikaci? Do frameworku můžete přenést kontrolu nad vytvářením všech uživatelských objektů, včetně kontrolérů. Jinými slovy, uživatelský kód by neměl volat metodu get kontejneru. V našem příkladu můžeme do kontejneru přidat definici ovladače:

$container["google_finder"] = function() use ($container) ( return new Controller(Grabber $grabber); );

A zbavte se kontejneru v ovladači:

Řadič třídy ( soukromý $finder; veřejná funkce __construct(GoogleFinder $finder) ( $this->finder = $finder; ) veřejná funkce action() ( /* Něco */ $results = $this->finder->find( "hledací řetězec"); /* Udělejte něco s výsledky */ ) )

Tento přístup (když není klientům třídám poskytován přístup ke kontejneru služeb) se nazývá Dependency Injection. Tato šablona má ale také výhody i nevýhody. Dokud dodržujeme zásadu jediné odpovědnosti, kód vypadá velmi krásně. V první řadě jsme se zbavili kontejneru v klientských třídách a jejich kód byl mnohem přehlednější a jednodušší. Řadič snadno otestujeme výměnou potřebných závislostí. Každou třídu můžeme vytvořit a otestovat nezávisle na ostatních (včetně tříd regulátorů) pomocí přístupu TDD nebo BDD. Při vytváření testů můžeme abstrahovat od kontejneru a později přidat Definici, když potřebujeme použít konkrétní instance. Díky tomu bude náš kód jednodušší a přehlednější a testování transparentnější.
Je ale potřeba zmínit i druhou stranu mince. Faktem je, že ovladače jsou velmi specifické třídy. Začněme tím, že správce zpravidla obsahuje soubor akcí, což znamená, že porušuje zásadu jediné odpovědnosti. V důsledku toho může mít třída řadiče mnohem více závislostí, než je nutné k provedení konkrétní akce. Použití líné inicializace (objekt je konkretizován při prvním použití a předtím je použit odlehčený proxy) do určité míry řeší problém s výkonem. Ale z architektonického hlediska není vytváření mnoha závislostí na ovladači také úplně správné. Navíc testování ovladačů je obvykle zbytečná operace. Vše samozřejmě závisí na tom, jak je testování ve vaší aplikaci organizováno a jak se na něj vy sami cítíte.
Z předchozího odstavce jste si uvědomili, že použití Dependency Injection zcela neodstraní architektonické problémy. Zamyslete se proto nad tím, jak pro vás bude pohodlnější, zda uložit odkaz na kontejner do kontrolérů nebo ne. Zde neexistuje jediné správné řešení. Myslím, že oba přístupy jsou dobré, pokud kód ovladače zůstane jednoduchý. Rozhodně byste však neměli vytvářet kromě ovladačů služby Conatiner Aware.

závěry

Nastal čas shrnout vše, co bylo řečeno. A bylo toho řečeno hodně... :)
Abychom strukturovali práci při vytváření objektů, můžeme použít následující vzory:
  • Registr: Šablona má zjevné nevýhody, z nichž nejzákladnější je potřeba vytvořit objekty před jejich vložením do společného kontejneru. Je zřejmé, že z jeho používání budeme mít více problémů než užitku. Toto zjevně není nejlepší využití šablony.
  • Tovární metoda: Hlavní výhoda vzoru: objekty jsou vytvořeny explicitně. Hlavní nevýhoda: řadiče se buď musí starat o to, aby si samy vytvořily továrny, což úplně neřeší problém názvů tříd natvrdo, nebo musí být framework zodpovědný za to, že řadičům poskytne všechny potřebné továrny, což nebude tak zřejmé. Chybí možnost centrálně řídit proces vytváření objektů.
  • Lokátor služeb: Pokročilejší způsob ovládání vytváření objektů. Další úroveň abstrakce lze použít k automatizaci běžných úloh, se kterými se setkáváme při vytváření objektů. Například:
    class ServiceContainer rozšiřuje ArrayObject ( veřejná funkce get($key) ( if (is_callable($this[$key])) ( $obj = call_user_func($this[$key]); if ($obj instanceof RequestAwareInterface) ( $obj- >setRequest($this->get("request")); ) return $obj; ) vyvolá novou \RuntimeException("Nelze najít definici služby pod klíčem [ $key ]"); ) )
    Nevýhodou Service Locatoru je, že veřejné API tříd přestává být informativní. Je nutné přečíst celý kód třídy, abyste pochopili, jaké služby se v ní používají. Třída, která obsahuje odkaz na kontejner, se testuje obtížněji.
  • Injekce závislosti: V podstatě můžeme použít stejný kontejner služeb jako u předchozího vzoru. Rozdíl je v tom, jak se tento kontejner používá. Pokud se vyhneme tomu, aby byly třídy závislé na kontejneru, získáme jasné a explicitní třídní API.
To není vše, co bych vám chtěl říci o problému vytváření objektů v PHP aplikacích. Nechybí vzor Prototype, neuvažovali jsme o použití Reflection API, nechali jsme stranou problém líného načítání služeb a mnoho dalších nuancí. Článek byl docela dlouhý, tak to uzavírám :)
Chtěl jsem ukázat, že Dependency Injection a další vzorce nejsou tak složité, jak se běžně věří.
Pokud mluvíme o Dependency Injection, pak existují implementace tohoto vzoru například KISS

Dotýkání se struktury budoucí databáze. Začalo to a my nemůžeme ustoupit a ani na to nemyslím.

K databázi se vrátíme o něco později, ale prozatím začneme psát kód pro náš engine. Nejprve ale trochu hardwaru. Začít.

Počátek času

V tuto chvíli máme jen nějaké představy a pochopení fungování systému, který chceme implementovat, ale samotná implementace zatím není. Nemáme s čím pracovat: nemáme žádné funkce - a jak si vzpomínáte, rozdělili jsme to na 2 části: interní a externí. Abeceda vyžaduje písmena, ale externí funkce vyžadují interní funkce – tím začneme.

Ale ne tak rychle. Aby to fungovalo, musíte jít trochu hlouběji. Náš systém představuje hierarchii a každý hierarchický systém má začátek: přípojný bod v Linuxu, lokální disk ve Windows, systém státu, společnosti, vzdělávací instituce atd. Každý prvek takového systému je někomu podřízen a může mít více podřízených a k oslovování svých sousedů a jejich podřízených využívá nadřízené nebo samotný začátek. Dobrým příkladem hierarchického systému je rodokmen: vybere se výchozí bod – nějaký předek – a jdeme pryč. V našem systému také potřebujeme výchozí bod, ze kterého budeme vyrůstat větve – moduly, pluginy atp. Potřebujeme nějaké rozhraní, přes které budou všechny naše moduly „komunikovat“. Pro další práci se musíme seznámit s konceptem „ návrhový vzor" a několik jejich implementací.

Designové vzory

Existuje spousta článků o tom, co to je a jaké existují odrůdy, téma je docela otřepané a neřeknu vám nic nového. Na mé oblíbené Wiki jsou informace na toto téma: kočár se skluzavkou a trochu víc.

Návrhové vzory se také často nazývají návrhové vzory nebo jednoduše vzory (z anglického slova pattern, v překladu znamená „vzor“). Dále v článcích, když budu mluvit o vzorech, budu mít na mysli designové vzory.

Z obrovského seznamu všemožných děsivých (a ne tak děsivých) názvů vzorů nás zatím zajímají pouze dva: registry a singleton.

Registr (nebo se registrujte) je vzor, ​​který funguje na určitém poli, do kterého můžete přidávat a odebírat určitou sadu objektů a získat přístup ke kterémukoli z nich a jeho schopnostem.

Samotář (nebo singleton) je vzor, ​​který zajišťuje, že může existovat pouze jedna instance třídy. Nelze jej zkopírovat, uspat ani probudit (mluvíme o magii PHP: __clone(), __sleep(), __wakeup()). Singleton má globální přístupový bod.

Definice nejsou úplné ani zobecněné, ale pro pochopení to stačí. Stejně je nepotřebujeme zvlášť. Zajímají nás schopnosti každého z těchto vzorů, ale v jedné třídě: takový vzor se nazývá singleton registry nebo Singleton Registry.

Co nám to dá?
  • Zaručeně budeme mít jedinou instanci registru, do které můžeme objekty kdykoli přidávat a používat je odkudkoli v kódu;
  • nebude možné jej zkopírovat a použít další nežádoucí (v tomto případě) kouzla jazyka PHP.

V této fázi stačí pochopit, že jeden registr nám umožní implementovat modulární strukturu systému, což jsme chtěli při diskuzi o cílech v , a zbytek pochopíte s postupem vývoje.

No, dost slov, pojďme tvořit!

První řádky

Protože se tato třída bude týkat funkčnosti jádra, začneme vytvořením složky v kořenu našeho projektu s názvem core, do které umístíme všechny třídy modulů jádra. Začneme registrem, takže soubor nazvěme registry.php

Možnost, že zvědavý uživatel zadá do řádku prohlížeče přímou adresu k našemu souboru, nás nezajímá, proto se před tím musíme chránit. K dosažení tohoto cíle nám stačí definovat určitou konstantu v hlavním spustitelném souboru, kterou zkontrolujeme. Myšlenka není nová, pokud si pamatuji, byla použita v Joomle. Jedná se o jednoduchou a fungující metodu, takže se zde obejdeme bez kol.

Protože chráníme něco, co je připojeno, budeme volat konstantu _PLUGSECURE_ :

If (!defined("_PLUGSECURE_")) ( die("Přímé volání modulu je zakázáno!"); )

Nyní, pokud se pokusíte získat přístup k tomuto souboru přímo, nic užitečného nevyjde, což znamená, že cíle bylo dosaženo.

Dále navrhuji stanovit určitý standard pro všechny naše moduly. Chci každému modulu poskytnout funkci, která o něm vrátí nějaké informace, jako je název modulu, a tato funkce musí být ve třídě vyžadována. K dosažení tohoto cíle píšeme následující:

Rozhraní StorableObject ( veřejná statická funkce getClassName(); )

Takhle. Nyní, když připojíme jakoukoli třídu bez funkce getClassName() zobrazí se nám chybová zpráva. Na to se teď nebudu zaměřovat, bude se nám to hodit později, alespoň pro testování a ladění.

Je čas na samotnou hodinu našeho registru jednotlivců. Začneme deklarováním třídy a některých jejích proměnných:

Class Registry implementuje StorableObject ( //název modulu, čitelný soukromý statický $className = "Registr"; //instance registru private static $instance; //pole objektů private static $objects = array();

Zatím je vše logické a srozumitelné. Nyní, jak si pamatujete, máme registr s vlastnostmi singleton, takže okamžitě napište funkci, která nám umožní pracovat s registrem tímto způsobem:

Veřejná statická funkce singleton() ( if(!isset(self::$instance)) ( $obj = __CLASS__; self::$instance = new $obj; ) return self::$instance; )

Doslova: funkce zkontroluje, zda instance našeho registru existuje: pokud ne, vytvoří ji a vrátí, pokud již existuje, jednoduše ji vrátí. V tomto případě nepotřebujeme žádnou magii, takže to pro ochranu prohlásíme za soukromé:

Soukromá funkce __construct()() soukromá funkce __clone()() soukromá funkce __wakeup()() soukromá funkce __sleep() ()

Nyní potřebujeme funkci pro přidání objektu do našeho registru - tato funkce se nazývá setter a rozhodl jsem se ji implementovat dvěma způsoby, abych ukázal, jak můžeme použít magii a poskytnout alternativní způsob, jak přidat objekt. První metoda je standardní funkce, druhá provede první pomocí kouzla __set().

//$object - cesta k připojenému objektu //$key - přístupový klíč k objektu v registru veřejná funkce addObject($key, $object) ( require_once($object); //vytvoření objektu v poli objektů self::$objects[ $key] = new $key(self::$instance); ) //alternativní metoda prostřednictvím magické veřejné funkce __set($key, $object) ( $this->addObject($key, $ objekt);)

Nyní, abychom přidali objekt do našeho registru, můžeme použít dva typy záznamů (řekněme, že jsme již vytvořili instanci registru $registry a chceme přidat soubor config.php):

$registry->addObject("config", "/core/config.php"); //běžná metoda $registry->config = "/core/config.php"; //prostřednictvím magické funkce PHP __set()

Oba záznamy budou plnit stejnou funkci – propojí soubor, vytvoří instanci třídy a umístí ji do registru s klíčem. Je zde jeden důležitý bod, na který v budoucnu nesmíme zapomenout: klíč objektu v registru se musí shodovat s názvem třídy v připojeném objektu. Když se podíváte na kód znovu, pochopíte proč.

Jakou nahrávku použijete, je jen na vás. Preferuji nahrávání magickou metodou - je „hezčí“ a kratší.

Takže jsme vyřešili přidávání objektu, nyní potřebujeme funkci pro přístup k připojenému objektu pomocí klíče - getter. Také jsem jej implementoval se dvěma funkcemi, podobnými setteru:

//získání objektu z registru //$key - klíč ve veřejné funkci pole getObject($key) ( //kontrola, zda je proměnná objektem if (is_object(self::$objects[$key])) ( //pokud ano, pak vrátíme tento objekt return self::$objects[$key]; ) ) //podobná metoda prostřednictvím magické veřejné funkce __get($key) ( if (is_object(self::$objects[$ klíč])) ( return self: :$objects[$key]; ) )

Stejně jako u setteru, abychom získali přístup k objektu, budeme mít 2 ekvivalentní položky:

$registry->getObject("config"); //běžná metoda $registry->config; //prostřednictvím magické funkce PHP __get()

Pozorný čtenář si hned položí otázku: proč v magické funkci __set() zavolám běžnou (nemagickou) funkci přidávání objektů, ale v getteru __get() zkopíruji kód funkce getObject() místo stejného volání? Upřímně, na tuto otázku nedokážu odpovědět dostatečně přesně, jen řeknu, že jsem měl problémy při práci s magií __get() v jiných modulech, ale při přepisování kódu „čelem“ žádné takové problémy nejsou.

Možná proto jsem v článcích často viděl výtky vůči magickým metodám PHP a rady, jak se jich vyvarovat.

"Všechna magie má svou cenu." © Rumplestiltskin

V této fázi je již připravena hlavní funkcionalita našeho registru: můžeme vytvořit jedinou instanci registru, přidávat objekty a přistupovat k nim jak konvenčními metodami, tak magickými metodami jazyka PHP. "A co smazání?"— tuto funkci zatím nebudeme potřebovat a nejsem si jistý, že se v budoucnu něco změní. Nakonec můžeme vždy přidat potřebnou funkcionalitu. Ale pokud se nyní pokusíme vytvořit instanci našeho registru,

$registry = Registry::singleton();

dostaneme chybu:

Fatální chyba: Class Registry obsahuje 1 abstraktní metodu, a proto musí být deklarována jako abstraktní nebo implementovat zbývající metody (StorableObject::getClassName) v ...

To vše proto, že jsme zapomněli napsat požadovanou funkci. Pamatujete si, že jsem na začátku mluvil o funkci, která vrací název modulu? To je to, co zbývá přidat pro plnou funkčnost. Je to jednoduché:

Veřejná statická funkce getClassName() ( return self::$className; )

Nyní by neměly být žádné chyby. Navrhuji přidat ještě jednu funkci, není to povinné, ale dříve nebo později se může hodit, v budoucnu ji využijeme pro kontrolu a ladění. Funkce vrátí jména všech objektů (modulů) přidaných do našeho registru:

Veřejná funkce getObjectsList() ( //pole, které vrátíme $names = array(); //získáte název každého objektu z pole objektů foreach(self::$objects as $obj) ( $names = $ obj->getClassName() ; ) //přidejte název modulu registru do pole array_push($names, self::getClassName()); //a vrátí návrat $names; )

To je vše. Tím je registr dokončen. Zkontrolujeme jeho práci? Při kontrole budeme potřebovat něco připojit - ať existuje konfigurační soubor. Vytvořte nový soubor core/config.php a přidejte minimální obsah, který náš registr vyžaduje:

//nezapomeňte zkontrolovat konstantu if (!defined("_PLUGSECURE_")) ( die("Přímé volání modulu je zakázáno!"); ) class Config ( //název modulu, čitelný soukromý statický $className = "Config "; veřejná statická funkce getClassName() ( return self::$className; ) )

Něco takového. Nyní přistoupíme k samotnému ověření. V kořenovém adresáři našeho projektu vytvořte soubor index.php a napište do něj následující kód:

Define("_PLUGSECURE_", true); //definoval konstantu pro ochranu před přímým přístupem k objektům require_once "/core/registry.php"; //připojen registr $registry = Registry::singleton(); //vytvořila jedinou instanci registru $registry->config = "/core/config.php"; //připoj naši, zatím zbytečnou konfiguraci //zobrazí názvy připojených modulů echo " Připojeno"; foreach ($registry->

  • ". $jména."
  • "; }

    Nebo, pokud se stále vyhýbáte magii, lze 5. řádek nahradit alternativní metodou:

    Define("_PLUGSECURE_", true); //definoval konstantu pro ochranu před přímým přístupem k objektům require_once "/core/registry.php"; //připojen registr $registry = Registry::singleton(); //vytvořila jedinou instanci registru $registry->addObject("config", "/core/config.php"); //připoj naši, zatím zbytečnou konfiguraci //zobrazí názvy připojených modulů echo " Připojeno"; foreach ($registry->getObjectsList() jako $names) ( echo "

  • ". $jména."
  • "; }

    Nyní otevřete prohlížeč a do adresního řádku napište http://localhost/index.php nebo jednoduše http://localhost/ (relevantní, pokud používáte standardní Open Server nebo podobná nastavení webového serveru)

    Ve výsledku bychom měli vidět něco takového:

    Jak vidíte, nejsou tam žádné chyby, což znamená, že vše funguje, k čemuž vám gratuluji :)

    Dnes se u toho zastavíme. V příštím článku se vrátíme k databázi a napíšeme třídu pro práci s MySQL SUDB, připojíme ji k registru a práci vyzkoušíme v praxi. Uvidíme se!

    Tento vzor, ​​stejně jako Singleton, zřídka způsobí pozitivní reakci vývojářů, protože způsobuje stejné problémy při testování aplikací. Přesto nadávají, ale aktivně využívají. Podobně jako Singleton se vzor Registry nachází v mnoha aplikacích a tak či onak výrazně zjednodušuje řešení určitých problémů.

    Zvažme obě možnosti v pořadí.

    To, co se nazývá „čistý registr“ nebo jednoduše Registr, je implementace třídy se statickým rozhraním. Hlavní rozdíl oproti vzoru Singleton je v tom, že blokuje možnost vytvořit alespoň jednu instanci třídy. S ohledem na to nemá smysl skrývat magické metody __clone() a __wakeup() za soukromý nebo chráněný modifikátor.

    Registrační třída musí mít dvě statické metody – getr a setter. Setter umístí předaný objekt do úložiště s vazbou na daný klíč. Getter tedy vrátí předmět z obchodu. Obchod není nic jiného než asociativní pole klíč–hodnota.

    Pro úplnou kontrolu nad registrem je zaveden další prvek rozhraní – metoda, která umožňuje odstranit objekt z úložiště.

    Kromě problémů shodných se vzorem Singleton existují ještě dva:

    • zavedení dalšího typu závislosti - na klíčích registru;
    • dva různé klíče registru mohou mít odkaz na stejný objekt

    V prvním případě se nelze vyhnout další závislosti. Do jisté míry jsme připoutáni ke klíčovým jménům.

    Druhý problém je vyřešen zavedením kontroly do metody Registry::set():

    Veřejná sada statických funkcí($key, $item) ( if (!array_key_exists($key, self::$_registry)) ( foreach (self::$_registry jako $val) ( if ($val === $item) ( throw new Exception("Položka již existuje"); ) ) self::$_registry[$key] = $item; ) )

    « Vzor čistého registru"vyvstává další problém - zvýšení závislosti kvůli nutnosti přístupu k setteru a getteru prostřednictvím názvu třídy. Nemůžete vytvořit odkaz na objekt a pracovat s ním, jako tomu bylo u vzoru Singleton, kdy byl tento přístup k dispozici:

    $instance = Singleton::getInstance(); $instance->Foo();

    Zde máme možnost uložit odkaz na instanci Singleton například do vlastnosti aktuální třídy a pracovat s ní tak, jak to vyžaduje ideologie OOP: předat ji jako parametr agregovaným objektům nebo ji použít v potomcích.

    K vyřešení tohoto problému existuje Implementace Singleton Registry, což se mnoha lidem nelíbí, protože to vypadá jako nadbytečný kód. Domnívám se, že důvodem tohoto postoje je určité nepochopení principů OOP nebo jejich záměrné ignorování.

    _registry[$klíč] = $objekt; ) statická veřejná funkce get($key) ( return self::getInstance()->_registry[$key]; ) soukromá funkce __wakeup() ( ) soukromá funkce __construct() ( ) soukromá funkce __clone() ( ) ) ?>

    Abych ušetřil peníze, záměrně jsem u metod a vlastností vynechal bloky komentářů. Nemyslím si, že jsou nutné.

    Jak jsem již řekl, zásadní rozdíl je v tom, že nyní je možné uložit odkaz na svazek registru a nepoužívat pokaždé těžkopádná volání statických metod. Tato varianta se mi zdá poněkud správnější. Souhlas nebo nesouhlas s mým názorem moc nezáleží, stejně jako můj názor samotný. Žádné implementační jemnosti nemohou odstranit vzor z řady zmíněných nevýhod.

    Rozhodl jsem se krátce napsat o vzorech často používaných v našem životě, více příkladů, méně vody, jdeme na to.

    jedináček

    Hlavním bodem „singlu“ je, že když řeknete „Potřebuji telefonní ústřednu“, řekli by vám „Už to tam bylo postaveno“, a ne „Postavme to znovu“. „Samotář“ je vždy sám.

    Class Singleton ( private static $instance = null; privátní funkce __construct())( /* ... @return Singleton */ ) // Ochrana proti vytvoření pomocí nové privátní funkce Singleton __clone() ( /* ... @return Singleton * / ) // Ochrana před vytvořením pomocí klonování soukromé funkce __wakeup() ( /* ... @return Singleton */ ) // Ochrana před vytvořením pomocí zrušení serializace veřejné statické funkce getInstance() ( if (is_null(self::$instance ) ) ( self::$instance = nové self; ) return self::$instance; ) )

    Registr (registr, deník záznamů)

    Jak název napovídá, tento vzor je navržen tak, aby ukládal záznamy, které jsou v něm umístěny, a podle toho tyto záznamy vracet (podle názvu), pokud jsou vyžadovány. V příkladu telefonní ústředny se jedná o registr ve vztahu k telefonním číslům obyvatel.

    Registr třídy ( private $registry = array(); public function set ($key, $object) ( $this->registry[$key] = $object; ) public function get($key) ( return $this->registry [$key]; ))

    Singletonův registr- nezaměňovat s)

    „Rejstřík“ je často „samotář“, ale nemusí to tak být vždy. Například v účtárně můžeme vytvořit několik deníků, v jednom jsou zaměstnanci od „A“ do „M“, ve druhém od „N“ do „Z“. Každý takový časopis bude „registr“, ale ne „jeden“, protože již existují 2 časopisy.

    Třída SingletonRegistry ( private static $instance = null; private $registry = array(); soukromá funkce __construct() ( /* ... @return Singleton */ ) // Ochrana před vytvořením pomocí nové soukromé funkce Singleton __clone() ( / * ... @return Singleton */ ) // Ochrana před vytvořením pomocí klonování soukromé funkce __wakeup() ( /* ... @return Singleton */ ) // Ochrana před vytvořením pomocí zrušení serializace veřejné statické funkce getInstance() ( if ( is_null(self::$instance)) ( self::$instance = nové self; ) return self::$instance; ) public function set($key, $object) ( $this->registry[$key] = $ objekt; ) veřejná funkce get($key) ( return $this->registry[$key]; ) )

    Multiton (skupina „jednotlivců“) nebo jinými slovyRegistr Singleton ) - nezaměňujte s registrem Singleton

    Často se „registr“ používá speciálně k ukládání „singles“. Ale protože vzor „registr“ není „generativní vzor“, ale rád bych zvážil „registr“ ve spojení s „singletonem“.Proto jsme vymysleli vzor Multiton, který podleVe svém jádru je to „registr“ obsahující několik „singlů“, z nichž každý má své vlastní „jméno“, pod kterým je možné k němu přistupovat.

    Krátký: umožňuje vytvářet objekty této třídy, ale pouze pokud objekt pojmenujete. Neexistuje žádný skutečný příklad, ale na internetu jsem našel následující příklad:

    Databáze tříd (privátní statická $instance = array(); soukromá funkce __construct() ( ) soukromá funkce __clone() ( ) veřejná statická funkce getInstance($key) ( if(!array_key_exists($key, self::$instance))) ( self::$instance[$key] = new self(); ) return self::$instances[$key]; ) ) $master = Database::getInstance("master"); var_dump($master); // object(Database)#1 (0) ( ) $logger = Database::getInstance("logger"); var_dump($logger); // object(Database)#2 (0) ( ) $masterDupe = Database::getInstance("master"); var_dump($masterDupe); // object(Database)#1 (0) ( ) // Závažná chyba: Volání soukromé databáze::__construct() z neplatného kontextu $dbFatalError = new Database(); // PHP Závažná chyba: Volání do soukromé databáze::__clone() $dbCloneError = klon $masterDupe;

    Objekt bazén

    V podstatě tento vzorec je „registr“, který ukládá pouze objekty, žádné řetězce, pole atd. typy dat.

    Továrna

    Podstatu vzoru téměř úplně vystihuje jeho název. Když potřebujete získat nějaké předměty, například krabice od džusu, nemusíte vědět, jak se vyrábí v továrně. Jednoduše řeknete „dej mi karton pomerančového džusu“ a „továrna“ vám vrátí požadovaný balíček. Jak? O tom všem rozhoduje samotná továrna, například „kopíruje“ již existující standard. Hlavním účelem „továrny“ je umožnit v případě potřeby změnit proces „vzhledu“ obalu džusu a spotřebiteli o tom nemusí být nic sdělováno, aby si to mohl vyžádat jako dříve. Jedna továrna se zpravidla zabývá „výrobou“ pouze jednoho typu „produktu“. Nedoporučuje se vytvářet „továrnu šťávy“ s ohledem na výrobu pneumatik pro automobily. Stejně jako v životě, tovární vzor často vytváří jediný člověk.

    Abstraktní třída AnimalAbstract ( chráněný $druh; veřejná funkce getSpecies() ( return $this->species; ) ) class Cat rozšiřuje AnimalAbstract ( protected $species = "kočka"; ) class Dog rozšiřuje AnimalAbstract ( chráněný $druh = "pes"; ) class AnimalFactory ( public static function factory($animal) ( switch ($animal) (case "kočka": $obj = new Cat(); break; case "pes": $obj = new Dog(); break; default : throw new Exception("Továrna na zvířata nemohla vytvořit zvíře druhu "" . $animal . """, 1000); ) return $obj; ) ) $cat = AnimalFactory::factory("cat"); // object(Cat)#1 echo $cat->getSpecies(); // kočka $pes = AnimalFactory::factory("pes"); // objekt(Pes)#1 echo $pes->getSpecies(); // pes $hippo = AnimalFactory::factory("hroch"); // Toto vyvolá výjimku

    Chtěl bych vás upozornit na skutečnost, že tovární metoda je také vzor, ​​nazývá se tovární metoda.

    stavitel (stavitel)

    Takže už jsme pochopili, že „Factory“ je automat na nápoje, už má vše připravené a vy jen řeknete, co potřebujete. „Builder“ je závod, který tyto nápoje vyrábí a obsahuje všechny složité operace a dokáže sestavit složité předměty z jednodušších (obal, etiketa, voda, příchutě atd.) dle požadavku.

    Class Bottle ( public $name; public $liters; ) /** * všichni stavitelé musí */ rozhraní BottleBuilderInterface ( veřejná funkce setName(); veřejná funkce setLiters(); veřejná funkce getResult(); ) třída CocaColaBuilder implementuje rozhraní BottleBuilderInterface ( private $ láhev; veřejná funkce __construct() ( $this->bottle = new Bottle(); ) veřejná funkce setName($value) ( ​​​​$this->bottle->name = $value; ) veřejná funkce setLiters($value) ($ this->lahev->litry = $value; ) veřejná funkce getResult() ( return $this->bottle; ) ) $juice = new CocaColaBuilder(); $juice->setName("Coca-Cola Light"); $juice->setLiters(2); $juice->getResult();

    Prototyp

    Připomíná továrnu, slouží také k vytváření objektů, ale s trochu jiným přístupem. Představte si, že jste v baru, pili jste pivo a dochází vám, říkáte barmanovi – udělejte mi další stejného druhu. Barman se na oplátku podívá na pivo, které pijete, a udělá kopii, jak jste požádali. PHP již implementaci tohoto vzoru má, nazývá se .

    $newJuice = klon $juice;

    Líná inicializace

    Například šéf vidí seznam sestav pro různé typy činností a myslí si, že tyto sestavy již existují, ale ve skutečnosti se zobrazují pouze názvy sestav a samotné sestavy ještě nebyly vygenerovány a budou se teprve generovat na objednávku (například kliknutím na tlačítko Zobrazit zprávu). Zvláštním případem líné inicializace je vytvoření objektu v okamžiku, kdy se k němu přistupuje. Na Wikipedii najdete jednu zajímavou, ale... podle teorie by správným příkladem v php byla např. funkce

    Adaptér nebo Wrapper (adaptér, obal)

    Tento vzor plně odpovídá svému názvu. Aby „sovětská“ zástrčka fungovala přes zásuvku Euro, je nutný adaptér. Přesně to dělá „adaptér“ – slouží jako meziobjekt mezi dvěma dalšími, které spolu nemohou přímo spolupracovat. I přes definici v praxi stále vidím rozdíl mezi Adapterem a Wrapperem.

    Třída MyClass ( public function methodA() () ) class MyClassWrapper ( public function __construct())( $this->myClass = new MyClass(); ) veřejná funkce __call($name, $arguments)( Log::info(" Chystáte se zavolat metodu $name."); return call_user_func_array(array($this->myClass, $name), $arguments); ) ) $obj = new MyClassWrapper(); $obj->methodA();

    Injekce závislosti

    Dependency injection umožňuje přesunout část odpovědnosti za některé funkce na jiné objekty. Pokud například potřebujeme najmout nové zaměstnance, nemůžeme si vytvořit vlastní HR oddělení, ale zavést závislost na personální společnosti, která zase na první žádost „potřebujeme člověka“ bude fungovat jako HR oddělení samo, nebo si najde jinou společnost (pomocí „lokátoru služeb“), která bude tyto služby poskytovat.
    „Injekce závislosti“ vám umožňuje posouvat a zaměňovat jednotlivé části společnosti bez ztráty celkové funkčnosti.

    Třída AppleJuice () // tato metoda je primitivní implementace vzoru Dependency injection a dále uvidíte tuto funkci getBottleJuice())( $obj = new Jablečný džus Jablečný džus)( return $obj; ) ) $bottleJuice = getBottleJuice();

    Teď si představte, že už nechceme jablečný džus, ale pomerančový džus.

    Třída AppleJuice() Třída Pomerančový džus() // tato metoda implementuje funkci Dependency injection getBottleJuice())( $obj = new Pomerančový džus; // zkontrolujte objekt, v případě, že nám podstrčili pivo (pivo není džus) if($obj instanceof Pomerančový džus)( return $obj; ) )

    Jak vidíte, museli jsme změnit nejen druh džusu, ale i kontrolu na typ džusu, což není příliš pohodlné. Mnohem správnější je použít princip inverze závislosti:

    Rozhraní Juice () Třída AppleJuice implementuje Juice () Třída OrangeJuice implementuje Juice () funkce getBottleJuice())( $obj = new OrangeJuice; // zkontrolujte objekt, v případě, že nám podstrčili pivo (pivo není džus) if($obj instanceof Džus)( return $obj; ) )

    Inverze závislosti se někdy zaměňuje s injekcí závislostí, ale není třeba je zaměňovat, protože Inverze závislosti je princip, nikoli vzorec.

    Lokátor služeb

    "Service Locator" je metoda implementace "Dependency Injection". Vrací různé typy objektů v závislosti na inicializačním kódu. Nechť je úkolem doručit náš džusový balíček, vytvořený stavitelem, továrnou nebo něčím jiným, kamkoli si kupující přeje. Řekneme lokátorovi „poskytněte nám doručovací službu“ a požádáme službu, aby doručila šťávu na požadovanou adresu. Dnes existuje jedna služba a zítra může být další. Nezáleží na tom, o jakou konkrétní službu se jedná, je pro nás důležité vědět, že tato služba dodá to, co jí řekneme a kde jí řekneme. Na druhé straně služby implementují „Deliver<предмет>na<адрес>».

    Pokud mluvíme o skutečném životě, pak pravděpodobně dobrým příkladem Service Locatoru by bylo rozšíření PDO PHP, protože Dnes pracujeme s databází MySQL a zítra můžeme pracovat s PostgreSQL. Jak jste již pochopili, naší třídě nezáleží na tom, do které databáze svá data posílá, důležité je, že to umí.

    $db = nové PDO(" mysql:dbname=test;host=localhost", $user, $pass); $db = nové PDO(" pgsql:dbname=test host=localhost", $user, $pass);

    Rozdíl mezi vložením závislosti a lokalizátorem služeb

    Pokud jste si toho ještě nevšimli, rád bych to vysvětlil. Injekce závislosti Výsledkem je, že nevrací službu (která může někam něco doručit), ale objekt, jehož data používá.

    Pokusím se vám říci o své implementaci vzoru Registry v PHP. Registry je OOP náhrada za globální proměnné, určená k ukládání dat a jejich přenosu mezi moduly systému. V souladu s tím je vybaven standardními vlastnostmi - zápis, čtení, mazání. Zde je typická implementace.

    No, tímto způsobem získáme hloupou náhradu metod $klíč = $hodnota - Registry::set($klíč, $hodnota) $klíč - Registr::get($key) unset($key) - odebrat Registry::remove ($key ) Začíná být nejasné – proč tento extra kód. Naučme tedy naši třídu dělat to, co globální proměnné neumí. Přidáme pepř.

    getMessage()); ) Amdy_Registry::unlock("test"); var_dump(Amdy_Registry::get("test")); ?>

    K typickým úkolům vzoru jsem přidal možnost zablokovat proměnnou před změnami, to je velmi výhodné na velkých projektech, nic náhodně nevložíte. Vhodné například pro práci s databázemi
    define('DB_DNS', 'mysql:host=localhost;dbname= ’);
    define('DB_USER', ' ’);
    define('DB_PASSWORD', ' ’);
    define('DB_HANDLE');

    Amdy_Regisrtry::set(DB_HANDLE, nové PDO(DB_DNS, DB_USER, DB_PASSWORD));
    Amdy_Registry::lock(DB_HANDLE);

    Nyní pro vysvětlení kódu, pro ukládání dat používáme statickou proměnnou $data, proměnná $lock ukládá data o klíčích, které jsou uzamčeny pro změnu. V síti zkontrolujeme, zda je proměnná uzamčena a změníme nebo přidáme do registru. Při mazání kontrolujeme i zámek, getter zůstává nezměněn s výjimkou výchozího nepovinného parametru. Dobře, stojí za to věnovat pozornost zpracování výjimek, které se z nějakého důvodu používá zřídka. Mimochodem, návrh výjimek už mám, počkejte si na článek. Níže je návrh kódu pro testování, zde je článek o testování, také by nebylo na škodu ho napsat, i když nejsem fanoušek TDD.

    V příštím článku funkcionalitu dále rozšíříme přidáním inicializace dat a implementací „lenosti“.