Problema inițializării obiectelor în aplicațiile OOP în PHP. Găsirea unei soluții utilizând Registry, Factory Method, Service Locator și Dependency Injection patterns. Modele OOP cu exemple și descrieri Registro autorizat php

Problema inițializării obiectelor în aplicațiile OOP în PHP. Găsirea unei soluții utilizând Registry, Factory Method, Service Locator și Dependency Injection patterns

Se întâmplă că programatorii consolidează soluții de succes sub formă de modele de design. Există multă literatură despre tipare. Cartea lui Gang of Four „Design Patterns” de Erich Gamma, Richard Helm, Ralph Johnson și John Vlissides” și, poate, „Patterns of Enterprise Application Architecture” de Martin Fowler sunt cu siguranță considerate clasice. Cel mai bun lucru pe care l-am citit cu exemple în PHP - asta. Se întâmplă că toată această literatură este destul de complexă pentru oamenii care tocmai au început să stăpânească POO. Așa că mi-a venit ideea să prezint câteva dintre modelele pe care le consider cele mai utile într-o formă foarte simplificată. În alte cuvinte, acest articol este prima mea încercare de a interpreta modele de design în stilul KISS.
Astăzi vom vorbi despre ce probleme pot apărea cu inițializarea obiectelor într-o aplicație OOP și despre cum puteți utiliza unele modele de design populare pentru a rezolva aceste probleme.

Exemplu

O aplicație OOP modernă funcționează cu zeci, sute și uneori mii de obiecte. Ei bine, să aruncăm o privire mai atentă la modul în care aceste obiecte sunt inițializate în aplicațiile noastre. Inițializarea obiectului este singurul aspect care ne interesează în acest articol, așa că am decis să omit toată implementarea „extra”.
Să presupunem că am creat o clasă utilă super-duper care poate trimite o solicitare GET la un anumit URI și poate returna HTML din răspunsul serverului. Pentru ca clasa noastră să nu pară prea simplă, lăsați-o să verifice și rezultatul și să arunce o excepție dacă serverul răspunde „incorect”.

Class Grabber (funcția publică get($url) (/** returnează cod HTML sau aruncă o excepție */) )

Să creăm o altă clasă ale cărei obiecte vor fi responsabile pentru filtrarea HTML-ului primit. Metoda de filtru ia cod HTML și un selector CSS ca argumente și returnează o serie de elemente găsite pentru selectorul dat.

Clasa HtmlExtractor (filtru de funcție publică($html, $selector) (/** returnează o matrice de elemente filtrate */) )

Acum, imaginați-vă că trebuie să obținem rezultate de căutare pe Google pentru anumite cuvinte cheie. Pentru a face acest lucru, vom introduce o altă clasă care va folosi clasa Grabber pentru a trimite o cerere și clasa HtmlExtractor pentru a extrage conținutul necesar. De asemenea, va conține logica pentru construirea URI-ului, un selector pentru filtrarea HTML-ului primit și procesarea rezultatelor obținute.

Clasa GoogleFinder ( private $grabber; private $filter; public function __construct() ( $this->grabber = new Grabber(); $this->filter = new HtmlExtractor(); ) public function find($searchString) ( /* * returnează o serie de rezultate fondate */))

Ați observat că inițializarea obiectelor Grabber și HtmlExtractor este în constructorul clasei GoogleFinder? Să ne gândim cât de bună este această decizie.
Desigur, codificarea creației de obiecte într-un constructor nu este o idee bună. Si de aceea. În primul rând, nu vom putea suprascrie cu ușurință clasa Grabber în mediul de testare pentru a evita trimiterea unei cereri reale. Pentru a fi corect, merită să spunem că acest lucru se poate face folosind API-ul Reflection. Acestea. posibilitatea tehnică există, dar aceasta este departe de a fi cea mai convenabilă și evidentă cale.
În al doilea rând, aceeași problemă va apărea dacă dorim să reutilizam logica GoogleFinder cu alte implementări Grabber și HtmlExtractor. Crearea dependențelor este codificată în constructorul clasei. Și în cel mai bun caz, vom putea moșteni GoogleFinder și să-i înlocuim constructorul. Și chiar și atunci, numai dacă domeniul de aplicare al proprietăților de captare și filtru este protejat sau public.
Un ultim punct, de fiecare dată când creăm un nou obiect GoogleFinder, o nouă pereche de obiecte dependență va fi creată în memorie, deși putem folosi destul de ușor un obiect Grabber și un obiect HtmlExtractor în mai multe obiecte GoogleFinder.
Cred că înțelegeți deja că inițializarea dependenței trebuie mutată în afara clasei. Putem cere ca dependențele deja pregătite să fie transmise constructorului clasei GoogleFinder.

Clasa GoogleFinder ( private $grabber; private $filter; public function __construct(Grabber $grabber, HtmlExtractor $filter) ( $this->grabber = $grabber; $this->filter = $filter; ) public function find($searchString) ( /** returnează o matrice de rezultate fondate */) )

Dacă dorim să oferim altor dezvoltatori posibilitatea de a adăuga și de a folosi propriile lor implementări Grabber și HtmlExtractor, atunci ar trebui să luăm în considerare introducerea de interfețe pentru ei. În acest caz, acest lucru este nu numai util, ci și necesar. Cred că dacă folosim o singură implementare într-un proiect și nu ne așteptăm să creăm altele noi în viitor, atunci ar trebui să refuzăm să creăm o interfață. Este mai bine să acționați în funcție de situație și să faceți o refactorizare simplă atunci când există o nevoie reală de ea.
Acum avem toate clasele necesare și putem folosi clasa GoogleFinder în controler.

Controller de clasă ( public function action() ( /* Unele lucruri */ $finder = nou GoogleFinder(nou Grabber(), nou HtmlExtractor()); $rezultate = $finder->

Să rezumam rezultatele intermediare. Am scris foarte puțin cod și, la prima vedere, nu am greșit cu nimic. Dar... ce se întâmplă dacă trebuie să folosim un obiect precum GoogleFinder în alt loc? Va trebui să duplicăm crearea acestuia. În exemplul nostru, aceasta este doar o linie și problema nu este atât de vizibilă. În practică, inițializarea obiectelor poate fi destul de complexă și poate dura până la 10 linii, sau chiar mai mult. Apar și alte probleme tipice dublării codului. Dacă în timpul procesului de refactorizare trebuie să schimbați numele clasei utilizate sau logica de inițializare a obiectului, va trebui să schimbați manual toate locurile. Cred ca stii cum se intampla :)
De obicei, hardcode-ul este tratat simplu. Valorile duplicate sunt de obicei incluse în configurație. Acest lucru vă permite să schimbați valorile la nivel central în toate locurile în care sunt utilizate.

Șablon de registru.

Deci, am decis să mutăm crearea de obiecte în configurație. Hai să facem asta.

$registry = nou ArrayObject(); $registry["grabber"] = nou Grabber(); $registry["filtru"] = nou HtmlExtractor(); $registry["google_finder"] = nou GoogleFinder($registry["grabber"], $registry["filter"]);
Tot ce trebuie să facem este să transmitem ArrayObject-ului nostru controlerului și problema este rezolvată.

Controller de clasă ( private $registry; public function __construct(ArrayObject $registry) ( $this->registry = $registry; ) public function action() ( /* Unele lucruri */ $rezults = $this->registry["google_finder" ]->find("șir de căutare"); /* Faceți ceva cu rezultate */ ) )

Putem dezvolta în continuare ideea de Registry. Moșteniți ArrayObject, încapsulați crearea de obiecte în interiorul unei clase noi, interziceți adăugarea de noi obiecte după inițializare etc. Dar, în opinia mea, codul dat explică pe deplin care este șablonul Registry. Acest tipar nu este generativ, dar merge într-un fel pentru a ne rezolva problemele. Registry este doar un container în care putem stoca obiecte și le putem transmite în cadrul aplicației. Pentru ca obiectele să devină disponibile, trebuie mai întâi să le creăm și să le înregistrăm în acest container. Să ne uităm la avantajele și dezavantajele acestei abordări.
La prima vedere, ne-am atins scopul. Am oprit codificarea numelor de clase și am creat obiecte într-un singur loc. Cream obiecte intr-o singura copie, ceea ce garanteaza reutilizarea lor. Dacă se modifică logica pentru crearea obiectelor, atunci va trebui editat un singur loc din aplicație. Ca bonus, am primit posibilitatea de a gestiona central obiectele din Registry. Putem obține cu ușurință o listă cu toate obiectele disponibile și putem efectua unele manipulări cu acestea. Să ne uităm acum la ce s-ar putea să nu ne placă la acest șablon.
În primul rând, trebuie să creăm obiectul înainte de a-l înregistra în Registry. În consecință, există o mare probabilitate de a crea „obiecte inutile”, adică. cele care vor fi create în memorie, dar nu vor fi folosite în aplicație. Da, putem adăuga obiecte în Registry în mod dinamic, de exemplu. creați numai acele obiecte care sunt necesare pentru a procesa o anumită cerere. Într-un fel sau altul, va trebui să controlăm acest lucru manual. În consecință, în timp, va deveni foarte dificil de întreținut.
În al doilea rând, avem o nouă dependență de controler. Da, putem primi obiecte printr-o metodă statică în Registry, astfel încât să nu fie nevoie să transmitem Registry constructorului. Dar, după părerea mea, nu ar trebui să faci asta. Metodele statice sunt o conexiune și mai strânsă decât crearea de dependențe în interiorul unui obiect și dificultățile de testare (pe acest subiect).
În al treilea rând, interfața controlerului nu ne spune nimic despre obiectele pe care le folosește. Putem obține orice obiect disponibil în Registrul din controler. Ne va fi dificil să spunem ce obiecte folosește controlerul până când nu îi verificăm tot codul sursă.

Metoda fabricii

Cea mai mare nemulțumire a noastră cu Registry este că un obiect trebuie inițializat înainte de a putea fi accesat. În loc să inițializam un obiect în configurație, putem separa logica pentru crearea obiectelor într-o altă clasă, căreia îi putem „cere” să construim obiectul de care avem nevoie. Clasele care sunt responsabile pentru crearea obiectelor se numesc fabrici. Iar modelul de design se numește Metoda fabricii. Să ne uităm la un exemplu de fabrică.

Class Factory (funcția publică getGoogleFinder() ( returnează GoogleFinder nou($this->getGrabber(), $this->getHtmlExtractor()); ) funcție privată getGrabber() ( returnează nou Grabber(); ) funcție privată getHtmlExtractor() ( returnează HtmlFiletr nou(); ))

De regulă, se fac fabrici care sunt responsabile pentru crearea unui tip de obiect. Uneori, o fabrică poate crea un grup de obiecte înrudite. Putem folosi memorarea în cache într-o proprietate pentru a evita recrearea obiectelor.

Class Factory ( privat $finder; funcție publică getGoogleFinder() ( dacă (null === $this->finder) ( $this->finder = nou GoogleFinder($this->getGrabber(), $this->getHtmlExtractor() ); ) returnează $this->finder; ) )

Putem parametriza o metodă din fabrică și putem delega inițializarea altor fabrici în funcție de parametrul primit. Acesta va fi deja un șablon Abstract Factory.
Dacă trebuie să modularizăm aplicația, putem solicita fiecărui modul să furnizeze propriile fabrici. Putem dezvolta în continuare tema fabricilor, dar cred că esența acestui șablon este clară. Să vedem cum vom folosi fabrica în controler.

Controller de clasă ( private $factory; public function __construct(Factory $factory) ( $this->factory = $factory; ) public function action() ( /* Unele lucruri */ $rezultate = $this->factory->getGoogleFinder( )->find("șir de căutare"); /* Faceți ceva cu rezultate */ ) )

Avantajele acestei abordări includ simplitatea ei. Obiectele noastre sunt create în mod explicit, iar IDE-ul dumneavoastră vă va conduce cu ușurință în locul în care se întâmplă acest lucru. Am rezolvat, de asemenea, problema Registrului, astfel încât obiectele din memorie să fie create doar atunci când „cerem” fabricii să facă acest lucru. Dar încă nu am decis cum să furnizăm fabricile necesare controlorilor. Există mai multe opțiuni aici. Puteți folosi metode statice. Putem lăsa controlorii să creeze ei înșiși fabricile necesare și să anuleze toate încercările noastre de a scăpa de copy-paste. Puteți crea o fabrică de fabrici și puteți transmite doar asta controlorului. Dar introducerea obiectelor în controler va deveni puțin mai complicată și va trebui să gestionați dependențele dintre fabrici. În plus, nu este complet clar ce să facem dacă dorim să folosim module în aplicația noastră, cum să înregistrăm fabricile de module, cum să gestionăm conexiunile între fabrici de la diferite module. În general, am pierdut principalul avantaj al fabricii - crearea explicită de obiecte. Și încă nu am rezolvat problema interfeței controlerului „implicite”.

Localizator de servicii

Șablonul Service Locator vă permite să rezolvați lipsa de fragmentare a fabricilor și să gestionați crearea de obiecte automat și centralizat. Dacă ne gândim bine, putem introduce un strat suplimentar de abstractizare care va fi responsabil cu crearea obiectelor în aplicația noastră și gestionarea relațiilor dintre aceste obiecte. Pentru ca acest strat să poată crea obiecte pentru noi, va trebui să îi dăm cunoștințele despre cum să facă acest lucru.
Termenii modelului de localizare a serviciilor:
  • Serviciul este un obiect gata făcut care poate fi obținut dintr-un container.
  • Definiție serviciu – logica de inițializare a serviciului.
  • Un container (Container de servicii) este un obiect central care stochează toate descrierile și poate crea servicii pe baza acestora.
Orice modul își poate înregistra descrierile de servicii. Pentru a obține ceva service de la container, va trebui să îl solicităm prin cheie. Există multe opțiuni pentru implementarea Service Locator; în cea mai simplă versiune, putem folosi ArrayObject ca container și o închidere ca descriere a serviciilor.

Clasa ServiceContainer extinde ArrayObject (funcția publică get($key) ( if (is_callable($this[$key])) ( return call_user_func($this[$key]); ) throw new \RuntimeException("Nu se poate găsi definiția serviciului sub tasta [ $key ]"); ) )

Apoi, înregistrarea Definițiilor va arăta astfel:

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

Și utilizarea în controler este așa:

Controller de clasă ( privat $container; funcția publică __construct(ServiceContainer $container) ( $this->container = $container; ) public function action() ( /* Unele lucruri */ $results = $this->container->get( "google_finder")->find("șir de căutare"); /* Faceți ceva cu rezultate */ ) )

Un container de servicii poate fi foarte simplu sau poate fi foarte complex. De exemplu, Symfony Service Container oferă o mulțime de caracteristici: parametri, domenii ale serviciilor, căutarea serviciilor după etichete, aliasuri, servicii private, posibilitatea de a face modificări containerului după adăugarea tuturor serviciilor (pase de compilare) și multe altele. DIExtraBundle extinde și mai mult capacitățile implementării standard.
Dar să revenim la exemplul nostru. După cum puteți vedea, Service Locator nu numai că rezolvă aceleași probleme ca și șabloanele anterioare, dar facilitează și utilizarea modulelor cu propriile definiții de servicii.
În plus, la nivel de cadru am primit un nivel suplimentar de abstractizare. Și anume, schimbând metoda ServiceContainer::get putem, de exemplu, înlocui obiectul cu un proxy. Iar domeniul de aplicare al obiectelor proxy este limitat doar de imaginația dezvoltatorului. Aici puteți implementa paradigma AOP, LazyLoading etc.
Dar majoritatea dezvoltatorilor încă consideră Service Locator un anti-model. Pentru că, în teorie, putem avea cât mai multe așa-zise Clasele Container Aware (adică clase care conțin o referință la container). De exemplu, controlerul nostru, în interiorul căruia putem obține orice serviciu.
Să vedem de ce este rău.
În primul rând, testează din nou. În loc să creați false doar pentru clasele folosite în teste, va trebui să bateți joc de întregul container sau să utilizați un container real. Prima varianta nu ti se potriveste, pentru ca... trebuie să scrii mult cod inutil în teste, în al doilea rând, pentru că contravine principiilor testării unitare și poate duce la costuri suplimentare pentru întreținerea testelor.
În al doilea rând, ne va fi dificil să refactorăm. Schimbând orice serviciu (sau ServiceDefinition) din container, vom fi forțați să verificăm și toate serviciile dependente. Și această problemă nu poate fi rezolvată cu ajutorul unui IDE. Găsirea unor astfel de locuri pe parcursul aplicației nu va fi atât de ușoară. Pe lângă serviciile dependente, va trebui să verificați și toate locurile de unde se obține serviciul refactorizat din container.
Ei bine, al treilea motiv este că tragerea necontrolată a serviciilor din container va duce mai devreme sau mai târziu la o mizerie în cod și o confuzie inutilă. Acest lucru este greu de explicat, va trebui doar să petreceți din ce în ce mai mult timp pentru a înțelege cum funcționează acest sau acel serviciu, cu alte cuvinte, puteți înțelege pe deplin ce face sau cum funcționează o clasă doar citind întregul său cod sursă.

Injecție de dependență

Ce altceva puteți face pentru a limita utilizarea unui container într-o aplicație? Puteți transfera controlul creării tuturor obiectelor utilizator, inclusiv controlerelor, către cadru. Cu alte cuvinte, codul utilizatorului nu ar trebui să apeleze metoda get a containerului. În exemplul nostru, putem adăuga o definiție pentru controler la container:

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

Și scăpați de containerul din controler:

Controller de clasă ( private $finder; public function __construct(GoogleFinder $finder) ( $this->finder = $finder; ) public function action() ( /* Unele lucruri */ $rezultate = $this->finder->find( "șir de căutare"); /* Faceți ceva cu rezultate */ ))

Această abordare (atunci când accesul la containerul de servicii nu este oferit claselor de clienți) se numește Injecție de dependență. Dar acest șablon are și avantaje și dezavantaje. Atâta timp cât respectăm principiul responsabilității unice, codul arată foarte frumos. În primul rând, am scăpat de containerul din clasele client, făcându-le codul mult mai clar și mai simplu. Putem testa cu ușurință controlerul prin înlocuirea dependențelor necesare. Putem crea și testa fiecare clasă independent de celelalte (inclusiv clase de controler) folosind o abordare TDD sau BDD. Când creăm teste, putem abstra de la container și mai târziu să adăugăm o Definiție atunci când trebuie să folosim instanțe specifice. Toate acestea vor face codul nostru mai simplu și mai clar, iar testarea mai transparentă.
Dar este necesar să menționăm cealaltă față a monedei. Cert este că controlerele sunt clase foarte specifice. Să începem cu faptul că controlorul, de regulă, conține un set de acțiuni, ceea ce înseamnă că încalcă principiul răspunderii unice. Ca rezultat, clasa controlerului poate avea mult mai multe dependențe decât sunt necesare pentru a executa o anumită acțiune. Utilizarea inițializării leneșe (obiectul este instanțiat în momentul primei utilizări și înainte de aceasta este utilizat un proxy ușor) rezolvă într-o oarecare măsură problema de performanță. Dar din punct de vedere arhitectural, crearea multor dependențe de un controler nu este, de asemenea, complet corectă. În plus, testarea controlerelor este de obicei o operațiune inutilă. Totul, desigur, depinde de modul în care este organizată testarea în aplicația dvs. și de cum vă simțiți în legătură cu aceasta.
Din paragraful anterior, ați realizat că utilizarea Dependency Injection nu elimină complet problemele arhitecturale. Prin urmare, gândiți-vă cum vă va fi mai convenabil, dacă stocați sau nu un link către container în controlere. Nu există o singură soluție corectă aici. Cred că ambele abordări sunt bune atâta timp cât codul controlerului rămâne simplu. Dar, cu siguranță, nu ar trebui să creați servicii Conatiner Aware pe lângă controlere.

concluzii

Ei bine, a venit momentul să rezumam tot ce s-a spus. Si s-au spus multe... :)
Deci, pentru a structura munca de creare a obiectelor, putem folosi următoarele modele:
  • Registru: Șablonul are dezavantaje evidente, dintre care cel mai de bază este necesitatea de a crea obiecte înainte de a le pune într-un container comun. Evident, vom avea mai multe probleme decât beneficii în urma utilizării lui. În mod clar, aceasta nu este cea mai bună utilizare a șablonului.
  • Metoda fabricii: Principalul avantaj al modelului: obiectele sunt create explicit. Principalul dezavantaj: controlorii fie trebuie să-și facă griji cu privire la crearea ei înșiși fabrici, ceea ce nu rezolvă în totalitate problema codificarea hard a numelor de clasă, fie cadrul trebuie să fie responsabil pentru furnizarea controlorilor cu toate fabricile necesare, ceea ce nu va fi atât de evident. Nu există posibilitatea de a gestiona central procesul de creare a obiectelor.
  • Localizator de servicii: O modalitate mai avansată de a controla crearea obiectelor. Un nivel suplimentar de abstractizare poate fi folosit pentru a automatiza sarcinile comune întâlnite la crearea obiectelor. De exemplu:
    clasa ServiceContainer extinde ArrayObject (funcția publică get($key) ( if (is_callable($this[$key])) ( $obj = call_user_func($this[$key]); if ($obj instanceof RequestAwareInterface) ( $obj- >setRequest($this->get("request")); ) return $obj; ) throw new \RuntimeException("Nu se poate găsi definiția serviciului sub cheia [ $key ]"); ) )
    Dezavantajul Service Locator este că API-ul public al claselor încetează să mai fie informativ. Este necesar să citiți întregul cod al clasei pentru a înțelege ce servicii sunt utilizate în ea. O clasă care conține o referință la un container este mai dificil de testat.
  • Injecție de dependență: În esență, putem folosi același container de servicii ca și pentru modelul anterior. Diferența este modul în care este utilizat acest recipient. Dacă evităm să facem clasele dependente de container, vom obține un API de clasă clar și explicit.
Nu este tot ce aș vrea să vă spun despre problema creării de obiecte în aplicațiile PHP. Există și modelul Prototype, nu am luat în considerare utilizarea API-ului Reflection, am lăsat deoparte problema încărcării leneșe a serviciilor și multe alte nuanțe. Articolul s-a dovedit a fi destul de lung, așa că îl voi încheia :)
Am vrut să arăt că injecția de dependență și alte modele nu sunt atât de complicate pe cât se crede în mod obișnuit.
Dacă vorbim despre Dependency Injection, atunci există implementări KISS ale acestui model, de exemplu

Atingerea structurii viitoarei baze de date. S-a început și nu ne putem retrage și nici măcar nu mă gândesc la asta.

Vom reveni la baza de date puțin mai târziu, dar deocamdată vom începe să scriem codul pentru motorul nostru. Dar mai întâi, puțin hardware. ÎNCEPE.

Începutul timpului

În momentul de față, avem doar câteva idei și înțelegere a funcționării sistemului pe care dorim să-l implementăm, dar nu există încă nicio implementare în sine. Nu avem cu ce să lucrăm: nu avem nicio funcționalitate - și, după cum vă amintiți, am împărțit-o în 2 părți: internă și externă. Alfabetul necesită litere, dar funcționalitatea externă necesită funcționalitate internă - de aici vom începe.

Dar nu atât de repede. Pentru ca acesta să funcționeze, trebuie să mergi puțin mai adânc. Sistemul nostru reprezintă o ierarhie, iar orice sistem ierarhic are un început: un punct de montare în Linux, un disc local în Windows, un sistem al unui stat, o companie, o instituție de învățământ etc. Fiecare element al unui astfel de sistem este subordonat cuiva și poate avea mai mulți subordonați, iar pentru a se adresa vecinilor săi și subordonaților acestora se folosește de superiori sau de începutul însuși. Un bun exemplu de sistem ierarhic este arborele genealogic: se alege un punct de plecare - un strămoș - și plecăm. În sistemul nostru, avem nevoie și de un punct de plecare din care vom crește ramuri - module, plugin-uri etc. Avem nevoie de un fel de interfață prin care toate modulele noastre vor „comunica”. Pentru a lucra în continuare, trebuie să ne familiarizăm cu conceptul „ model de design" și câteva dintre implementările lor.

Modele de design

Există o mulțime de articole despre ce este și ce soiuri există; subiectul este destul de complicat și nu vă voi spune nimic nou. Pe Wiki-ul meu preferat există informații despre acest subiect: o căruță cu tobogan și puțin mai mult.

Modelele de design sunt adesea numite modele de design sau pur și simplu modele (din cuvântul englezesc model, tradus însemnând „model”). În continuare în articole, când vorbesc despre modele, mă voi referi la modele de design.

Din lista uriașă cu tot felul de nume de modele înfricoșătoare (și nu atât de înfricoșătoare), până acum ne interesează doar două: registry și singleton.

Registru (sau inregistreaza-te) este un model care operează pe o anumită matrice în care puteți adăuga și elimina un anumit set de obiecte și puteți obține acces la oricare dintre ele și la capacitățile sale.

Singuratic (sau singleton) este un model care asigură că poate exista o singură instanță a unei clase. Nu poate fi copiat, pus în somn sau trezit (vorbind despre magia PHP: __clone(), __sleep(), __wakeup()). Singleton are un punct de acces global.

Definițiile nu sunt complete sau generalizate, dar acest lucru este suficient pentru înțelegere. Oricum nu avem nevoie de ele separat. Suntem interesați de capacitățile fiecăruia dintre aceste modele, dar într-o singură clasă: se numește un astfel de model singleton registry sau Singleton Registry.

Ce ne va oferi asta?
  • Ne va garanta că avem o singură instanță a registrului, în care putem adăuga obiecte în orice moment și să le folosim de oriunde în cod;
  • va fi imposibil să îl copiați și să folosiți altă magie nedorită (în acest caz) a limbajului PHP.

În această etapă, este suficient să înțelegem că un singur registru ne va permite să implementăm o structură modulară a sistemului, ceea ce ne-am dorit atunci când discutăm obiectivele din , iar restul îl veți înțelege pe măsură ce dezvoltarea progresează.

Ei bine, destule cuvinte, hai să creăm!

Primele rânduri

Deoarece această clasă se va referi la funcționalitatea nucleului, vom începe prin a crea un folder în rădăcina proiectului nostru numit core în care vom plasa toate clasele de module de kernel. Începem cu registry, așa că să numim fișierul registry.php

Nu ne interesează posibilitatea ca un utilizator curios să introducă o adresă directă a fișierului nostru în linia browserului, așa că trebuie să ne protejăm de acest lucru. Pentru a atinge acest obiectiv, trebuie doar să definim o anumită constantă în fișierul executabil principal, pe care o vom verifica. Ideea nu este nouă; din câte îmi amintesc, a fost folosită în Joomla. Aceasta este o metodă simplă și de lucru, așa că ne putem lipsi de biciclete aici.

Deoarece protejăm ceva care este conectat, vom numi constanta _PLUGSECURE_ :

If (!defined("_PLUGSECURE_")) ( die("Apelul direct al modulului este interzis!"); )

Acum, dacă încercați să accesați acest fișier direct, nu va ieși nimic util, ceea ce înseamnă că scopul a fost atins.

În continuare, îmi propun să stipulăm un anumit standard pentru toate modulele noastre. Vreau să ofer fiecărui modul o funcție care va returna câteva informații despre acesta, cum ar fi numele modulului, iar această funcție trebuie să fie necesară în clasă. Pentru atingerea acestui obiectiv scriem următoarele:

Interfață StorableObject (funcția publică statică getClassName(); )

Ca aceasta. Acum, dacă conectăm orice clasă fără o funcție getClassName() vom vedea un mesaj de eroare. Nu mă voi concentra pe asta deocamdată, ne va fi util mai târziu, cel puțin pentru testare și depanare.

Este timpul pentru clasa registrului nostru de single. Vom începe prin a declara clasa și unele dintre variabilele acesteia:

Class Registry implementează StorableObject ( //numele modulului lizibil privat static $className = "Registry"; //instanță registry private static $instance; //matrice de obiecte private static $obiecte = array();

Până acum totul este logic și de înțeles. Acum, după cum vă amintiți, avem un registru cu proprietăți singleton, așa că să scriem imediat o funcție care ne va permite să lucrăm cu registrul în acest fel:

Funcție publică statică singleton() ( if(!isset(self::$instanță)) ( $obj = __CLASS__; self::$instance = new $obj; ) return self::$instance; )

Literal: funcția verifică dacă există o instanță a registrului nostru: dacă nu, o creează și o returnează; dacă există deja, pur și simplu o returnează. În acest caz, nu avem nevoie de ceva magie, așa că pentru protecție îl vom declara privat:

Funcție privată __construct()() funcție privată __clone()() funcție privată __wakeup()() funcție privată __sleep() ()

Acum avem nevoie de o funcție pentru a adăuga un obiect în registry - această funcție se numește setter și am decis să o implementez în două moduri pentru a arăta cum putem folosi magia și a oferi o modalitate alternativă de a adăuga un obiect. Prima metodă este o funcție standard, a doua o execută pe prima prin magia lui __set().

//$obiect - calea către obiectul conectat //$cheie - cheie de acces la obiectul din registrul funcția publică addObject($key, $object) ( require_once($object); //creați un obiect într-o matrice de obiecte self::$obiecte[ $key] = new $key(self::$instanță); ) //o metodă alternativă prin funcția publică magică __set($key, $object) ( $this->addObject($key, $ obiect); )

Acum, pentru a adăuga un obiect în registry, putem folosi două tipuri de intrări (să spunem că am creat deja o instanță de registry $registry și dorim să adăugăm fișierul config.php):

$registry->addObject("config", "/core/config.php"); //metoda obișnuită $registry->config = "/core/config.php"; //prin funcția magică PHP __set()

Ambele intrări vor îndeplini aceeași funcție - vor conecta fișierul, vor crea o instanță a clasei și o vor plasa în registru cu cheia. Există un punct important aici, nu trebuie să uităm de el în viitor: cheia obiectului din registru trebuie să se potrivească cu numele clasei din obiectul conectat. Dacă te uiți din nou la cod, vei înțelege de ce.

Ce înregistrare să utilizați depinde de dvs. Prefer să înregistrez prin metoda magică - este „mai frumoasă” și mai scurtă.

Deci, am rezolvat adăugarea unui obiect, acum avem nevoie de o funcție pentru accesarea unui obiect conectat prin tastă - un getter. L-am implementat și cu două funcții, similare cu setter-ul:

//obține obiectul din registru //$key - cheia din funcția publică matrice getObject($key) ( //verifică dacă variabila este un obiect dacă (is_object(self::$objects[$key])) ( //dacă da, atunci returnăm acest obiect return self::$objects[$key]; ) ) //o metodă similară prin funcția publică magică __get($key) ( if (is_object(self::$obiecte[$) cheie])) ( returnează sine: :$obiecte[$cheie]; ) )

Ca și în cazul setterului, pentru a avea acces la obiect vom avea 2 intrări echivalente:

$registry->getObject("config"); //metoda obișnuită $registry->config; //prin funcția magică PHP __get()

Cititorul atent va pune imediat întrebarea: de ce în funcția magică __set() apelez doar o funcție obișnuită (non-magică) de adăugare a obiectelor, dar în getterul __get() copiez codul funcției getObject() în loc de același apel? Sincer, nu pot răspunde suficient de precis la această întrebare, voi spune doar că am avut probleme când am lucrat cu magia __get() în alte module, dar când am rescris codul „direct” nu există astfel de probleme.

Poate de aceea am văzut adesea în articole reproșuri față de metodele magice PHP și sfaturi pentru a evita utilizarea lor.

„Toată magia vine cu un preț”. © Rumplestiltskin

În această etapă, funcționalitatea principală a registrului nostru este deja gata: putem crea o singură instanță a registrului, putem adăuga obiecte și le accesăm atât folosind metode convenționale, cât și prin metodele magice ale limbajului PHP. „Ce zici de ștergere?”— nu vom avea nevoie de această funcție deocamdată și nu sunt sigur că ceva se va schimba în viitor. În final, putem adăuga întotdeauna funcționalitatea necesară. Dar dacă acum încercăm să creăm o instanță a registrului nostru,

$registry = Registry::singleton();

vom primi o eroare:

Eroare fatala: Class Registry conține 1 metodă abstractă și, prin urmare, trebuie să fie declarată abstractă sau să implementeze metodele rămase (StorableObject::getClassName) în ...

Totul pentru că am uitat să scriem o funcție necesară. Vă amintiți că la început am vorbit despre o funcție care returnează numele modulului? Acesta este ceea ce rămâne de adăugat pentru funcționalitatea completă. E simplu:

Funcția publică statică getClassName() ( returnează self::$className; )

Acum nu ar trebui să existe erori. Vă propun să adăugați încă o funcție, nu este necesară, dar mai devreme sau mai târziu poate fi utilă; o vom folosi în viitor pentru verificare și depanare. Funcția va returna numele tuturor obiectelor (modulelor) adăugate în registrul nostru:

Funcția publică getObjectsList() ( //matricea pe care o vom returna $names = array(); //obține numele fiecărui obiect din tabloul de obiecte foreach(self::$objects as $obj) ( $names = $ obj->getClassName() ; ) //adăugați numele modulului de registru la matricea array_push($names, self::getClassName()); //și returnează $names; )

Asta e tot. Aceasta completează registrul. Să-i verificăm munca? Când verificăm, va trebui să conectăm ceva - să existe un fișier de configurare. Creați un nou fișier core/config.php și adăugați conținutul minim pe care îl necesită registrul nostru:

//nu uitați să verificați constanta if (!defined("_PLUGSECURE_")) ( die("Apelul direct la modul este interzis!"); ) class Config ( //nume modul, static privat lizibil $className = "Config "; funcția publică statică getClassName() ( returnează self::$className; ) )

Ceva de genul. Acum să trecem la verificarea în sine. În rădăcina proiectului nostru, creați un fișier index.php și scrieți următorul cod în el:

Define("_PLUGSECURE_", adevărat); //a definit o constantă pentru a proteja împotriva accesului direct la obiecte require_once "/core/registry.php"; //a conectat registrul $registry = Registry::singleton(); //creat o instanță singleton de registru $registry->config = "/core/config.php"; //conectați configurația noastră, până acum inutilă //afișează numele modulelor conectate echo " Conectat"; foreach ($registry->

  • " . $nume ."
  • "; }

    Sau, dacă tot eviți magia, atunci a 5-a linie poate fi înlocuită cu o metodă alternativă:

    Define("_PLUGSECURE_", adevărat); //a definit o constantă pentru a proteja împotriva accesului direct la obiecte require_once "/core/registry.php"; //a conectat registrul $registry = Registry::singleton(); //creat o instanță singleton de registru $registry->addObject("config", "/core/config.php"); //conectați configurația noastră, până acum inutilă //afișează numele modulelor conectate echo " Conectat"; foreach ($registry->getObjectsList() ca $nume) ( echo "

  • " . $nume ."
  • "; }

    Acum deschideți browserul și scrieți http://localhost/index.php sau pur și simplu http://localhost/ în bara de adrese (relevant dacă utilizați Open Server standard sau setări similare de server web)

    Ca rezultat, ar trebui să vedem ceva de genul acesta:

    După cum puteți vedea, nu există erori, ceea ce înseamnă că totul funcționează, pentru care vă felicit :)

    Astăzi ne vom opri la asta. În articolul următor, vom reveni la baza de date și vom scrie o clasă pentru lucrul cu MySQL SUDB, o vom conecta la registru și vom testa munca în practică. Te văd!

    Acest model, ca și Singleton, provoacă rareori o reacție pozitivă din partea dezvoltatorilor, deoarece dă naștere la aceleași probleme la testarea aplicațiilor. Cu toate acestea, ei certa, dar folosesc activ. Ca și Singleton, modelul Registry se găsește în multe aplicații și, într-un fel sau altul, simplifică foarte mult rezolvarea anumitor probleme.

    Să luăm în considerare ambele opțiuni în ordine.

    Ceea ce se numește „registru pur” sau pur și simplu Registry este o implementare a unei clase cu o interfață statică. Principala diferență față de modelul Singleton este că blochează capacitatea de a crea cel puțin o instanță a unei clase. Având în vedere acest lucru, nu are rost să ascunzi metodele magice __clone() și __wakeup() în spatele modificatorului privat sau protejat.

    Clasa de registru trebuie să aibă două metode statice - un getter și un setter. Setter-ul plasează obiectul transmis în depozit cu o legătură la cheia dată. Prin urmare, getterul returnează un obiect din magazin. Un magazin nu este altceva decât o matrice cheie-valoare asociativă.

    Pentru un control complet asupra registrului, este introdus un alt element de interfață - o metodă care vă permite să ștergeți un obiect din stocare.

    Pe lângă problemele identice cu modelul Singleton, mai există două:

    • introducerea unui alt tip de dependență - pe cheile de registry;
    • două chei de registry diferite pot avea o referință la același obiect

    În primul caz, este imposibil să se evite dependența suplimentară. Într-o oarecare măsură, devenim atașați de nume cheie.

    A doua problemă este rezolvată prin introducerea unei verificări în metoda Registry::set():

    Set de funcții statice publice ($key, $item) ( dacă (!array_key_exists($key, self::$_registry)) ( foreach (self::$_registry ca $val) ( dacă ($val === $item) ( throw new Exception("Elementul există deja"); ) ) self::$_registry[$key] = $item; ) )

    « Curățați modelul de registru„Dă naștere unei alte probleme – creșterea dependenței datorită necesității de a accesa setter-ul și getter-ul prin numele clasei. Nu puteți crea o referință la un obiect și lucra cu el, așa cum a fost cazul modelului Singleton, când această abordare era disponibilă:

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

    Aici avem posibilitatea de a salva o referință la o instanță Singleton, de exemplu, într-o proprietate a clasei curente și de a lucra cu ea așa cum este cerut de ideologia OOP: treceți-l ca parametru la obiectele agregate sau folosiți-l în descendenți.

    Pentru a rezolva această problemă există Implementarea Singleton Registry, care multor oameni nu le place pentru că pare un cod redundant. Cred că motivul acestei atitudini este o neînțelegere a principiilor OOP sau o nerespectare deliberată a acestora.

    _registry[$cheie] = $obiect; ) funcție publică statică get($key) ( return self::getInstance()->_registry[$key]; ) funcție privată __wakeup() ( ) funcție privată __construct() ( ) funcție privată __clone() ( ) ) ?>

    Pentru a economisi bani, am omis în mod deliberat blocurile de comentarii pentru metode și proprietăți. Nu cred că sunt necesare.

    După cum am spus deja, diferența fundamentală este că acum este posibil să salvați o referință la volumul de registry și să nu folosiți apeluri greoaie la metode statice de fiecare dată. Această opțiune mi se pare ceva mai corectă. A fi de acord sau a nu fi de acord cu opinia mea nu contează prea mult, la fel ca și opinia mea în sine. Nicio subtilitate de implementare nu poate elimina modelul dintr-un număr dintre dezavantajele menționate.

    Am decis să scriu pe scurt despre tiparele folosite des în viața noastră, mai multe exemple, mai puțină apă, hai să mergem.

    Singleton

    Principalul punct al „single” este că atunci când spui „Am nevoie de o centrală telefonică”, ei ți-ar spune „A fost deja construit acolo”, și nu „Hai să-l construim din nou”. Un „singurat” este întotdeauna singur.

    Clasa Singleton ( private static $instance = null; funcție privată __construct())( /* ... @return Singleton */ ) // Protejează împotriva creării prin noua funcție privată Singleton __clone() ( /* ... @return Singleton * / ) // Protejați împotriva creării prin clonarea funcției private __wakeup() ( /* ... @return Singleton */ ) // Protejați împotriva creării prin deserializarea funcției publice statice getInstance() ( if (is_null(self::$instance) ) ) ( self::$instance = self new; ) return self::$instance; ) )

    Registrul (registru, jurnal de înregistrări)

    După cum sugerează și numele, acest model este conceput pentru a stoca înregistrările care sunt plasate în el și, în consecință, să returneze aceste înregistrări (după nume) dacă sunt necesare. În exemplul unei centrale telefonice, este un registru în raport cu numerele de telefon ale rezidenților.

    Registrul clasei ( private $registry = array(); public function set($cheie, $obiect) ( $this->registry[$key] = $object; ) public function get($key) ( return $this->registry) [$key]; ) )

    Registrul Singleton- nu confunda cu)

    „Registrul” este adesea un „singurat”, dar nu trebuie să fie întotdeauna așa. De exemplu, putem crea mai multe jurnale în departamentul de contabilitate, într-un angajați de la „A” la „M”, în celălalt de la „N” la „Z”. Fiecare astfel de jurnal va fi un „registru”, dar nu un „unic”, deoarece există deja 2 reviste.

    Clasa SingletonRegistry ( private static $instance = null; private $registry = array(); funcție privată __construct() ( /* ... @return Singleton */ ) // Protejează împotriva creării prin noua funcție privată Singleton __clone() ( / * ... @return Singleton */ ) // Protejează împotriva creării prin clonarea funcției private __wakeup() ( /* ... @return Singleton */ ) // Protejează împotriva creării prin deserializarea funcției publice statice getInstance() ( dacă ( is_null(self::$instanță)) ( self::$instanță = sine nou; ) return self::$instanță; ) set de funcții publice ($cheie, $obiect) ( $this->registry[$key] = $ obiect; ) funcția publică get($key) ( returnează $this->registry[$key]; ) )

    Multiton (grup de „single”) sau cu alte cuvinteRegistrul Singleton ) - nu confundați cu Singleton Registry

    Adesea, „registrul” este folosit special pentru a stoca „single”. Dar, pentru că modelul „registry” nu este un „model generativ”, dar aș dori să iau în considerare „registrul” în legătură cu „singleton”.De aceea am venit cu un model Multiton, care conformÎn esență, este un „registru” care conține mai multe „single”, fiecare având propriul „nume” prin care poate fi accesat.

    Mic de statura: vă permite să creați obiecte din această clasă, dar numai dacă denumiți obiectul. Nu există un exemplu din viața reală, dar am găsit următorul exemplu pe Internet:

    Baza de date de clasă ( private static $instances = array(); funcție privată __construct() ( ) private function __clone() ( ) public static function getInstance($key) ( if(!array_key_exists($key, self::$instances)) ( self::$instances[$key] = new self(); ) return self::$instances[$key]; ) ) $master = Database::getInstance("master"); var_dump($master); // obiect(Bază de date)#1 (0) ( ) $logger = Database::getInstance("logger"); var_dump($logger); // obiect(Bază de date)#2 (0) ( ) $masterDupe = Baza de date::getInstance("master"); var_dump($masterDupe); // obiect(Bază de date)#1 (0) ( ) // Eroare fatală: Apel la baza de date privată::__construct() din context nevalid $dbFatalError = new Database(); // Eroare fatală PHP: Apel la baza de date privată::__clone() $dbCloneError = clona $masterDupe;

    Bazin de obiecte

    În esență, acest model este un „registru” care stochează numai obiecte, fără șiruri de caractere, matrice etc. tipuri de date.

    Fabrică

    Esența modelului este aproape complet descrisă de numele său. Când trebuie să obțineți unele obiecte, cum ar fi cutii cu suc, nu trebuie să știți cum sunt făcute într-o fabrică. Pur și simplu spui „da-mi o cutie de suc de portocale”, iar „fabrica” îți returnează pachetul necesar. Cum? Toate acestea sunt decise de fabrica însăși, de exemplu, „copiază” un standard deja existent. Scopul principal al „fabricii” este de a face posibilă, dacă este necesar, schimbarea procesului de „apariție” a unui pachet de suc, iar consumatorului însuși nu trebuie să i se spună nimic despre acest lucru, pentru a putea solicita acest lucru. Ca înainte. De regulă, o fabrică este angajată în „producția” unui singur tip de „produs”. Nu este recomandat să se creeze o „fabrică de sucuri” ținând cont de producția de anvelope auto. Ca și în viață, modelul fabricii este adesea creat de o singură persoană.

    Abstract class AnimalAbstract ( protected $specie; public function getSpecies() ( return $this->species; ) ) class Cat extinde AnimalAbstract ( protected $specie = "pisica"; ) clasa Dog extinde AnimalAbstract ( protected $specie = "câine"; ) clasa AnimalFactory (fabrica de funcții statice publice($animal) ( comutator ($animal) (caz „pisica”: $obj = new Cat(); break; case „dog”: $obj = new Dog(); break; implicit : throw new Exception("Fabrica de animale nu a putut crea animale din specia "" . $animal . """, 1000); ) return $obj; ) ) $cat = AnimalFactory::factory("pisica"); // obiect(Cat)#1 echo $cat->getSpecies(); // pisica $caine = AnimalFactory::factory("caine"); // obiect(Câine)#1 echo $câine->getSpecies(); // câine $hippo = AnimalFactory::factory("hipopotam"); // Aceasta va genera o excepție

    Aș dori să vă atrag atenția asupra faptului că metoda fabrică este și un model; se numește metoda Factory.

    constructor (constructor)

    Așadar, am înțeles deja că „Factory” este un automat de băuturi, are deja totul pregătit și tu doar spui ce ai nevoie. „Builder” este o fabrică care produce aceste băuturi și conține toate operațiunile complexe și poate asambla obiecte complexe din altele mai simple (ambalaj, etichetă, apă, arome etc.) în funcție de cerere.

    Clasa Bottle ( public $nume; public $litri; ) /** * toți constructorii trebuie să */ interfață BottleBuilderInterface ( funcția publică setName(); funcția publică setLiters(); funcția publică getResult(); ) clasa CocaColaBuilder implementează BottleBuilderInterface ( private $ sticlă; funcția publică __construct() ( $this->bottle = new Bottle(); ) public function setName($value) ( ​​​​$this->botttle->name = $value; ) funcția public setLiters($valoare) ( ​​$ this->bottle->liters = $value; ) public function getResult() ( return $this->bottle; ) ) $juice = new CocaColaBuilder(); $juice->setName("Coca-Cola Light"); $suc->setLitri(2); $suc->getResult();

    Prototip

    Semănând cu o fabrică, servește și la crearea de obiecte, dar cu o abordare puțin diferită. Imaginați-vă într-un bar, beai bere și rămâi fără ea, îi spui barmanului - fă-mi încă unul de același fel. Barmanul se uită la rândul său la berea pe care o bei și face o copie așa cum ai cerut-o. PHP are deja o implementare a acestui model, se numește .

    $newJuice = clona $suc;

    Inițializare leneșă

    De exemplu, un șef vede o listă de rapoarte pentru diferite tipuri de activități și crede că aceste rapoarte există deja, dar de fapt sunt afișate doar numele rapoartelor, iar rapoartele în sine nu au fost încă generate și vor fi doar generate. la comandă (de exemplu, făcând clic pe butonul Vizualizare raport). Un caz special de inițializare leneșă este crearea unui obiect în momentul în care este accesat. Puteți găsi unul interesant pe Wikipedia, dar... conform teoriei, exemplul corect în php ar fi, de exemplu, o funcție

    Adaptor sau Wrapper (adaptor, wrapper)

    Acest model corespunde pe deplin numelui său. Pentru ca o priză „sovietică” să funcționeze printr-o priză euro, este necesar un adaptor. Exact asta face un „adaptor” - servește ca obiect intermediar între alți doi care nu pot lucra direct unul cu celălalt. În ciuda definiției, în practică încă văd diferența dintre Adapter și Wrapper.

    Clasa MyClass (metoda funcției publiceA() () ) clasa MyClassWrapper (funcția publică __construct())( $this->myClass = new MyClass(); ) funcție publică __call($nume, $argumente)( Log::info(" Sunteți pe cale să apelați metoda $name."); return call_user_func_array(array($this->myClass, $name), $arguments); ) ) $obj = new MyClassWrapper(); $obj->methodA();

    Injecție de dependență

    Injecția de dependență vă permite să transferați o parte din responsabilitatea pentru anumite funcționalități către alte obiecte. De exemplu, dacă trebuie să angajăm personal nou, atunci nu ne putem crea propriul departament de resurse umane, ci să introducem dependența de o companie de recrutare, care, la rândul său, la prima noastră cerere „avem nevoie de o persoană”, fie va funcționa ca un Departamentul de resurse umane propriu-zis sau va găsi o altă companie (folosind un „service locator”) care va furniza aceste servicii.
    „Injectarea dependenței” vă permite să mutați și să schimbați părți individuale ale companiei fără a pierde funcționalitatea generală.

    Clasa AppleJuice () // această metodă este o implementare primitivă a modelului de injectare Dependency și mai departe veți vedea această funcție getBottleJuice())( $obj = new Suc de mere Suc de mere)( return $obj; ) ) $bottleJuice = getBottleJuice();

    Acum imaginați-vă că nu mai vrem suc de mere, vrem suc de portocale.

    Clasa AppleJuice() Clasa Suc de portocale() // această metodă implementează funcția de injectare a dependenței getBottleJuice())( $obj = new Suc de portocale; // verifică obiectul, în cazul în care ne-au strecurat bere (bere nu este suc) if($obj instanceof Suc de portocale)( returnează $obj; ) )

    După cum puteți vedea, a trebuit să schimbăm nu numai tipul de suc, ci și verificarea pentru tipul de suc, ceea ce nu este foarte convenabil. Este mult mai corect să folosiți principiul inversării dependenței:

    Interfață Juice () Class AppleJuice implementează Juice () Class OrangeJuice implementează Juice () funcția getBottleJuice())( $obj = nou OrangeJuice; // verifică obiectul, în cazul în care ne-au strecurat bere (bere nu este suc) if($obj instanță de Suc)( returnează $obj; ) )

    Inversarea dependenței este uneori confundată cu injectarea dependenței, dar nu este nevoie să le confundați, deoarece Inversarea dependenței este un principiu, nu un model.

    Localizator de servicii

    „Service Locator” este o metodă de implementare a „Dependency Injection”. Returnează diferite tipuri de obiecte în funcție de codul de inițializare. Sarcina să fie de a livra pachetul nostru de suc, creat de un constructor, fabrică sau altceva, oriunde dorește cumpărătorul. Îi spunem locatorului „dați-ne un serviciu de livrare” și cerem serviciului să livreze sucul la adresa dorită. Astăzi există un serviciu, iar mâine poate fi altul. Nu contează pentru noi ce serviciu specific este, este important pentru noi să știm că acest serviciu va oferi ceea ce îi spunem și unde îl spunem. La rândul lor, serviciile implementează programul „Livrare<предмет>pe<адрес>».

    Dacă vorbim despre viața reală, atunci probabil un bun exemplu de Localizare de servicii ar fi extensia PHP PDO, deoarece Astăzi lucrăm cu o bază de date MySQL, iar mâine putem lucra cu PostgreSQL. După cum ați înțeles deja, nu contează pentru clasa noastră la ce bază de date își trimite datele, este important să o poată face.

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

    Diferența dintre injecția de dependență și Localizator de servicii

    Dacă nu ați observat încă, aș dori să vă explic. Injecție de dependență Ca urmare, returnează nu un serviciu (care poate livra ceva undeva), ci un obiect ale cărui date le folosește.

    Voi încerca să vă spun despre implementarea mea a modelului Registry în PHP. Registry este un înlocuitor OOP pentru variabilele globale, conceput pentru a stoca date și a le transfera între modulele de sistem. În consecință, este dotat cu proprietăți standard - scrieți, citiți, ștergeți. Iată o implementare tipică.

    Ei bine, în acest fel obținem o înlocuire stupidă a metodelor $key = $value - Registry::set($key, $value) $key - Registry::get($key) unset($key) - remove Registry::remove ($key ) Pur și simplu devine neclar - de ce acest cod suplimentar. Deci, să învățăm clasa noastră să facă ceea ce variabilele globale nu pot face. Să adăugăm piper.

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

    La sarcinile tipice ale modelului, am adăugat capacitatea de a bloca o variabilă de la modificări, acest lucru este foarte convenabil pentru proiectele mari, nu veți introduce nimic accidental. De exemplu, convenabil pentru lucrul cu baze de date
    define('DB_DNS', 'mysql:host=localhost;dbname= ’);
    define('DB_USER', ' ’);
    define('DB_PASSWORD', ' ’);
    define('DB_HANDLE');

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

    Acum, pentru o explicație a codului, pentru a stoca date, folosim variabila statică $data, variabila $lock stochează date despre cheile care sunt blocate pentru modificare. În rețea, verificăm dacă variabila este blocată și o modificăm sau o adăugăm în registru. La ștergere, verificăm și blocarea; getter-ul rămâne neschimbat, cu excepția parametrului opțional implicit. Ei bine, merită să acordați atenție gestionării excepțiilor, care din anumite motive este rar folosită. Apropo, am deja o schiță despre excepții, așteptați articolul. Mai jos este o schiță de cod pentru testare, iată un articol despre testare, nici n-ar strica să-l scriu, deși nu sunt fan TDD.

    În articolul următor vom extinde și mai mult funcționalitatea adăugând inițializarea datelor și implementând „lenea”.