Problém inicializácie objektov v OOP aplikáciách v PHP. Nájdenie riešenia pomocou Registry, Factory Method, Service Locator a Dependency Injection vzorov. Vzory OOP s príkladmi a popismi Autoritatívne registro php

Problém inicializácie objektov v OOP aplikáciách v PHP. Nájdenie riešenia pomocou Registry, Factory Method, Service Locator a Dependency Injection vzorov

Stáva sa, že programátori konsolidujú úspešné riešenia vo forme návrhových vzorov. Existuje veľa literatúry o vzoroch. Kniha Gang of Four „Design Patterns“ od Ericha Gammu, Richarda Helma, Ralpha Johnsona a Johna Vlissidesa“ a možno aj „Patterns of Enterprise Application Architecture“ od Martina Fowlera sa určite považujú za klasiku. To najlepšie, čo som čítal s príkladmi v PHP - toto. Náhodou je všetka táto literatúra dosť zložitá pre ľudí, ktorí práve začali ovládať OOP. Napadlo ma teda predstaviť niektoré vzory, ktoré považujem za najužitočnejšie, vo výrazne zjednodušenej forme. slovami, tento článok je mojím prvým pokusom o interpretáciu dizajnových vzorov v štýle KISS.
Dnes si povieme, aké problémy môžu nastať pri inicializácii objektov v aplikácii OOP a ako môžete použiť niektoré populárne návrhové vzory na vyriešenie týchto problémov.

Príklad

Moderná OOP aplikácia pracuje s desiatkami, stovkami a niekedy aj tisíckami objektov. Poďme sa teda bližšie pozrieť na to, ako sú tieto objekty inicializované v našich aplikáciách. Inicializácia objektu je jediným aspektom, ktorý nás v tomto článku zaujíma, preto som sa rozhodol vynechať všetky „extra“ implementácie.
Povedzme, že sme vytvorili super-duper užitočnú triedu, ktorá dokáže odoslať požiadavku GET na konkrétny URI a vrátiť HTML z odpovede servera. Aby naša trieda nevyzerala príliš jednoducho, nech skontroluje aj výsledok a ak server odpovie „nesprávne“, vyvolá výnimku.

Class Grabber ( verejná funkcia get($url) (/** vráti HTML kód alebo vyvolá výnimku */) )

Vytvorme ďalšiu triedu, ktorej objekty budú zodpovedné za filtrovanie prijatého HTML. Metóda filtra berie ako argumenty kód HTML a selektor CSS a vracia pole prvkov nájdených pre daný selektor.

Class HtmlExtractor ( filter verejnej funkcie ($html, $selector) (/** vráti pole filtrovaných prvkov */) )

Teraz si predstavte, že potrebujeme získať výsledky vyhľadávania na Google pre dané kľúčové slová. K tomu si predstavíme ďalšiu triedu, ktorá bude využívať triedu Grabber na odoslanie požiadavky a triedu HtmlExtractor na extrakciu potrebného obsahu. Bude obsahovať aj logiku na zostavenie URI, selektor na filtrovanie prijatého HTML a spracovanie získaných výsledkov.

Trieda GoogleFinder ( private $grabber; private $filter; verejná funkcia __construct() ( $this->grabber = new Grabber(); $this->filter = new HtmlExtractor(); ) verejná funkcia find($searchString) ( /* * vráti pole založených výsledkov */) )

Všimli ste si, že inicializácia objektov Grabber a HtmlExtractor je v konštruktore triedy GoogleFinder? Zamyslime sa nad tým, aké dobré je toto rozhodnutie.
Samozrejme, napevno zakódovať vytváranie objektov v konštruktore nie je dobrý nápad. A preto. Po prvé, v testovacom prostredí nebudeme môcť jednoducho prepísať triedu Grabber, aby sme sa vyhli odoslaniu skutočnej požiadavky. Aby sme boli spravodliví, stojí za to povedať, že to možno urobiť pomocou rozhrania Reflection API. Tie. technická možnosť existuje, ale toto nie je ani zďaleka najpohodlnejší a najzrejmejší spôsob.
Po druhé, rovnaký problém nastane, ak chceme znova použiť logiku GoogleFinder s inými implementáciami Grabber a HtmlExtractor. Vytváranie závislostí je pevne zakódované v konštruktore triedy. A v najlepšom prípade budeme môcť zdediť GoogleFinder a prepísať jeho konštruktor. A aj to len vtedy, ak je rozsah vlastností grabberu a filtra chránený alebo verejný.
Posledný bod, zakaždým, keď vytvoríme nový objekt GoogleFinder, v pamäti sa vytvorí nový pár objektov závislosti, hoci jeden objekt Grabber a jeden objekt HtmlExtractor môžeme celkom jednoducho použiť vo viacerých objektoch GoogleFinder.
Myslím, že už chápete, že inicializáciu závislosti je potrebné presunúť mimo triedy. Môžeme požadovať, aby sa už pripravené závislosti odovzdali konštruktorovi triedy GoogleFinder.

Trieda GoogleFinder ( private $grabber; private $filter; public function __construct(Grabber $grabber, HtmlExtractor $filter) ( $this->grabber = $grabber; $this->filter = $filter; ) verejná funkcia find($searchString) ( /** vráti pole založených výsledkov */) )

Ak chceme dať ostatným vývojárom možnosť pridávať a používať ich vlastné implementácie Grabber a HtmlExtractor, potom by sme mali zvážiť zavedenie rozhraní pre nich. V tomto prípade je to nielen užitočné, ale aj potrebné. Domnievam sa, že ak v projekte používame iba jednu implementáciu a neočakávame, že v budúcnosti vytvoríme nové, mali by sme odmietnuť vytvorenie rozhrania. Je lepšie konať podľa situácie a urobiť jednoduchý refaktoring, keď je to skutočne potrebné.
Teraz máme všetky potrebné triedy a v ovládači môžeme použiť triedu GoogleFinder.

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

Zhrňme si priebežné výsledky. Napísali sme veľmi málo kódu a na prvý pohľad sme neurobili nič zlé. Ale... čo ak potrebujeme použiť objekt ako GoogleFinder na inom mieste? Jeho vytvorenie budeme musieť duplikovať. V našom príklade je to len jeden riadok a problém nie je taký nápadný. V praxi môže byť inicializácia objektov pomerne zložitá a môže trvať až 10 riadkov alebo aj viac. Objavujú sa aj ďalšie problémy typické pre duplikáciu kódu. Ak počas procesu refaktorovania potrebujete zmeniť názov použitej triedy alebo logiku inicializácie objektu, budete musieť manuálne zmeniť všetky miesta. myslim ze vies ako to dopadlo :)
Zvyčajne sa s pevným kódom zaobchádza jednoducho. V konfigurácii sú zvyčajne zahrnuté duplicitné hodnoty. To vám umožňuje centrálne meniť hodnoty na všetkých miestach, kde sa používajú.

Šablóna registra.

Rozhodli sme sa teda presunúť vytváranie objektov do konfigurácie. Poďme to urobiť.

$registre = new ArrayObject(); $registry["grabber"] = new Grabber(); $registry["filter"] = new HtmlExtractor(); $registry["google_finder"] = nový GoogleFinder($registry["grabber"], $registry["filter"]);
Všetko, čo musíme urobiť, je odovzdať náš ArrayObject ovládaču a problém je vyriešený.

Class Controller ( súkromný $registre; verejná funkcia __construct(ArrayObject $registry) ( $this->registre = $registry; ) verejná funkcia action() ( /* Niektoré veci */ $results = $this->registre["google_finder" ]->find("hľadaný reťazec"); /* Urobte niečo s výsledkami */ ) )

Myšlienku Registra môžeme ďalej rozvíjať. Zdediť ArrayObject, zapuzdreť vytváranie objektov v rámci novej triedy, zakázať pridávanie nových objektov po inicializácii atď. Ale podľa môjho názoru daný kód plne objasňuje, čo je šablóna Registry. Tento vzorec nie je generatívny, ale určitým spôsobom rieši naše problémy. Register je len kontajner, do ktorého môžeme ukladať objekty a prenášať ich v rámci aplikácie. Aby boli objekty dostupné, musíme ich najprv vytvoriť a zaregistrovať v tomto kontajneri. Pozrime sa na výhody a nevýhody tohto prístupu.
Na prvý pohľad sme svoj cieľ splnili. Prestali sme pevne kódovať názvy tried a vytvárať objekty na jednom mieste. Objekty vytvárame v jedinej kópii, čo zaručuje ich opätovné použitie. Ak sa zmení logika vytvárania objektov, potom bude potrebné upraviť iba jedno miesto v aplikácii. Ako bonus sme dostali možnosť centrálne spravovať objekty v Registri. Môžeme ľahko získať zoznam všetkých dostupných objektov a vykonať s nimi nejaké manipulácie. Poďme sa teraz pozrieť na to, čo sa nám na tejto šablóne nemusí páčiť.
Najprv musíme vytvoriť objekt pred jeho registráciou v registri. V súlade s tým existuje vysoká pravdepodobnosť vytvorenia „zbytočných predmetov“, t.j. tie, ktoré sa vytvoria v pamäti, no v aplikácii sa nepoužijú. Áno, objekty môžeme pridávať do Registra dynamicky, t.j. vytvárať len tie objekty, ktoré sú potrebné na spracovanie konkrétnej požiadavky. Tak či onak to budeme musieť ovládať manuálne. V dôsledku toho bude časom veľmi ťažké ho udržiavať.
Po druhé, máme novú závislosť na ovládači. Áno, objekty môžeme prijímať prostredníctvom statickej metódy v Registri, takže Registry nemusíme odovzdávať konštruktorovi. Ale podľa mňa by si to nemal robiť. Statické metódy sú ešte tesnejšie spojenie ako vytváranie závislostí vo vnútri objektu a ťažkosti pri testovaní (na túto tému).
Po tretie, rozhranie ovládača nám nehovorí nič o tom, aké objekty používa. Môžeme získať akýkoľvek objekt dostupný v registri v ovládači. Bude pre nás ťažké povedať, ktoré objekty ovládač používa, kým neskontrolujeme celý jeho zdrojový kód.

Továrenská metóda

Naša najväčšia sťažnosť na Registry je, že objekt musí byť inicializovaný predtým, ako k nemu bude možné pristupovať. Namiesto inicializácie objektu v konfigurácii môžeme oddeliť logiku vytvárania objektov do inej triedy, ktorú môžeme „požiadať“ o vytvorenie objektu, ktorý potrebujeme. Triedy, ktoré sú zodpovedné za vytváranie objektov, sa nazývajú továrne. A návrhový vzor sa nazýva Factory Method. Pozrime sa na príklad továrne.

Class Factory ( verejná funkcia getGoogleFinder() ( return new GoogleFinder($this->getGrabber(), $this->getHtmlExtractor()); ) súkromná funkcia getGrabber() ( return new Grabber(); ) súkromná funkcia getHtmlExtractor() ( vrátiť nový HtmlFiletr(); ) )

Spravidla sa vyrábajú továrne, ktoré sú zodpovedné za vytvorenie jedného typu objektu. Niekedy môže továreň vytvoriť skupinu súvisiacich objektov. Môžeme použiť ukladanie do vyrovnávacej pamäte do vlastnosti, aby sme sa vyhli opätovnému vytváraniu objektov.

Class Factory ( súkromné ​​$finder; verejná funkcia getGoogleFinder() ( if (null === $this->finder) ( $this->finder = new GoogleFinder($this->getGrabber(), $this->getHtmlExtractor() ); ) vrátiť $this->finder; ) )

Môžeme parametrizovať továrenskú metódu a delegovať inicializáciu na iné továrne v závislosti od prichádzajúceho parametra. Toto už bude šablóna Abstract Factory.
Ak potrebujeme modularizovať aplikáciu, môžeme požadovať, aby každý modul poskytoval svoje vlastné továrne. Tému tovární môžeme ďalej rozvíjať, ale myslím si, že podstata tejto šablóny je jasná. Pozrime sa, ako budeme používať továreň v ovládači.

Class Controller ( private $factory; verejná funkcia __construct(Factory $factory) ( $this->factory = $factory; ) verejná funkcia action() ( /* Niektoré veci */ $results = $this->factory->getGoogleFinder( )->find("hľadaný reťazec"); /* Urobte niečo s výsledkami */ ) )

Medzi výhody tohto prístupu patrí jeho jednoduchosť. Naše objekty sú vytvorené explicitne a vaše IDE vás jednoducho dovedie na miesto, kde sa to stane. Vyriešili sme aj problém s Registry, takže objekty v pamäti sa vytvoria len vtedy, keď o to „požiadame“ ​​továreň. Ale ešte sme sa nerozhodli, ako dodať potrebné továrne kontrolórom. Tu je viacero možností. Môžete použiť statické metódy. Môžeme nechať kontrolórov, aby si sami vytvorili potrebné továrne a anulovali všetky naše pokusy zbaviť sa kopírovania a vkladania. Môžete vytvoriť továreň na továrne a odovzdať iba to ovládačovi. Ale dostať objekty do ovládača bude trochu komplikovanejšie a budete musieť spravovať závislosti medzi továrňami. Navyše nie je úplne jasné, čo robiť, ak chceme moduly využívať v našej aplikácii, ako registrovať továrne modulov, ako spravovať spojenia medzi továrňami z rôznych modulov. Vo všeobecnosti sme stratili hlavnú výhodu továrne - explicitné vytváranie objektov. A stále sme nevyriešili problém „implicitného“ rozhrania ovládača.

Vyhľadávač služieb

Šablóna Service Locator vám umožňuje vyriešiť chýbajúcu fragmentáciu tovární a riadiť vytváranie objektov automaticky a centrálne. Ak sa nad tým zamyslíme, môžeme zaviesť ďalšiu vrstvu abstrakcie, ktorá bude zodpovedná za vytváranie objektov v našej aplikácii a riadenie vzťahov medzi týmito objektmi. Aby táto vrstva mohla vytvárať objekty za nás, budeme jej musieť dať vedomosti, ako na to.
Podmienky vzoru lokátora služieb:
  • Služba je hotový predmet, ktorý je možné získať z kontajnera.
  • Definícia služby – logika inicializácie služby.
  • Kontajner (Service Container) je centrálny objekt, ktorý uchováva všetky popisy a na základe nich môže vytvárať služby.
Každý modul môže zaregistrovať svoje popisy služieb. Aby sme dostali nejakú službu z kontajnera, budeme si ju musieť vyžiadať kľúčom. Existuje veľa možností na implementáciu Service Locator, v najjednoduchšej verzii môžeme použiť ArrayObject ako kontajner a uzáver ako popis služieb.

Class ServiceContainer rozširuje ArrayObject ( verejná funkcia get($key) ( if (is_callable($this[$key])) ( return call_user_func($this[$key]); ) vyvoláva novú \RuntimeException("Nedá sa nájsť definícia služby pod kľúč [ $key ]"); ) )

Potom bude registrácia definícií vyzerať takto:

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

A použitie v ovládači je takéto:

Class Controller ( private $container; public function __construct(ServiceContainer $container) ( $this->container = $container; ) public function action() ( /* Some things */ $results = $this->container->get( "google_finder")->find("hľadaný reťazec"); /* Urobte niečo s výsledkami */ ) )

Servisný kontajner môže byť veľmi jednoduchý alebo môže byť veľmi zložitý. Napríklad Symfony Service Container poskytuje množstvo funkcií: parametre, rozsahy služieb, vyhľadávanie služieb podľa značiek, aliasov, súkromných služieb, možnosť vykonávať zmeny v kontajneri po pridaní všetkých služieb (prekladače) a mnoho ďalšieho. DIExtraBundle ďalej rozširuje možnosti štandardnej implementácie.
Ale vráťme sa k nášmu príkladu. Ako vidíte, Service Locator nielenže rieši všetky rovnaké problémy ako predchádzajúce šablóny, ale tiež uľahčuje používanie modulov s vlastnými definíciami služieb.
Okrem toho sme na rámcovej úrovni dostali ďalšiu úroveň abstrakcie. Totiž zmenou metódy ServiceContainer::get môžeme napríklad nahradiť objekt proxy. A rozsah použitia proxy objektov je obmedzený iba fantáziou vývojára. Tu môžete implementovať paradigmu AOP, LazyLoading atď.
Ale väčšina vývojárov stále považuje Service Locator za anti-vzor. Pretože teoreticky môžeme mať toľko tzv Triedy Container Aware (t. j. triedy, ktoré obsahujú odkaz na kontajner). Napríklad náš Controller, v rámci ktorého môžeme získať akúkoľvek službu.
Pozrime sa, prečo je to zlé.
Najprv znova testovanie. Namiesto vytvárania simulácií iba pre triedy používané v testoch, budete musieť zosmiešniť celý kontajner alebo použiť skutočný kontajner. Prvá možnosť vám nevyhovuje, pretože... v testoch musíte napísať veľa zbytočného kódu, po druhé, pretože odporuje to zásadám testovania jednotiek a môže viesť k dodatočným nákladom na údržbu testov.
Po druhé, bude pre nás ťažké refaktorovať. Zmenou akejkoľvek služby (alebo ServiceDefinition) v kontajneri budeme nútení skontrolovať aj všetky závislé služby. A tento problém nie je možné vyriešiť pomocou IDE. Nájsť takéto miesta v celej aplikácii nebude také jednoduché. Okrem závislých služieb budete musieť skontrolovať aj všetky miesta, kde sa refaktorovaná služba získava z kontajnera.
No a tretím dôvodom je, že nekontrolované vyťahovanie služieb z kontajnera skôr či neskôr povedie k neporiadku v kóde a zbytočnému zmätku. Je to ťažké vysvetliť, budete musieť stráviť stále viac času, aby ste pochopili, ako tá alebo oná služba funguje, inými slovami, môžete úplne pochopiť, čo robí alebo ako funguje trieda, iba ak si prečítate celý jej zdrojový kód.

Injekcia závislosti

Čo ešte môžete urobiť, aby ste obmedzili používanie kontajnera v aplikácii? Do rámca môžete preniesť kontrolu nad vytváraním všetkých používateľských objektov vrátane ovládačov. Inými slovami, používateľský kód by nemal volať metódu get kontajnera. V našom príklade môžeme do kontajnera pridať definíciu ovládača:

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

A zbavte sa kontajnera v ovládači:

Ovládač triedy ( súkromný $finder; verejná funkcia __construct(GoogleFinder $finder) ( $this->finder = $finder; ) verejná funkcia action() ( /* Niektoré veci */ $results = $this->finder->find( "hľadaný reťazec"); /* Urobte niečo s výsledkami */ ) )

Tento prístup (keď nie je poskytovaný prístup ku kontajneru služieb klientskym triedam) sa nazýva Dependency Injection. Ale táto šablóna má tiež výhody aj nevýhody. Pokiaľ dodržiavame zásadu jedinej zodpovednosti, kód vyzerá veľmi krásne. V prvom rade sme sa zbavili kontajnera v triedach klientov, vďaka čomu je ich kód oveľa prehľadnejší a jednoduchší. Radič môžeme jednoducho otestovať výmenou potrebných závislostí. Každú triedu môžeme vytvoriť a otestovať nezávisle od ostatných (vrátane tried radičov) pomocou prístupu TDD alebo BDD. Pri vytváraní testov môžeme abstrahovať od kontajnera a neskôr pridať definíciu, keď potrebujeme použiť konkrétne inštancie. Vďaka tomu bude náš kód jednoduchší a prehľadnejší a testovanie transparentnejšie.
Treba ale spomenúť aj druhú stranu mince. Faktom je, že ovládače sú veľmi špecifické triedy. Začnime tým, že kontrolór spravidla obsahuje súbor akcií, čo znamená, že porušuje zásadu jedinej zodpovednosti. V dôsledku toho môže mať trieda radiča oveľa viac závislostí, ako je potrebné na vykonanie konkrétnej akcie. Použitie lenivej inicializácie (inštancia objektu sa vytvorí pri prvom použití a predtým sa použije odľahčený proxy server) do určitej miery rieši problém s výkonom. Ale z architektonického hľadiska vytváranie mnohých závislostí na ovládači tiež nie je úplne správne. Navyše testovanie ovládačov je zvyčajne zbytočná operácia. Všetko, samozrejme, závisí od toho, ako je testovanie vo vašej aplikácii zorganizované a ako sa na to cítite vy.
Z predchádzajúceho odseku ste si uvedomili, že použitie Dependency Injection úplne neodstráni architektonické problémy. Zamyslite sa preto nad tým, ako vám bude pohodlnejšie, či odkaz na kontajner uložiť do ovládačov alebo nie. Tu neexistuje jediné správne riešenie. Myslím si, že oba prístupy sú dobré, pokiaľ kód ovládača zostane jednoduchý. Rozhodne by ste však nemali vytvárať okrem ovládačov aj služby Conatiner Aware.

závery

Nastal čas zhrnúť všetko, čo bolo povedané. A veľa sa toho popísalo... :)
Takže na štruktúrovanie práce pri vytváraní objektov môžeme použiť nasledujúce vzory:
  • Registratúra: Šablóna má zjavné nevýhody, z ktorých najzákladnejšou je potreba vytvárať objekty pred ich vložením do spoločného kontajnera. Je zrejmé, že z jej používania budeme mať viac problémov ako výhod. Toto zjavne nie je najlepšie využitie šablóny.
  • Továrenská metóda: Hlavná výhoda vzoru: objekty sú vytvorené explicitne. Hlavná nevýhoda: kontrolóri sa buď musia starať o to, aby si sami vytvorili továrne, čo úplne nevyrieši problém s pevným kódovaním názvov tried, alebo musí byť framework zodpovedný za to, že kontrolérom poskytne všetky potrebné továrne, čo nebude také zrejmé. Chýba tu možnosť centrálne riadiť proces vytvárania objektov.
  • Vyhľadávač služieb: Pokročilejší spôsob ovládania vytvárania objektov. Dodatočnú úroveň abstrakcie možno použiť na automatizáciu bežných úloh, s ktorými sa stretávame pri vytváraní objektov. Napríklad:
    class ServiceContainer rozširuje ArrayObject ( verejná funkcia get($key) ( if (is_callable($this[$key])) ( $obj = call_user_func($this[$key]); if ($obj instanceof RequestAwareInterface) ( $obj- >setRequest($this->get("request")); ) vrátiť $obj; ) vyvolať novú \RuntimeException("Nedá sa nájsť definícia služby pod kľúčom [ $key ]"); ) )
    Nevýhodou Service Locator je, že verejné API tried prestáva byť informatívne. Je potrebné prečítať si celý kód triedy, aby ste pochopili, aké služby sa v nej používajú. Trieda, ktorá obsahuje odkaz na kontajner, sa testuje ťažšie.
  • Injekcia závislosti: V podstate môžeme použiť rovnaký kontajner služieb ako v predchádzajúcom vzore. Rozdiel je v tom, ako sa tento kontajner používa. Ak sa vyhneme tomu, aby boli triedy závislé od kontajnera, získame jasné a explicitné API triedy.
Toto nie je všetko, čo by som vám chcel povedať o probléme vytvárania objektov v aplikáciách PHP. Nechýba ani vzor Prototype, neuvažovali sme o použití Reflection API, bokom sme nechali problém lenivého načítavania služieb a mnohé ďalšie nuansy. Článok bol dosť dlhý, tak to uzatváram :)
Chcel som ukázať, že Dependency Injection a iné vzorce nie sú také zložité, ako sa bežne verí.
Ak hovoríme o Dependency Injection, potom existujú implementácie tohto vzoru napríklad KISS

Dotknutie sa štruktúry budúcej databázy. Začalo sa a nemôžeme ustúpiť a ani na to nemyslím.

K databáze sa vrátime o niečo neskôr, ale zatiaľ začneme písať kód pre náš engine. Najprv však trochu hardvéru. Začať.

Začiatok času

Momentálne máme len nejaké predstavy a pochopenie fungovania systému, ktorý chceme implementovať, no k samotnej implementácii zatiaľ nedochádza. Nemáme s čím pracovať: nemáme žiadnu funkčnosť - a ako si pamätáte, rozdelili sme to na 2 časti: internú a externú. Abeceda vyžaduje písmená, ale externá funkčnosť vyžaduje internú funkčnosť – tam začneme.

Ale nie tak rýchlo. Aby to fungovalo, musíte ísť trochu hlbšie. Náš systém predstavuje hierarchiu a každý hierarchický systém má začiatok: bod pripojenia v Linuxe, lokálny disk vo Windows, systém štátu, spoločnosť, vzdelávacia inštitúcia atď. Každý prvok takéhoto systému je niekomu podriadený a môže mať viacero podriadených a na oslovovanie svojich susedov a ich podriadených využíva nadriadených alebo samotný začiatok. Dobrým príkladom hierarchického systému je rodokmeň: vyberie sa východiskový bod – nejaký predok – a ideme preč. V našom systéme potrebujeme aj východiskový bod, z ktorého budeme vyrastať pobočky – moduly, pluginy atď. Potrebujeme nejaké rozhranie, cez ktoré budú všetky naše moduly „komunikovať“. Pre ďalšiu prácu sa musíme zoznámiť s konceptom “ dizajnový vzor" a pár ich implementácií.

Dizajnové vzory

Existuje veľa článkov o tom, čo to je a aké odrody existujú; téma je dosť otrepaná a nepoviem vám nič nové. Na mojej obľúbenej Wiki sú informácie o tejto téme: kočiar so šmýkačkou a trochu viac.

Dizajnové vzory sa tiež často nazývajú dizajnové vzory alebo jednoducho vzory (z anglického slova pattern, v preklade znamená „vzor“). Ďalej v článkoch, keď hovorím o vzoroch, budem mať na mysli dizajnové vzory.

Z obrovského zoznamu všelijakých strašidelných (a nie tak strašidelných) názvov vzorov nás zatiaľ zaujímajú len dva: register a singleton.

Registratúra (alebo sa zaregistrujte) je vzor, ​​ktorý funguje na určitom poli, do ktorého môžete pridať a odstrániť určitú množinu objektov a získať prístup ku ktorémukoľvek z nich a ich schopnostiam.

Samotár (alebo singleton) je vzor, ​​ktorý zabezpečuje, že môže existovať iba jedna inštancia triedy. Nedá sa skopírovať, uspať ani zobudiť (hovoríme o PHP mágii: __clone(), __sleep(), __wakeup()). Singleton má globálny prístupový bod.

Definície nie sú úplné ani zovšeobecnené, ale na pochopenie to stačí. Samostatne ich aj tak nepotrebujeme. Zaujímajú nás schopnosti každého z týchto vzorov, ale v jednej triede: takýto vzor sa nazýva singleton register alebo Singleton register.

Čo nám to dá?
  • Zaručene budeme mať jedinú inštanciu registra, do ktorej môžeme kedykoľvek pridávať objekty a používať ich odkiaľkoľvek v kóde;
  • nebude možné ho skopírovať a použiť inú nechcenú (v tomto prípade) mágiu jazyka PHP.

V tejto fáze stačí pochopiť, že jeden register nám umožní implementovať modulárnu štruktúru systému, čo sme chceli pri diskusii o cieľoch v , a zvyšok pochopíte, ako vývoj napreduje.

No dosť slov, poďme tvoriť!

Prvé riadky

Keďže táto trieda sa bude týkať funkčnosti jadra, začneme vytvorením priečinka v koreňovom adresári nášho projektu s názvom core, do ktorého umiestnime všetky triedy modulov jadra. Začneme registrom, takže súbor nazvime register.php

Možnosť, že zvedavý používateľ zadá do riadku prehliadača priamu adresu nášho súboru, nás nezaujíma, preto sa musíme pred tým chrániť. Na dosiahnutie tohto cieľa nám stačí zadefinovať určitú konštantu v hlavnom spustiteľnom súbore, ktorú skontrolujeme. Myšlienka nie je nová; pokiaľ si pamätám, bola použitá v Joomle. Ide o jednoduchú a fungujúcu metódu, takže sa tu zaobídeme aj bez bicyklov.

Keďže chránime niečo, čo je pripojené, budeme volať konštantu _PLUGSECURE_ :

If (!defined("_PLUGSECURE_")) ( die("Priame volanie modulu je zakázané!"); )

Teraz, ak sa pokúsite získať prístup k tomuto súboru priamo, nič užitočné nevyjde, čo znamená, že cieľ bol dosiahnutý.

Ďalej navrhujem stanoviť určitý štandard pre všetky naše moduly. Chcem poskytnúť každému modulu funkciu, ktorá o ňom vráti nejaké informácie, ako napríklad názov modulu, a táto funkcia musí byť v triede vyžadovaná. Na dosiahnutie tohto cieľa napíšeme nasledovné:

Rozhranie StorableObject ( verejná statická funkcia getClassName(); )

Páči sa ti to. Teraz, ak pripojíme akúkoľvek triedu bez funkcie getClassName() zobrazí sa nám chybové hlásenie. Zatiaľ sa tomu nebudem venovať, bude sa nám to hodiť neskôr, aspoň na testovanie a ladenie.

Nastal čas na hodinu samotnej matriky nezadaných. Začneme deklarovaním triedy a niektorých jej premenných:

Class Registry implementuje StorableObject ( //meno modulu čitateľné private static $className = "Registry"; //inštancia registra private static $instance; //pole objektov private static $objects = array();

Zatiaľ je všetko logické a zrozumiteľné. Teraz, ako si pamätáte, máme register s vlastnosťami singleton, takže okamžite napíšme funkciu, ktorá nám umožní pracovať s registrom týmto spôsobom:

Verejná statická funkcia singleton() ( if(!isset(self::$instance)) ( $obj = __CLASS__; self::$instance = new $obj; ) return self::$instance; )

Doslova: funkcia skontroluje, či inštancia nášho registra existuje: ak nie, vytvorí ju a vráti, ak už existuje, jednoducho ju vráti. V tomto prípade nepotrebujeme žiadnu mágiu, takže to z dôvodu ochrany vyhlásime za súkromné:

Súkromná funkcia __construct()() súkromná funkcia __clone()() súkromná funkcia __wakeup()() súkromná funkcia __sleep() ()

Teraz potrebujeme funkciu na pridanie objektu do nášho registra – táto funkcia sa nazýva nastavovač a rozhodol som sa ju implementovať dvoma spôsobmi, aby som ukázal, ako môžeme použiť mágiu a poskytnúť alternatívny spôsob pridania objektu. Prvá metóda je štandardná funkcia, druhá vykoná prvú pomocou kúzla __set().

//$object - cesta k pripojenému objektu //$key - prístupový kľúč k objektu v registri verejná funkcia addObject($key, $object) ( required_once($object); //vytvorenie objektu v poli objektov self::$objects[ $key] = new $key(self::$instance); ) //alternatívna metóda prostredníctvom magickej verejnej funkcie __set($key, $object) ( $this->addObject($key, $ objekt);)

Teraz na pridanie objektu do nášho registra môžeme použiť dva typy záznamov (povedzme, že sme už vytvorili inštanciu registra $registry a chceme pridať súbor config.php):

$registry->addObject("config", "/core/config.php"); //bežná metóda $registry->config = "/core/config.php"; //prostredníctvom magickej funkcie PHP __set()

Oba záznamy budú vykonávať rovnakú funkciu – spoja súbor, vytvoria inštanciu triedy a umiestnia ju do registra pomocou kľúča. Je tu jeden dôležitý bod, na ktorý nesmieme v budúcnosti zabúdať: kľúč objektu v registri sa musí zhodovať s názvom triedy v pripojenom objekte. Ak sa znova pozriete na kód, pochopíte prečo.

Ktorý záznam použijete, je len na vás. Preferujem nahrávanie magickou metódou - je „krajšia“ a kratšia.

Takže sme vyriešili pridávanie objektu, teraz potrebujeme funkciu na prístup k pripojenému objektu pomocou kľúča - getter. Implementoval som to aj s dvoma funkciami, podobnými nastavovaču:

//získanie objektu z registra //$key - kľúč vo verejnej funkcii poľa getObject($key) ( //kontrola, či je premenná objektom if (is_object(self::$objects[$key])) ( //ak áno, potom vrátime tento objekt return self::$objects[$key]; ) ) //podobná metóda prostredníctvom magickej verejnej funkcie __get($key) ( if (is_object(self::$objects[$ kľúč])) ( return self: :$objects[$key]; ) )

Rovnako ako v prípade nastavovača, na získanie prístupu k objektu budeme mať 2 ekvivalentné položky:

$registry->getObject("config"); //bežná metóda $registry->config; //prostredníctvom magickej funkcie PHP __get()

Pozorný čitateľ si hneď položí otázku: prečo v magickej funkcii __set() volám bežnú (nie magickú) funkciu na pridávanie objektov, ale v getteri __get() skopírujem kód funkcie getObject() namiesto toho istého volania?Úprimne povedané, nemôžem na túto otázku odpovedať dostatočne presne, poviem len, že som mal problémy pri práci s mágiou __get() v iných moduloch, ale pri prepisovaní kódu „hlavou“ takéto problémy nie sú.

Možno preto som v článkoch často videl výčitky voči magickým metódam PHP a rady, ako sa ich vyhnúť.

"Všetka mágia má svoju cenu." © Rumplestiltskin

V tejto fáze je už hotová hlavná funkcionalita nášho registra: môžeme vytvoriť jednu inštanciu registra, pridávať objekty a pristupovať k nim konvenčnými metódami aj magickými metódami jazyka PHP. "A čo vymazanie?"— túto funkciu zatiaľ nebudeme potrebovať a nie som si istý, či sa v budúcnosti niečo zmení. Nakoniec môžeme vždy pridať potrebnú funkcionalitu. Ale ak sa teraz pokúsime vytvoriť inštanciu nášho registra,

$registre = Register::singleton();

dostaneme chybu:

Fatálna chyba: Register tried obsahuje 1 abstraktnú metódu, a preto musí byť vyhlásená za abstraktnú alebo implementovať zostávajúce metódy (StorableObject::getClassName) v ...

Všetko preto, že sme zabudli napísať požadovanú funkciu. Pamätáte si, že som na začiatku hovoril o funkcii, ktorá vracia názov modulu? To je to, čo zostáva pridať pre plnú funkčnosť. Je to jednoduché:

Verejná statická funkcia getClassName() ( return self::$className; )

Teraz by nemali byť žiadne chyby. Navrhujem pridať ešte jednu funkciu, nie je potrebná, ale skôr či neskôr sa môže hodiť, v budúcnosti ju využijeme na kontrolu a ladenie. Funkcia vráti názvy všetkých objektov (modulov) pridaných do nášho registra:

Verejná funkcia getObjectsList() ( //pole, ktoré vrátime $names = array(); //získame názov každého objektu z poľa objektov foreach(self::$objects as $obj) ( $names = $ obj->getClassName() ; ) //pridať názov modulu registra do poľa array_push($names, self::getClassName()); //a vrátiť návrat $names; )

To je všetko. Tým je register hotový. Skontrolujeme jeho prácu? Pri kontrole budeme musieť niečo pripojiť - nech existuje konfiguračný súbor. Vytvorte nový súbor core/config.php a pridajte minimálny obsah, ktorý náš register vyžaduje:

//nezabudnite skontrolovať konštantu if (!defined("_PLUGSECURE_")) ( die("Priame volanie modulu je zakázané!"); ) class Config ( //názov modulu, čitateľný súkromný statický $className = "Config "; verejná statická funkcia getClassName() ( return self::$className; ) )

Niečo také. Teraz prejdime k samotnému overovaniu. V koreňovom adresári nášho projektu vytvorte súbor index.php a napíšte doň nasledujúci kód:

Define("_PLUGSECURE_", true); //definoval konštantu na ochranu pred priamym prístupom k objektom require_once "/core/registry.php"; //pripojeny register $registre = Registry::singleton(); //vytvorila sa inštancia jedného registra $registry->config = "/core/config.php"; //pripoj našu, zatiaľ zbytočnú, konfiguráciu //zobrazenie názvov pripojených modulov echo " Pripojené"; foreach ($registry->

  • ". $names."
  • "; }

    Alebo, ak sa stále vyhýbate mágii, 5. riadok možno nahradiť alternatívnou metódou:

    Define("_PLUGSECURE_", true); //definoval konštantu na ochranu pred priamym prístupom k objektom require_once "/core/registry.php"; //pripojeny register $registre = Registry::singleton(); //vytvorila sa inštancia jedného registra $registry->addObject("config", "/core/config.php"); //pripoj našu, zatiaľ zbytočnú, konfiguráciu //zobrazenie názvov pripojených modulov echo " Pripojené"; foreach ($registry->getObjectsList() ako $names) ( echo "

  • ". $names."
  • "; }

    Teraz otvorte prehliadač a do panela s adresou napíšte http://localhost/index.php alebo jednoducho http://localhost/ (relevantné, ak používate štandardný otvorený server alebo podobné nastavenia webového servera)

    V dôsledku toho by sme mali vidieť niečo takéto:

    Ako vidíte, neexistujú žiadne chyby, čo znamená, že všetko funguje, k čomu vám blahoželám :)

    Dnes sa pri tomto zastavíme. V ďalšom článku sa vrátime k databáze a napíšeme triedu pre prácu s MySQL SUDB, napojíme ju na register a otestujeme prácu v praxi. Maj sa!

    Tento vzor, ​​podobne ako Singleton, len zriedka spôsobuje pozitívnu reakciu vývojárov, pretože spôsobuje rovnaké problémy pri testovaní aplikácií. Napriek tomu nadávajú, ale aktívne využívajú. Podobne ako Singleton, aj vzor Registry sa nachádza v mnohých aplikáciách a tak či onak výrazne zjednodušuje riešenie určitých problémov.

    Zvážme obe možnosti v poradí.

    To, čo sa nazýva „čistý register“ alebo jednoducho register, je implementácia triedy so statickým rozhraním. Hlavný rozdiel oproti vzoru Singleton je v tom, že blokuje možnosť vytvoriť aspoň jednu inštanciu triedy. Vzhľadom na to nemá zmysel skrývať magické metódy __clone() a __wakeup() za súkromný alebo chránený modifikátor.

    Registratúrna trieda musí mať dve statické metódy - getter a setter. Setter ukladá odovzdaný objekt do úložiska s väzbou na daný kľúč. Getter teda vráti predmet z obchodu. Obchod nie je nič iné ako asociatívne pole kľúč – hodnota.

    Pre úplnú kontrolu nad registrom je zavedený ďalší prvok rozhrania – metóda, ktorá umožňuje vymazať objekt z úložiska.

    Okrem problémov identických so vzorom Singleton existujú ďalšie dva:

    • zavedenie iného typu závislosti - na kľúčoch registra;
    • dva rôzne kľúče databázy Registry môžu mať odkaz na rovnaký objekt

    V prvom prípade nie je možné vyhnúť sa ďalšej závislosti. Do určitej miery sme pripútaní ku kľúčovým menám.

    Druhý problém je vyriešený zavedením kontroly do metódy Registry::set():

    Verejná sada statických funkcií($key, $item) ( if (!array_key_exists($key, self::$_registry)) (foreach (self::$_registry ako $val) ( if ($val === $item) ( throw new Exception("Položka už existuje"); ) ) self::$_registry[$key] = $item; ) )

    « Vzor čistého registra"vyvoláva ďalší problém - zvýšenie závislosti kvôli potrebe pristupovať k setterovi a getterovi cez názov triedy. Nemôžete vytvoriť odkaz na objekt a pracovať s ním, ako to bolo v prípade vzoru Singleton, keď bol tento prístup k dispozícii:

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

    Tu máme možnosť uložiť referenciu na inštanciu Singleton napríklad do vlastnosti aktuálnej triedy a pracovať s ňou tak, ako to vyžaduje ideológia OOP: odovzdať ju ako parameter agregovaným objektom alebo použiť v potomkoch.

    Na vyriešenie tohto problému existuje Implementácia registra Singleton, ktorý sa mnohým ľuďom nepáči, pretože sa javí ako nadbytočný kód. Myslím si, že dôvodom tohto postoja je určité nepochopenie princípov OOP alebo ich zámerné ignorovanie.

    _registre[$kľúč] = $objekt; ) statická verejná funkcia get($key) ( return self::getInstance()->_registry[$key]; ) súkromná funkcia __wakeup() ( ) súkromná funkcia __construct() ( ) súkromná funkcia __clone() ( ) ) ?>

    Aby som ušetril peniaze, zámerne som vynechal bloky komentárov pre metódy a vlastnosti. Nemyslím si, že sú potrebné.

    Ako som už povedal, zásadný rozdiel je v tom, že teraz je možné uložiť odkaz na zväzok registrov a nepoužívať zakaždým ťažkopádne volania statických metód. Táto možnosť sa mi zdá o niečo správnejšia. Súhlas alebo nesúhlas s mojím názorom nemá veľký význam, rovnako ako môj názor samotný. Žiadne implementačné jemnosti nemôžu odstrániť vzor z množstva spomenutých nevýhod.

    Rozhodla som sa stručne napísať o vzoroch často používaných v našom živote, viac príkladov, menej vody, poďme na to.

    Singleton

    Hlavnou pointou „single“ je, že keď poviete „Potrebujem telefónnu ústredňu“, povedia vám „Už je to tam postavené“, a nie „Poďme to postaviť znova“. „Samotár“ je vždy sám.

    Class Singleton ( private static $instance = null; súkromná funkcia __construct())( /* ... @return Singleton */ ) // Ochrana pred vytvorením pomocou novej súkromnej funkcie Singleton __clone() ( /* ... @return Singleton * / ) // Ochrana pred vytvorením pomocou klonovania súkromnej funkcie __wakeup() ( /* ... @return Singleton */ ) // Ochrana pred vytvorením prostredníctvom zrušenia serializácie verejnej statickej funkcie getInstance() ( if (is_null(self::$instance ) ) ( self::$instance = new self; ) return self::$instance; ) )

    Registratúra (registra, denník záznamov)

    Ako už názov napovedá, tento vzor je určený na ukladanie záznamov, ktoré sú v ňom umiestnené, a podľa toho na vrátenie týchto záznamov (podľa názvu), ak sú potrebné. V príklade telefónnej ústredne ide o register vo vzťahu k telefónnym číslam obyvateľov.

    Register triedy ( súkromný $registre = pole(); sada verejných funkcií ($kľúč, $objekt) ( $tento->registre[$kľúč] = $objekt; ) verejná funkcia get($kľúč) ( návrat $tento->registre [$key]; ) )

    Singletonov register- nezamieňať s)

    „Register“ je často „samotár“, ale nemusí to tak byť vždy. Napríklad v účtovníctve môžeme vytvoriť niekoľko denníkov, v jednom sú zamestnanci od „A“ po „M“, v druhom od „N“ po „Z“. Každý takýto časopis bude „registrom“, ale nie „jednotlivým“, pretože už existujú 2 časopisy.

    Trieda SingletonRegistry ( private static $instance = null; private $registry = array(); súkromná funkcia __construct() ( /* ... @return Singleton */ ) // Ochrana pred vytvorením pomocou novej súkromnej funkcie Singleton __clone() ( / * ... @return Singleton */ ) // Ochrana pred vytvorením prostredníctvom klonovania súkromnej funkcie __wakeup() ( /* ... @return Singleton */ ) // Ochrana pred vytvorením prostredníctvom zrušenia serializácie verejnej statickej funkcie getInstance() ( if ( is_null(self::$instance)) ( self::$instance = new self; ) return self::$instance; ) public function set ($key, $object) ( $this->registry[$key] = $ objekt; ) verejná funkcia get($key) ( return $this->registry[$key]; ) )

    Multiton (skupina „singlov“) alebo inými slovamiRegister Singleton ) - nezamieňajte s registrom Singleton

    Často sa „register“ používa špeciálne na ukladanie „singlov“. Ale pretože vzor „registra“ nie je „generatívny vzor“, ale rád by som o „registri“ uvažoval v súvislosti s „singletonom“.Preto sme vymysleli vzor Multiton, ktorý podľaVo svojom jadre je to „register“ obsahujúci niekoľko „singlov“, z ktorých každý má svoje vlastné „meno“, pod ktorým je možné k nemu pristupovať.

    Krátky: umožňuje vytvárať objekty tejto triedy, ale iba ak objekt pomenujete. Neexistuje žiadny skutočný príklad, ale na internete som našiel nasledujúci príklad:

    Databáza tried ( private static $instances = array(); súkromná funkcia __construct() ( ) súkromná funkcia __clone() ( ) verejná statická funkcia getInstance($key) ( if(!array_key_exists($key, self::$instances)) ( 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: Volanie do súkromnej databázy::__construct() z neplatného kontextu $dbFatalError = new Database(); // PHP Závažná chyba: Volanie do súkromnej databázy::__clone() $dbCloneError = klon $masterDupe;

    Objektový bazén

    V podstate ide o tento vzor „register“, ktorý ukladá iba objekty, žiadne reťazce, polia atď. dátové typy.

    Fabrika

    Podstatu vzoru takmer úplne vystihuje jeho názov. Keď potrebujete získať nejaké predmety, napríklad krabice od džúsov, nemusíte vedieť, ako sa vyrábajú v továrni. Jednoducho poviete: „Dajte mi kartón pomarančového džúsu“ a „továreň“ vám vráti požadovaný balík. Ako? O tom všetkom rozhoduje samotná továreň, napríklad „kopíruje“ už existujúci štandard. Hlavným účelom „továrne“ je v prípade potreby umožniť zmenu procesu „vzhľadu“ balenia džúsu, pričom samotnému spotrebiteľovi o tom netreba nič hovoriť, aby o to mohol požiadať. ako predtým. Jedna továreň sa spravidla zaoberá „výrobou“ iba jedného typu „produktu“. Neodporúča sa vytvárať „továreň na šťavu“ s prihliadnutím na výrobu automobilových pneumatík. Tak ako v živote, vzor továrne často vytvára jediný človek.

    Abstraktná trieda AnimalAbstract ( chránený $druh; verejná funkcia getSpecies() ( return $this->species; ) ) class Cat rozširuje AnimalAbstract ( protected $species = "mačka"; ) class Dog rozširuje AnimalAbstract ( chránený $druh = "pes"; ) trieda AnimalFactory ( verejná továreň statickej funkcie ($animal) ( switch ($animal) ( case "cat": $obj = new Cat(); break; case "pes": $obj = new Dog(); break; default : throw new Exception("Továreň na zvieratá nemohla vytvoriť zviera druhu "" . $zviera . """, 1000); ) return $obj; ) ) $cat = AnimalFactory::factory("cat"); // objekt(Cat)#1 echo $cat->getSpecies(); // mačka $pes = AnimalFactory::factory("pes"); // objekt(Pes)#1 echo $pes->getSpecies(); // pes $hippo = AnimalFactory::factory("hroch"); // Toto vyvolá výnimku

    Chcel by som upriamiť vašu pozornosť na skutočnosť, že továrenská metóda je tiež vzor, ​​nazýva sa to továrenská metóda.

    staviteľ (staviteľ)

    Takže sme už pochopili, že „Factory“ je automat na nápoje, už má všetko pripravené a vy len poviete, čo potrebujete. „Builder“ je závod, ktorý vyrába tieto nápoje a obsahuje všetky zložité operácie a dokáže zostaviť zložité predmety z jednoduchších (obal, etiketa, voda, príchute atď.) v závislosti od požiadavky.

    Class Bottle ( public $name; public $liters; ) /** * všetci stavitelia musia */ rozhranie BottleBuilderInterface (verejná funkcia setName(); verejná funkcia setLiters(); verejná funkcia getResult(); ) class CocaColaBuilder implementuje rozhranie BottleBuilderInterface (privátne $ fľaša; verejná funkcia __construct() ( $this->bottle = new Bottle(); ) verejná funkcia setName($value) (​$this->bottle->name = $value; ) verejná funkcia setLiters($value) ($ táto->fľaša->litre = $hodnota; ) verejná funkcia getResult() ( return $this->fľaša; ) ) $juice = new CocaColaBuilder(); $juice->setName("Coca-Cola Light"); $juice->setLiters(2); $juice->getResult();

    Prototyp

    Pripomína továreň, slúži aj na vytváranie predmetov, no s trochu iným prístupom. Predstavte si seba v bare, pili ste pivo a dochádza vám, hovoríte barmanovi – urobte mi ďalšie rovnaké. Barman sa na oplátku pozrie na pivo, ktoré pijete, a urobí kópiu, ako ste požiadali. PHP už má implementáciu tohto vzoru, nazýva sa to .

    $newJuice = klon $šťava;

    Lenivá inicializácia

    Napríklad šéf vidí zoznam výkazov pre rôzne typy činností a myslí si, že tieto výkazy už existujú, no v skutočnosti sa zobrazujú iba názvy výkazov a samotné výkazy ešte neboli vygenerované a budú sa generovať pri objednávke (napríklad kliknutím na tlačidlo Zobraziť prehľad). Špeciálnym prípadom lenivej inicializácie je vytvorenie objektu v čase, keď sa k nemu pristupuje. Na Wikipédii nájdete jeden zaujímavý, ale... podľa teórie by správnym príkladom v php bola napríklad funkcia

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

    Tento vzor plne zodpovedá svojmu názvu. Aby „sovietska“ zástrčka fungovala cez euro zásuvku, je potrebný adaptér. Presne to robí „adaptér“ – slúži ako medziobjekt medzi dvoma ďalšími, ktoré spolu nemôžu priamo spolupracovať. Napriek definícii v praxi stále vidím rozdiel medzi Adaptérom a Wrapperom.

    Trieda MyClass ( public function methodA() () ) class MyClassWrapper ( public function __construct())( $this->myClass = new MyClass(); ) verejná funkcia __call($name, $arguments)( Log::info(" Chystáte sa zavolať metódu $name."); return call_user_func_array(pole($this->myClass, $name), $arguments); ) ) $obj = new MyClassWrapper(); $obj->methodA();

    Injekcia závislosti

    Injekcia závislostí vám umožňuje presunúť časť zodpovednosti za niektoré funkcie na iné objekty. Napríklad, ak potrebujeme prijať nových zamestnancov, nemôžeme si vytvoriť vlastné HR oddelenie, ale zaviesť závislosť na personálnej spoločnosti, ktorá zase na našu prvú žiadosť „potrebujeme človeka“ bude fungovať buď ako HR oddelenie, alebo si nájde inú spoločnosť (pomocou „lokátora služieb“), ktorá bude tieto služby poskytovať.
    „Injekcia závislosti“ vám umožňuje posúvať a zamieňať jednotlivé časti spoločnosti bez straty celkovej funkčnosti.

    Trieda AppleJuice () // táto metóda je primitívnou implementáciou vzoru vstrekovania Dependency a ďalej uvidíte túto funkciu getBottleJuice())( $obj = new Jablkový džús Jablkový džús)( return $obj; ) ) $bottleJuice = getBottleJuice();

    Teraz si predstavte, že už nechceme jablkový džús, ale pomarančový džús.

    Trieda AppleJuice() Trieda Pomarančový džús() // táto metóda implementuje funkciu vstrekovania závislosti getBottleJuice())( $obj = new Pomarančový džús; // skontrolujte objekt, v prípade, že nám podstrčili pivo (pivo nie je džús) if($obj instanceof Pomarančový džús)( return $obj; ) )

    Ako vidíte, museli sme zmeniť nielen druh šťavy, ale aj kontrolu na typ šťavy, čo nie je príliš pohodlné. Oveľa správnejšie je použiť princíp inverzie závislosti:

    Rozhranie Juice () Trieda AppleJuice implementuje Juice () Trieda OrangeJuice implementuje Juice () funkcia getBottleJuice())( $obj = new OrangeJuice; // skontrolujte objekt, v prípade, že nám podstrčili pivo (pivo nie je džús) if($obj instanceof Šťava)( return $obj; ) )

    Inverzia závislosti sa niekedy zamieňa s injekciou závislosti, ale netreba si ich zamieňať, pretože Inverzia závislosti je princíp, nie vzor.

    Vyhľadávač služieb

    "Service Locator" je metóda implementácie "Dependency Injection". Vracia rôzne typy objektov v závislosti od inicializačného kódu. Nech je úlohou doručiť náš balík džúsov, vytvorený staviteľom, továrňou alebo niečím iným, kamkoľvek si kupujúci želá. Lokátorovi povieme „poskytnite nám doručovaciu službu“ a požiadame službu, aby doručila džús na požadovanú adresu. Dnes je tu jedna služba a zajtra môže byť ďalšia. Nezáleží na tom, o akú konkrétnu službu ide, je dôležité, aby sme vedeli, že táto služba poskytne to, čo jej povieme a kde jej povieme. Na druhej strane služby implementujú „Doručiť<предмет>na<адрес>».

    Ak hovoríme o skutočnom živote, potom pravdepodobne dobrým príkladom lokátora služieb by bolo rozšírenie PDO PHP, pretože Dnes pracujeme s databázou MySQL a zajtra môžeme pracovať s PostgreSQL. Ako ste už pochopili, pre našu triedu nezáleží na tom, do ktorej databázy posiela svoje údaje, dôležité je, že to dokáže.

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

    Rozdiel medzi injekciou závislosti a lokalizátorom služieb

    Ak ste si to ešte nevšimli, rád by som to vysvetlil. Injekcia závislosti Výsledkom je, že nevracia službu (ktorá môže niekde niečo doručiť), ale objekt, ktorého dáta používa.

    Pokúsim sa vám povedať o mojej implementácii vzoru Registry v PHP. Register je OOP náhradou za globálne premenné, ktorá je určená na ukladanie údajov a ich prenos medzi modulmi systému. V súlade s tým je vybavený štandardnými vlastnosťami - zapisovať, čítať, mazať. Tu je typická implementácia.

    Takto získame hlúpu náhradu metód $key = $value - Registry::set($key, $value) $key - Registry::get($key) unset($key) - odstrániť Registry::remove ($key ) Začína byť nejasné – prečo tento dodatočný kód. Naučme teda našu triedu robiť to, čo globálne premenné nedokážu. Pridáme k tomu korenie.

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

    K typickým úlohám vzoru som pridal možnosť blokovať premennú pred zmenami, čo je veľmi výhodné pri veľkých projektoch, nič náhodne nevložíte. Napríklad vhodné na prácu s databázami
    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);

    Teraz pre vysvetlenie kódu, na ukladanie údajov používame statickú premennú $data, premenná $lock ukladá údaje o kľúčoch, ktoré sú uzamknuté pre zmenu. V sieti skontrolujeme, či je premenná uzamknutá a zmeníme alebo pridáme do registra. Pri mazaní kontrolujeme aj zámok, getter zostáva nezmenený, s výnimkou predvoleného voliteľného parametra. Stojí za to venovať pozornosť spracovaniu výnimiek, ktoré sa z nejakého dôvodu zriedka používa. Mimochodom, návrh výnimiek už mám, počkajte na článok. Nižšie je návrh kódu na testovanie, tu je článok o testovaní, nezaškodilo by ho napísať, hoci nie som fanúšikom TDD.

    V ďalšom článku funkčnosť ďalej rozšírime o inicializáciu dát a implementáciu „lenivosti“.