Problem inicijalizacije objekata u OOP aplikacijama u PHP-u. Pronalaženje rješenja pomoću obrazaca Registry, Factory Method, Service Locator i Dependency Injection. OOP obrasci sa primjerima i opisima Autoritativni registro php

Problem inicijalizacije objekata u OOP aplikacijama u PHP-u. Pronalaženje rješenja pomoću obrazaca Registry, Factory Method, Service Locator i Dependency Injection

Desilo se da programeri konsoliduju uspješna rješenja u obliku dizajnerskih obrazaca. Postoji mnogo literature o obrascima. Knjiga Gang of Four "Design Patterns" Ericha Gamme, Richarda Helma, Ralpha Johnsona i Johna Vlissidesa" i, možda, "Patterns of Enterprise Application Architecture" Martina Fowlera svakako se smatraju klasicima. Najbolja stvar koju sam pročitao s primjerima u PHP-u - ovo. Desilo se da je sva ova literatura prilično složena za ljude koji su tek počeli da savladavaju OOP. Pa sam došao na ideju da neke od šablona koje smatram najkorisnijima predstavim u znatno pojednostavljenom obliku. riječima, ovaj članak je moj prvi pokušaj interpretacije dizajnerskih obrazaca u stilu KISS.
Danas ćemo razgovarati o tome koji problemi mogu nastati s inicijalizacijom objekta u OOP aplikaciji i kako možete koristiti neke popularne obrasce dizajna za rješavanje ovih problema.

Primjer

Moderna OOP aplikacija radi sa desetinama, stotinama, a ponekad i hiljadama objekata. Pa, pogledajmo bliže kako se ovi objekti inicijaliziraju u našim aplikacijama. Inicijalizacija objekta je jedini aspekt koji nas zanima u ovom članku, pa sam odlučio da izostavim sve „dodatne“ implementacije.
Recimo da smo kreirali super-duper korisnu klasu koja može poslati GET zahtjev na određeni URI i vratiti HTML iz odgovora servera. Kako naša klasa ne bi izgledala previše jednostavno, neka također provjeri rezultat i izbaci izuzetak ako server odgovori "netačno".

Class Grabber ( javna funkcija get($url) (/** vraća HTML kod ili izbacuje izuzetak */))

Kreirajmo drugu klasu čiji će objekti biti odgovorni za filtriranje primljenog HTML-a. Metoda filtera uzima HTML kod i CSS selektor kao argumente i vraća niz elemenata pronađenih za dati selektor.

Klasa HtmlExtractor (filter javnih funkcija ($html, $selector) (/** vraća niz filtriranih elemenata */))

Sada zamislite da trebamo dobiti rezultate pretraživanja na Googleu za date ključne riječi. Da bismo to učinili, uvest ćemo još jednu klasu koja će koristiti klasu Grabber za slanje zahtjeva i klasu HtmlExtractor za izdvajanje potrebnog sadržaja. Takođe će sadržati logiku za konstruisanje URI-ja, selektor za filtriranje primljenog HTML-a i obradu dobijenih rezultata.

Klasa GoogleFinder (privatni $grabber; privatni $filter; javna funkcija __construct() ( $this->grabber = novi Grabber(); $this->filter = novi HtmlExtractor(); ) javna funkcija find($searchString) ( /* * vraća niz osnovanih rezultata */) )

Jeste li primijetili da je inicijalizacija Grabber i HtmlExtractor objekata u konstruktoru klase GoogleFinder? Hajde da razmislimo koliko je ova odluka dobra.
Naravno, tvrdo kodiranje kreiranja objekata u konstruktoru nije dobra ideja. I zato. Prvo, nećemo moći lako nadjačati Grabber klasu u testnom okruženju kako bismo izbjegli slanje stvarnog zahtjeva. Da budemo pošteni, vrijedi reći da se to može učiniti pomoću Reflection API-ja. One. tehnička mogućnost postoji, ali ovo je daleko od najprikladnijeg i najočiglednijeg načina.
Drugo, isti problem će se pojaviti ako želimo ponovo koristiti GoogleFinder logiku s drugim Grabber i HtmlExtractor implementacijama. Kreiranje zavisnosti je tvrdo kodirano u konstruktoru klase. A u najboljem slučaju, moći ćemo naslijediti GoogleFinder i nadjačati njegov konstruktor. Pa čak i tada, samo ako je opseg svojstava grabbera i filtera zaštićen ili javan.
Još jedna posljednja točka, svaki put kada kreiramo novi GoogleFinder objekat, novi par objekata zavisnosti će biti kreiran u memoriji, iako vrlo lako možemo koristiti jedan Grabber objekat i jedan HtmlExtractor objekat u nekoliko GoogleFinder objekata.
Mislim da već razumijete da inicijalizaciju zavisnosti treba premjestiti izvan klase. Možemo zahtijevati da se već pripremljene zavisnosti proslijede konstruktoru klase GoogleFinder.

Klasa GoogleFinder (privatni $grabber; privatni $filter; javna funkcija __construct(Grabber $grabber, HtmlExtractor $filter) ( $this->grabber = $grabber; $this->filter = $filter; ) javna funkcija find($searchString) ( /** vraća niz osnovanih rezultata */) )

Ako želimo dati drugim programerima mogućnost da dodaju i koriste svoje Grabber i HtmlExtractor implementacije, onda bismo trebali razmisliti o uvođenju interfejsa za njih. U ovom slučaju, ovo nije samo korisno, već i neophodno. Vjerujem da ako koristimo samo jednu implementaciju u projektu i ne očekujemo da ćemo u budućnosti kreirati nove, onda bismo trebali odbiti kreiranje interfejsa. Bolje je djelovati u skladu sa situacijom i napraviti jednostavan refaktor kada postoji stvarna potreba za tim.
Sada imamo sve potrebne klase i možemo koristiti klasu GoogleFinder u kontroleru.

Kontroler klase ( javna funkcija action() ( /* Neke stvari */ $finder = novi GoogleFinder(novi Grabber(), novi HtmlExtractor()); $results = $finder->

Hajde da sumiramo međurezultate. Napisali smo vrlo malo koda i na prvi pogled nismo uradili ništa loše. Ali... šta ako trebamo koristiti objekat kao što je GoogleFinder na drugom mjestu? Morat ćemo duplicirati njegovu kreaciju. U našem primjeru, ovo je samo jedan red i problem nije toliko primjetan. U praksi, inicijalizacija objekata može biti prilično složena i može trajati do 10 redova, ili čak i više. Pojavljuju se i drugi problemi tipični za dupliciranje koda. Ako tokom procesa refaktoriranja trebate promijeniti ime klase koja se koristi ili logiku inicijalizacije objekta, morat ćete ručno promijeniti sva mjesta. Mislim da znaš kako se to dešava :)
Obično se tvrdi kod jednostavno rješava. Duplicirane vrijednosti su obično uključene u konfiguraciju. Ovo vam omogućava da centralno mijenjate vrijednosti na svim mjestima gdje se koriste.

Predložak registra.

Stoga smo odlučili da kreiranje objekata premjestimo u konfiguraciju. Uradimo to.

$registry = novi ArrayObject(); $registry["grabber"] = novi Grabber(); $registry["filter"] = novi HtmlExtractor(); $registry["google_finder"] = novi GoogleFinder($registry["grabber"], $registry["filter"]);
Sve što treba da uradimo je da prosledimo naš ArrayObject kontroleru i problem je rešen.

Kontroler klase ( privatni $registry; javna funkcija __construct(ArrayObject $registry) ( $this->registry = $registry; ) javna funkcija action() ( /* Neke stvari */ $results = $this->registry["google_finder" ]->find("string za pretraživanje"); /* Uradite nešto s rezultatima */ ) )

Možemo dalje razvijati ideju registra. Naslijediti ArrayObject, inkapsulirati kreiranje objekata unutar nove klase, zabraniti dodavanje novih objekata nakon inicijalizacije, itd. Ali po mom mišljenju, dati kod u potpunosti pokazuje šta je predložak registra. Ovaj obrazac nije generativan, ali na neki način ide u rješavanje naših problema. Registar je samo kontejner u koji možemo pohraniti objekte i proslijediti ih unutar aplikacije. Da bi objekti postali dostupni, moramo ih prvo kreirati i registrirati u ovom kontejneru. Pogledajmo prednosti i nedostatke ovog pristupa.
Na prvi pogled smo postigli cilj. Zaustavili smo tvrdo kodiranje imena klasa i kreiranje objekata na jednom mjestu. Izrađujemo objekte u jednoj kopiji, što garantuje njihovu ponovnu upotrebu. Ako se promijeni logika kreiranja objekata, potrebno je urediti samo jedno mjesto u aplikaciji. Kao bonus, dobili smo mogućnost centralnog upravljanja objektima u Registru. Lako možemo dobiti listu svih dostupnih objekata i izvršiti neke manipulacije s njima. Pogledajmo sada šta nam se možda ne sviđa kod ovog šablona.
Prvo, moramo kreirati objekat prije nego ga registriramo u Registru. Shodno tome, postoji velika vjerovatnoća stvaranja „nepotrebnih objekata“, tj. oni koji će biti kreirani u memoriji, ali se neće koristiti u aplikaciji. Da, možemo dodavati objekte u registar dinamički, tj. kreirajte samo one objekte koji su potrebni za obradu određenog zahtjeva. Na ovaj ili onaj način, ovo ćemo morati kontrolirati ručno. Shodno tome, vremenom će postati veoma teško održavati.
Drugo, imamo novu ovisnost kontrolera. Da, možemo primiti objekte putem statičke metode u Registry-u tako da ne moramo prosljeđivati ​​Registry konstruktoru. Ali po mom mišljenju, ovo ne bi trebalo da radiš. Statičke metode su još čvršća veza od stvaranja zavisnosti unutar objekta i poteškoća u testiranju (na ovu temu).
Treće, interfejs kontrolera nam ne govori ništa o tome koje objekte koristi. Možemo dobiti bilo koji objekat dostupan u Registry u kontroleru. Biće nam teško reći koje objekte koristi kontroler dok ne provjerimo sav njegov izvorni kod.

Factory Method

Naša najveća zamjerka Registry-u je da se objekt mora inicijalizirati prije nego što mu se može pristupiti. Umjesto inicijalizacije objekta u konfiguraciji, možemo odvojiti logiku za kreiranje objekata u drugu klasu, koju možemo „tražiti“ da napravi objekat koji nam je potreban. Klase koje su odgovorne za kreiranje objekata nazivaju se tvornicama. A obrazac dizajna se zove Factory Method. Pogledajmo primjer fabrike.

Tvornica klasa ( javna funkcija getGoogleFinder() (vrati novi GoogleFinder($this->getGrabber(), $this->getHtmlExtractor()); ) privatna funkcija getGrabber() (vrati new Grabber(); ) privatna funkcija getHtmlExtractor() ( vrati novi HtmlFiletr(); ) )

Po pravilu se prave fabrike koje su odgovorne za stvaranje jedne vrste objekta. Ponekad fabrika može kreirati grupu povezanih objekata. Možemo koristiti keširanje u svojstvu kako bismo izbjegli ponovno kreiranje objekata.

Fabrika klasa (privatni $finder; javna funkcija getGoogleFinder() (if (null === $this->finder) ( $this->finder = novi GoogleFinder($this->getGrabber(), $this->getHtmlExtractor() ); ) vrati $this->finder; ) )

Možemo parametrizovati fabrički metod i delegirati inicijalizaciju drugim fabrikama u zavisnosti od dolaznog parametra. Ovo će već biti predložak Abstract Factory.
Ako trebamo modularizirati aplikaciju, možemo zahtijevati da svaki modul obezbijedi svoje vlastite fabrike. Možemo dalje razvijati temu fabrika, ali mislim da je suština ovog šablona jasna. Da vidimo kako ćemo koristiti fabriku u kontroleru.

Kontroler klase (privatna $fabrika; javna funkcija __construct(Fabrika $fabrika) ( $this->factory = $factory; ) javna funkcija action() ( /* Neke stvari */ $results = $this->factory->getGoogleFinder( )->find("string za pretraživanje"); /* Uradite nešto s rezultatima */ ) )

Prednosti ovog pristupa uključuju njegovu jednostavnost. Naši objekti su kreirani eksplicitno, a vaš IDE će vas lako dovesti do mjesta gdje se to dešava. Također smo riješili problem registra tako da će objekti u memoriji biti kreirani samo kada to "zatražimo" od tvornice. Ali još nismo odlučili kako ćemo kontrolore snabdjeti potrebnim tvornicama. Ovdje postoji nekoliko opcija. Možete koristiti statičke metode. Možemo dozvoliti kontrolerima da sami kreiraju potrebne tvornice i poništiti sve naše pokušaje da se riješimo copy-paste-a. Možete kreirati fabriku fabrika i samo to proslijediti kontroloru. Ali dobivanje objekata u kontroleru će postati malo složenije i morat ćete upravljati ovisnostima između tvornica. Osim toga, nije sasvim jasno što učiniti ako želimo koristiti module u našoj aplikaciji, kako registrirati tvornice modula, kako upravljati vezama između tvornica iz različitih modula. Generalno, izgubili smo glavnu prednost fabrike - eksplicitnu izradu objekata. I još uvijek nismo riješili problem "implicitnog" interfejsa kontrolera.

Service Locator

Predložak Service Locator vam omogućava da riješite nedostatak fragmentacije fabrika i upravljate kreiranjem objekata automatski i centralno. Ako razmislimo o tome, možemo uvesti dodatni sloj apstrakcije koji će biti odgovoran za kreiranje objekata u našoj aplikaciji i upravljanje odnosima između tih objekata. Da bi ovaj sloj mogao da kreira objekte za nas, moraćemo da mu damo znanje kako da to uradi.
Uslovi obrasca lokatora usluge:
  • Servis je gotov objekat koji se može dobiti iz kontejnera.
  • Definicija usluge – logika inicijalizacije usluge.
  • Kontejner (Service Container) je centralni objekat koji pohranjuje sve opise i može kreirati servise na osnovu njih.
Svaki modul može registrovati svoje servisne opise. Da bismo dobili neku uslugu iz kontejnera, morat ćemo je zatražiti ključem. Postoji mnogo opcija za implementaciju Service Locator-a; u najjednostavnijoj verziji, možemo koristiti ArrayObject kao kontejner i zatvaranje kao opis usluga.

Class ServiceContainer proširuje ArrayObject (javna funkcija get($key) (if (is_callable($this[$key])) (vrati call_user_func($this[$key]); ) baca novi \RuntimeException("Ne mogu pronaći definiciju usluge pod ključ [ $key ]"); ) )

Tada će registracija definicija izgledati ovako:

$container = novi ServiceContainer(); $container["grabber"] = funkcija () (vrati novi Grabber(); ); $container["html_filter"] = funkcija () (vrati novi HtmlExtractor(); ); $container["google_finder"] = function() upotreba ($container) (vrati novi GoogleFinder($container->get("grabber"), $container->get("html_filter")); );

A upotreba u kontroleru je ovakva:

Kontroler klase ( privatni $container; javna funkcija __construct(ServiceContainer $container) ( $this->container = $container; ) javna funkcija action() ( /* Neke stvari */ $results = $this->container->get( "google_finder")->find("string za pretraživanje"); /* Uradite nešto s rezultatima */ ) )

Servisni kontejner može biti vrlo jednostavan, ili može biti vrlo složen. Na primjer, Symfony Service Container pruža puno mogućnosti: parametre, opseg usluga, pretraživanje servisa po oznakama, pseudonimima, privatnim servisima, mogućnost izmjene kontejnera nakon dodavanja svih servisa (prolazi kompajlera) i još mnogo toga. DIExtraBundle dalje proširuje mogućnosti standardne implementacije.
No, vratimo se našem primjeru. Kao što možete vidjeti, Service Locator ne samo da rješava sve iste probleme kao i prethodni predlošci, već i olakšava korištenje modula s vlastitim definicijama usluga.
Osim toga, na nivou okvira dobili smo dodatni nivo apstrakcije. Naime, promjenom ServiceContainer::get metode možemo, na primjer, zamijeniti objekat proxyjem. A opseg primjene proxy objekata ograničen je samo maštom programera. Ovdje možete implementirati AOP paradigmu, LazyLoading, itd.
Ali većina programera i dalje smatra Service Locator anti-uzorkom. Jer, u teoriji, možemo imati što više tzv Container Aware klase (tj. klase koje sadrže referencu na kontejner). Na primjer, naš Controller, unutar kojeg možemo dobiti bilo koju uslugu.
Hajde da vidimo zašto je ovo loše.
Prvo, ponovo testiranje. Umjesto da kreirate mockove samo za klase koje se koriste u testovima, morat ćete ismijavati cijeli kontejner ili koristiti pravi kontejner. Prva opcija vam ne odgovara, jer... morate napisati mnogo nepotrebnog koda u testovima, drugo, jer to je u suprotnosti sa principima jediničnog testiranja i može dovesti do dodatnih troškova održavanja testova.
Drugo, biće nam teško refaktorirati. Promjenom bilo koje usluge (ili ServiceDefinition) u kontejneru, bit ćemo prisiljeni provjeriti i sve zavisne usluge. A ovaj problem se ne može riješiti uz pomoć IDE-a. Pronalaženje takvih mjesta u cijeloj aplikaciji neće biti tako lako. Osim zavisnih servisa, morat ćete provjeriti i sva mjesta na kojima se refaktorirana usluga dobija iz kontejnera.
Pa, treći razlog je taj što će nekontrolirano izvlačenje servisa iz kontejnera prije ili kasnije dovesti do nereda u kodu i nepotrebne zabune. Ovo je teško objasniti, samo ćete morati trošiti sve više i više vremena da shvatite kako ova ili ona usluga funkcionira, drugim riječima, možete u potpunosti razumjeti šta radi ili kako klasa radi samo čitajući njen cijeli izvorni kod.

Injekcija zavisnosti

Šta još možete učiniti da ograničite upotrebu kontejnera u aplikaciji? Možete prenijeti kontrolu nad kreiranjem svih korisničkih objekata, uključujući kontrolere, na okvir. Drugim riječima, korisnički kod ne bi trebao pozivati ​​get metodu spremnika. U našem primjeru, možemo dodati definiciju za kontroler u kontejner:

$container["google_finder"] = funkcija() upotreba ($container) (vrati novi kontroler(Grabber $grabber); );

I riješite se kontejnera u kontroleru:

Kontroler klase ( privatni $finder; javna funkcija __construct(GoogleFinder $finder) ( $this->finder = $finder; ) javna funkcija action() ( /* Neke stvari */ $results = $this->finder->find( "string za pretraživanje"); /* Uradite nešto s rezultatima */ ) )

Ovaj pristup (kada pristup kontejneru usluge nije omogućen klijentskim klasama) naziva se ubacivanje zavisnosti. Ali ovaj šablon ima i prednosti i nedostatke. Sve dok se držimo principa jedinstvene odgovornosti, kodeks izgleda jako lijepo. Prije svega, riješili smo se kontejnera u klijentskim klasama, čineći njihov kod mnogo jasnijim i jednostavnijim. Lako možemo testirati kontroler zamjenom potrebnih ovisnosti. Možemo kreirati i testirati svaku klasu nezavisno od drugih (uključujući klase kontrolera) koristeći TDD ili BDD pristup. Kada kreiramo testove, možemo se apstrahovati od kontejnera, a kasnije dodati definiciju kada trebamo koristiti određene instance. Sve ovo će naš kod učiniti jednostavnijim i jasnijim, a testiranje transparentnijim.
No, potrebno je spomenuti i drugu stranu medalje. Činjenica je da su kontrolori vrlo specifične klase. Počnimo s činjenicom da kontrolor, po pravilu, sadrži skup radnji, što znači da krši princip jedinstvene odgovornosti. Kao rezultat toga, klasa kontrolera može imati mnogo više zavisnosti nego što je potrebno za izvršenje određene akcije. Upotreba lijene inicijalizacije (objekat se instancira u vrijeme prve upotrebe, a prije toga se koristi lagani proxy) u određenoj mjeri rješava problem performansi. Ali sa arhitektonske tačke gledišta, stvaranje mnogih zavisnosti od kontrolera takođe nije sasvim ispravno. Osim toga, testiranje kontrolera je obično nepotrebna operacija. Sve, naravno, zavisi od toga kako je testiranje organizovano u vašoj aplikaciji i kako se sami osećate u vezi sa tim.
Iz prethodnog paragrafa ste shvatili da korištenje ubrizgavanja zavisnosti ne eliminiše u potpunosti arhitektonske probleme. Stoga razmislite kako će vam biti zgodnije, hoćete li pohraniti vezu do kontejnera u kontrolere ili ne. Ovdje ne postoji jedno ispravno rješenje. Mislim da su oba pristupa dobra sve dok je kod kontrolera jednostavan. Ali, definitivno, ne biste trebali kreirati Conatiner Aware usluge pored kontrolera.

zaključci

Pa, došlo je vrijeme da sumiramo sve što je rečeno. I dosta je rečeno... :)
Dakle, da strukturiramo rad stvaranja objekata, možemo koristiti sljedeće obrasce:
  • Registry: Šablon ima očigledne nedostatke, od kojih je najosnovniji potreba za kreiranjem objekata prije nego što ih stavite u zajednički kontejner. Očigledno je da ćemo od njegove upotrebe dobiti više problema nego koristi. Ovo očigledno nije najbolja upotreba šablona.
  • Factory Method: Glavna prednost šablona: objekti se kreiraju eksplicitno. Glavni nedostatak: kontrolori ili moraju brinuti o stvaranju samih tvornica, što ne rješava u potpunosti problem tvrdokodiranih imena klasa, ili okvir mora biti odgovoran za obezbjeđivanje kontrolerima svih potrebnih fabrika, što neće biti tako očigledno. Ne postoji mogućnost centralnog upravljanja procesom kreiranja objekata.
  • Service Locator: Napredniji način kontrole kreiranja objekata. Dodatni nivo apstrakcije može se koristiti za automatizaciju uobičajenih zadataka koji se sreću prilikom kreiranja objekata. Na primjer:
    class ServiceContainer proširuje ArrayObject (javna funkcija 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("Ne mogu pronaći definiciju usluge pod ključem [ $key ]"); ) )
    Nedostatak Service Locator-a je što javni API klasa prestaje da bude informativan. Neophodno je pročitati cijeli kod klase da biste razumjeli koji se servisi u njoj koriste. Klasu koja sadrži referencu na kontejner je teže testirati.
  • Injekcija zavisnosti: U suštini možemo koristiti isti servisni kontejner kao i za prethodni obrazac. Razlika je u tome kako se ovaj kontejner koristi. Ako izbjegnemo da klase zavise od kontejnera, dobićemo jasan i eksplicitan API klase.
Ovo nije sve što bih želeo da vam kažem o problemu kreiranja objekata u PHP aplikacijama. Tu je i obrazac Prototype, nismo razmatrali korištenje Reflection API-ja, ostavili smo po strani problem lijenog učitavanja servisa i mnoge druge nijanse. Članak je ispao prilično dugačak, pa ću ga zaključiti :)
Htio sam pokazati da ubrizgavanje zavisnosti i drugi obrasci nisu tako komplikovani kao što se obično vjeruje.
Ako govorimo o ubrizgavanju zavisnosti, onda postoje KISS implementacije ovog uzorka, na primjer

Dodirujući strukturu buduće baze podataka. Početak je napravljen, ne možemo se povući, a ja o tome ni ne razmišljam.

Vratit ćemo se na bazu podataka malo kasnije, ali za sada ćemo početi pisati kod za naš motor. Ali prvo, malo hardvera. Počni.

Početak vremena

Trenutno imamo samo neke ideje i razumijevanje rada sistema koje želimo implementirati, ali same implementacije još nema. Nemamo s čime raditi: nemamo nikakvu funkcionalnost - i, kao što se sjećate, podijelili smo je na 2 dijela: unutrašnji i vanjski. Abeceda zahtijeva slova, ali vanjska funkcionalnost zahtijeva internu funkcionalnost – tu ćemo početi.

Ali ne tako brzo. Da bi to funkcionisalo potrebno je da uđete malo dublje. Naš sistem predstavlja hijerarhiju, a svaki hijerarhijski sistem ima početak: tačku montiranja u Linuxu, lokalni disk u Windows-u, sistem države, kompanije, obrazovne institucije itd. Svaki element takvog sistema je nekome podređen i može imati više podređenih, a za obraćanje svojim susjedima i njihovim podređenima koristi nadređene ili sam početak. Dobar primjer hijerarhijskog sistema je porodično stablo: odabrana je polazna tačka - neki predak - i idemo dalje. U našem sistemu nam je potrebna i polazna tačka iz koje ćemo rasti grane - module, dodatke itd. Treba nam neka vrsta interfejsa preko kojeg će svi naši moduli „komunicirati“. Za dalji rad potrebno je da se upoznamo sa konceptom “ uzorak dizajna" i nekoliko njihovih implementacija.

Design Patterns

Mnogo je članaka o tome šta je to i koje varijante postoje; tema je prilično zeznuta i neću vam reći ništa novo. Na mom omiljenom Wiki-u postoje informacije o ovoj temi: kočija sa toboganom i još malo.

Dizajnerski obrasci se također često nazivaju dizajnerskim uzorcima ili jednostavno uzorcima (od engleske riječi pattern, što prevedeno znači "uzorak"). Dalje u člancima, kada govorim o uzorcima, mislit ću na uzorke dizajna.

Od ogromne liste svih vrsta zastrašujućih (i ne tako strašnih) imena obrazaca, do sada nas zanimaju samo dva: registry i singleton.

Registry (ili se registrirajte) je obrazac koji radi na određenom nizu u koji možete dodati i ukloniti određeni skup objekata i dobiti pristup bilo kojem od njih i njegovim mogućnostima.

Usamljenik (ili singleton) je obrazac koji osigurava da samo jedna instanca klase može postojati. Ne može se kopirati, uspavati ili probuditi (govorimo o PHP magiji: __clone(), __sleep(), __wakeup()). Singleton ima globalnu pristupnu tačku.

Definicije nisu potpune niti generalizirane, ali ovo je dovoljno za razumijevanje. Ionako nam ne trebaju odvojeno. Zanimaju nas mogućnosti svakog od ovih obrazaca, ali u jednoj klasi: takav obrazac se zove singleton registar ili singleton registar.

Šta će nam ovo dati?
  • Garantovano ćemo imati jednu instancu registra, u koju možemo dodati objekte u bilo kom trenutku i koristiti ih sa bilo kog mesta u kodu;
  • biće nemoguće kopirati ga i koristiti drugu neželjenu (u ovom slučaju) magiju PHP jezika.

U ovoj fazi, dovoljno je shvatiti da će nam jedinstveni registar omogućiti implementaciju modularne strukture sistema, što smo željeli kada smo razgovarali o ciljevima u , a ostalo ćete razumjeti kako razvoj bude napredovao.

Pa, dosta riječi, krenimo!

Prve linije

Pošto će se ova klasa odnositi na funkcionalnost kernela, počet ćemo kreiranjem foldera u korijenu našeg projekta pod nazivom core u koji ćemo smjestiti sve klase modula kernela. Počinjemo sa registrom, pa nazovimo datoteku registry.php

Ne zanima nas mogućnost da radoznali korisnik unese direktnu adresu našeg fajla u liniju pretraživača, pa se moramo zaštititi od toga. Da bismo postigli ovaj cilj, samo trebamo definirati određenu konstantu u glavnoj izvršnoj datoteci, koju ćemo provjeriti. Ideja nije nova, koliko se sjećam, korištena je u Joomli. Ovo je jednostavna i funkcionalna metoda, tako da ovdje možemo bez bicikala.

Pošto štitimo nešto što je povezano, konstantu ćemo nazvati _PLUGSECURE_ :

If (!defined("_PLUGSECURE_")) ( die("Direktni poziv modula je zabranjen!"); )

Sada, ako pokušate direktno pristupiti ovoj datoteci, neće izaći ništa korisno, što znači da je cilj postignut.

Zatim predlažem da odredimo određeni standard za sve naše module. Želim da svakom modulu pružim funkciju koja će vratiti neke informacije o njemu, kao što je ime modula, a ova funkcija mora biti obavezna u klasi. Za postizanje ovog cilja pišemo sljedeće:

Interfejs StorableObject (javna statička funkcija getClassName(); )

Volim ovo. Sada, ako povežemo bilo koju klasu bez funkcije getClassName() vidjet ćemo poruku o grešci. Za sada se neću fokusirati na ovo, biće nam korisno kasnije, barem za testiranje i otklanjanje grešaka.

Vrijeme je za sam čas našeg registra samaca. Počećemo tako što ćemo deklarisati klasu i neke od njenih varijabli:

Class Registry implementira StorableObject ( //ime modula čitljivo privatno static $className = "Registry"; // instanca registra privatna static $instance; //niz objekata privatni statički $objects = array();

Za sada je sve logično i razumljivo. Sada, kao što se sjećate, imamo registar sa singleton svojstvima, pa hajde da odmah napišemo funkciju koja će nam omogućiti da radimo s registrom na ovaj način:

Javna statička funkcija singleton() ( if(!isset(self::$instance)) ( $obj = __CLASS__; self::$instance = novi $obj; ) vrati self::$instance; )

Doslovno: funkcija provjerava postoji li instanca našeg registra: ako ne, kreira je i vraća; ako već postoji, jednostavno je vraća. U ovom slučaju nam nije potrebna magija, pa ćemo je radi zaštite proglasiti privatnim:

Privatna funkcija __construct()() privatna funkcija __clone()() privatna funkcija __wakeup()() privatna funkcija __sleep() ()

Sada nam je potrebna funkcija za dodavanje objekta u naš registar - ova funkcija se zove setter i odlučio sam je implementirati na dva načina da pokažem kako možemo koristiti magiju i obezbijediti alternativni način za dodavanje objekta. Prva metoda je standardna funkcija, druga izvršava prvu kroz magiju __set().

//$object - put do povezanog objekta //$key - ključ za pristup objektu u javnoj funkciji registra addObject($key, $object) ( require_once($object); //kreiraj objekt u nizu objekata self::$objects[ $key] = novi $key(self::$instance); ) //alternativna metoda putem magične javne funkcije __set($key, $object) ( $this->addObject($key, $ objekat);)

Sada, da bismo dodali objekat u naš registar, možemo koristiti dvije vrste unosa (recimo da smo već kreirali instancu registra $registry i želimo dodati datoteku config.php):

$registry->addObject("config", "/core/config.php"); //regularni metod $registry->config = "/core/config.php"; //preko PHP magične funkcije __set()

Oba unosa će obavljati istu funkciju - spojit će datoteku, kreirati instancu klase i smjestiti je u registar sa ključem. Ovdje postoji jedna važna stvar koju ne smijemo zaboraviti u budućnosti: ključ objekta u registru mora odgovarati imenu klase u povezanom objektu. Ako ponovo pogledate kod, shvatit ćete zašto.

Na vama je koji snimak ćete koristiti. Više volim snimanje magičnom metodom - "ljepše" je i kraće.

Dakle, riješili smo dodavanje objekta, sada nam je potrebna funkcija za pristup povezanom objektu pomoću ključa - getter. Također sam ga implementirao sa dvije funkcije, slično kao i setter:

//dobijemo objekt iz registra //$key - ključ u javnoj funkciji niza getObject($key) ( //provjeri da li je varijabla objekt if (is_object(self::$objects[$key])) ( //ako je tako, onda vraćamo ovaj objekat return self::$objects[$key]; ) ) //slična metoda kroz magičnu javnu funkciju __get($key) ( if (is_object(self::$objects[$ ključ])) ( vrati self: :$objects[$key]; ) )

Kao i kod postavljača, da bismo dobili pristup objektu imat ćemo 2 ekvivalentna unosa:

$registry->getObject("config"); //redovna metoda $registry->config; //preko PHP magične funkcije __get()

Pažljivi čitalac će odmah postaviti pitanje: zašto u magičnoj funkciji __set() jednostavno pozivam regularnu (nemagična) funkciju za dodavanje objekata, ali u getteru __get() kopiram kod funkcije getObject() umjesto istog poziva? Iskreno, ne mogu dovoljno precizno odgovoriti na ovo pitanje, samo ću reći da sam imao problema pri radu sa magijom __get() u drugim modulima, ali kod ponovnog pisanja koda „head-on“ takvih problema nema.

Možda sam zato često u člancima viđao zamjerke PHP magičnim metodama i savjete da ih izbjegavate.

"Sva magija ima cijenu." © Rumplestiltskin

U ovoj fazi, glavna funkcionalnost našeg registra je već spremna: možemo kreirati jednu instancu registra, dodavati objekte i pristupati im koristeći konvencionalne metode i magične metode PHP jezika. "Šta je sa brisanjem?"— ova funkcija nam za sada neće trebati, a nisam siguran da će se nešto promijeniti u budućnosti. Na kraju, uvijek možemo dodati potrebnu funkcionalnost. Ali ako sada pokušamo kreirati instancu našeg registra,

$registry = Registry::singleton();

dobićemo grešku:

Fatalna greska: Registar klasa sadrži 1 apstraktnu metodu i stoga mora biti proglašen apstraktnim ili implementirati preostale metode (StorableObject::getClassName) u ...

Sve zato što smo zaboravili napisati potrebnu funkciju. Sjećate se da sam na samom početku govorio o funkciji koja vraća ime modula? Ovo je ono što treba dodati za punu funkcionalnost. jednostavno je:

Javna statička funkcija getClassName() (vrati self::$className; )

Sada ne bi trebalo biti grešaka. Predlažem da dodate još jednu funkciju, nije potrebna, ali prije ili kasnije može nam dobro doći; koristit ćemo je u budućnosti za provjeru i otklanjanje grešaka. Funkcija će vratiti imena svih objekata (modula) dodatih u naš registar:

Javna funkcija getObjectsList() ( //niz koji ćemo vratiti $names = array(); //dobiti ime svakog objekta iz niza objekata foreach(self::$objects kao $obj) ( $names = $ obj->getClassName() ; ) //dodajte ime modula registra u niz array_push($names, self::getClassName()); //i vratite $names; )

To je sve. Time je registar završen. Hajde da proverimo njegov rad? Prilikom provjere morat ćemo nešto povezati - neka postoji konfiguracijski fajl. Kreirajte novi core/config.php fajl i dodajte minimalni sadržaj koji naš registar zahtijeva:

//ne zaboravite provjeriti konstantu if (!defined("_PLUGSECURE_")) ( die("Direktni poziv modula je zabranjen!"); ) class Config ( //ime modula, čitljiv privatni statički $className = "Config "; javna statička funkcija getClassName() (vrati self::$className; ) )

Nešto slično tome. Sada idemo na samu verifikaciju. U korijenu našeg projekta kreirajte datoteku index.php i u nju upišite sljedeći kod:

Define("_PLUGSECURE_", istina); //definisana konstanta za zaštitu od direktnog pristupa objektima require_once "/core/registry.php"; //povezan registar $registry = Registry::singleton(); //kreirao pojedinačnu instancu registra $registry->config = "/core/config.php"; //poveži našu, do sada beskorisnu, konfiguraciju //prikaži nazive povezanih modula echo " Povezano"; foreach ($registry->

  • " . $names ."
  • "; }

    Ili, ako i dalje izbjegavate magiju, onda se 5. red može zamijeniti alternativnom metodom:

    Define("_PLUGSECURE_", istina); //definisana konstanta za zaštitu od direktnog pristupa objektima require_once "/core/registry.php"; //povezan registar $registry = Registry::singleton(); //kreirao pojedinačnu instancu registra $registry->addObject("config", "/core/config.php"); //poveži našu, do sada beskorisnu, konfiguraciju //prikaži nazive povezanih modula echo " Povezano"; foreach ($registry->getObjectsList() kao $names) ( echo "

  • " . $names ."
  • "; }

    Sada otvorite pretraživač i upišite http://localhost/index.php ili jednostavno http://localhost/ u adresnu traku (relevantno ako koristite standardni Open Server ili slične postavke web servera)

    Kao rezultat, trebali bismo vidjeti nešto poput ovoga:

    Kao što vidite, nema grešaka, što znači da sve radi, na čemu vam čestitam :)

    Danas ćemo se zaustaviti na ovome. U narednom članku ćemo se vratiti bazi podataka i napisati klasu za rad sa MySQL SUDB, povezati je sa registrom i testirati rad u praksi. Vidimo se!

    Ovaj obrazac, kao i Singleton, rijetko izaziva pozitivnu reakciju programera, jer dovodi do istih problema prilikom testiranja aplikacija. Ipak, oni se grde, ali aktivno koriste. Kao i Singleton, obrazac Registry se nalazi u mnogim aplikacijama i, na ovaj ili onaj način, uvelike pojednostavljuje rješavanje određenih problema.

    Razmotrimo obje opcije po redu.

    Ono što se zove "čisti registar" ili jednostavno Registry je implementacija klase sa statičkim interfejsom. Glavna razlika od obrasca Singleton je u tome što blokira mogućnost kreiranja barem jedne instance klase. S obzirom na ovo, nema smisla skrivati ​​magične metode __clone() i __wakeup() iza privatnog ili zaštićenog modifikatora.

    Klasa registra mora imati dvije statičke metode - getter i setter. Postavljač stavlja proslijeđeni objekt u memoriju sa vezanjem za dati ključ. Getter, u skladu s tim, vraća objekt iz trgovine. Prodavnica nije ništa drugo do asocijativni niz ključ/vrijednost.

    Za potpunu kontrolu nad registrom, uveden je još jedan element interfejsa - metoda koja vam omogućava da izbrišete objekat iz skladišta.

    Osim problema identičnih Singleton obrascu, postoje još dva:

    • uvođenje druge vrste zavisnosti - od ključeva registratora;
    • dva različita ključa registratora mogu imati referencu na isti objekat

    U prvom slučaju nemoguće je izbjeći dodatnu ovisnost. U određenoj mjeri postajemo vezani za ključna imena.

    Drugi problem je riješen uvođenjem provjere u Registry::set() metodu:

    Javni set statičkih funkcija ($key, $item) ( if (!array_key_exists($key, self::$_registry)) ( foreach (self::$_registry kao $val) ( if ($val === $item) ( throw new Exception("Stavka već postoji"); ) ) self::$_registry[$key] = $item; ) )

    « Očistite obrazac registra"proizlazi još jedan problem - povećanje zavisnosti zbog potrebe da se pristupi seteru i getteru preko imena klase. Ne možete kreirati referencu na objekt i raditi s njim, kao što je bio slučaj sa uzorkom Singleton, kada je ovaj pristup bio dostupan:

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

    Ovdje imamo priliku da spremimo referencu na instancu Singleton, na primjer, u svojstvo trenutne klase, i radimo s njom kako to zahtijeva OOP ideologija: proslijediti je kao parametar agregiranim objektima ili ga koristiti u potomcima.

    Za rješavanje ovog problema postoji Implementacija jedinstvenog registra, što se mnogima ne sviđa jer izgleda kao suvišan kod. Mislim da je razlog ovakvog stava neko nerazumijevanje principa OOP-a ili njihovo namjerno zanemarivanje.

    _registry[$key] = $objekat; ) statička javna funkcija get($key) ( vrati self::getInstance()->_registry[$key]; ) privatna funkcija __wakeup() ( ) privatna funkcija __construct() ( ) privatna funkcija __clone() ( ) ) ?>

    Da bih uštedio novac, namjerno sam izostavio blokove komentara za metode i svojstva. Mislim da nisu potrebni.

    Kao što sam već rekao, fundamentalna razlika je u tome što je sada moguće sačuvati referencu na volumen registra i ne koristiti glomazne pozive statičkim metodama svaki put. Ova opcija mi se čini nešto ispravnijom. Slaganje ili neslaganje sa mojim mišljenjem nije bitno, baš kao i samo moje mišljenje. Nikakve suptilnosti implementacije ne mogu eliminirati obrazac iz niza navedenih nedostataka.

    Odlučio sam ukratko napisati o obrascima koji se često koriste u našim životima, više primjera, manje vode, idemo.

    Singleton

    Glavna poenta "singla" je da kada kažete "Treba mi telefonska centrala", oni bi vam rekli "Tamo je već napravljena", a ne "Hajde da je ponovo napravimo". “Usamljenik” je uvijek sam.

    Class Singleton ( private static $instance = null; privatna funkcija __construct())( /* ... @return Singleton */ ) // Zaštita od kreiranja putem nove privatne funkcije Singleton __clone() ( /* ... @return Singleton * / ) // Zaštita od kreiranja putem kloniranja privatne funkcije __wakeup() ( /* ... @return Singleton */ ) // Zaštita od kreiranja putem unserialize javne statičke funkcije getInstance() ( if (is_null(self::$instance ) ) ( self::$instance = novo ja; ) vrati self::$instance; ) )

    Registar (matični registar, dnevnik upisa)

    Kao što ime sugerira, ovaj obrazac je dizajniran za pohranjivanje zapisa koji su smješteni u njega i, shodno tome, vraćanje ovih zapisa (po imenu) ako su potrebni. U primjeru telefonske centrale, to je registar u odnosu na telefonske brojeve stanovnika.

    Class Registry (privatni $registry = array(); javna funkcija set($key, $object) ( $this->registry[$key] = $object; ) javna funkcija get($key) (vrati $this->registry [$key]; ) )

    Singleton Registry- nemojte brkati sa)

    “Registar” je često “usamljenik”, ali ne mora uvijek biti tako. Na primjer, možemo kreirati nekoliko časopisa u računovodstvu, u jednom su zaposleni od “A” do “M”, u drugom od “N” do “Z”. Svaki takav časopis će biti “registrator”, ali ne i “jedan”, jer već postoje 2 časopisa.

    Class SingletonRegistry (privatna static $instance = null; privatni $registry = array(); privatna funkcija __construct() ( /* ... @return Singleton */ ) // Zaštita od kreiranja putem nove privatne funkcije Singleton __clone() ( / * ... @return Singleton */ ) // Zaštita od kreiranja putem kloniranja privatne funkcije __wakeup() ( /* ... @return Singleton */ ) // Zaštita od kreiranja putem unserialize javne statičke funkcije getInstance() ( if ( is_null(self::$instance)) ( self::$instance = new self; ) return self::$instance; ) skup javnih funkcija ($key, $object) ( $this->registry[$key] = $ objekat; ) javna funkcija get($key) (vrati $this->registry[$key]; ) )

    Multiton (skup "singlova") ili drugim riječimaRegistry Singleton ) - nemojte brkati sa Singleton Registry

    Često se „registar“ koristi posebno za pohranjivanje „singlova“. Ali, jer obrazac “registra” nije “generativni obrazac”, ali bih želio da razmotrim “registr” u vezi sa “singleton”.Zato smo smislili obrazac Multiton, što premaU svojoj srži, to je “registrator” koji sadrži nekoliko “singlova”, od kojih svaki ima svoje “ime” pomoću kojeg mu se može pristupiti.

    Kratko: omogućava vam da kreirate objekte ove klase, ali samo ako objektu date naziv. Ne postoji primjer iz stvarnog života, ali sam na internetu našao sljedeći primjer:

    Baza podataka klasa (privatna statička $instances = array(); privatna funkcija __construct() ( ) privatna funkcija __clone() ( ) javna statička funkcija 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); // object(Baza podataka)#1 (0) ( ) $logger = Database::getInstance("logger"); var_dump($logger); // object(Baza podataka)#2 (0) ( ) $masterDupe = Database::getInstance("master"); var_dump($masterDupe); // object(Database)#1 (0) ( ) // Fatalna greška: Poziv privatne baze podataka::__construct() iz nevažećeg konteksta $dbFatalError = new Database(); // PHP Fatalna greška: Poziv privatne baze podataka::__clone() $dbCloneError = klon $masterDupe;

    Objekat bazen

    U suštini ovaj obrazac jeste “registrator” koji pohranjuje samo objekte, bez nizova, nizova, itd. tipovi podataka.

    Fabrika

    Suština uzorka gotovo je u potpunosti opisana njegovim imenom. Kada trebate nabaviti neke predmete, kao što su kutije za sokove, ne morate znati kako se prave u fabrici. Jednostavno kažete „daj mi kutiju soka od narandže“, a „fabrika“ vam vraća traženi paket. Kako? O svemu tome odlučuje sama fabrika, na primer, „kopira“ već postojeći standard. Osnovna svrha "tvornice" je da omogući, ako je potrebno, promjenu procesa "izgleda" pakovanja sokova, a samom potrošaču ne treba ništa reći o tome, kako bi to mogao zatražiti. kao prije. Jedna fabrika se po pravilu bavi „proizvodnjom“ samo jedne vrste „proizvoda“. Ne preporučuje se stvaranje "fabrike sokova" uzimajući u obzir proizvodnju automobilskih guma. Kao iu životu, fabrički obrazac često kreira jedna osoba.

    Apstraktna klasa AnimalAbstract ( zaštićena $species; javna funkcija getSpecies() (vrati $this->species; ) ) klasa Cat proširuje AnimalAbstract ( protected $species = "cat"; ) klasa Dog proširuje AnimalAbstract ( zaštićena $species = "dog"; ) klasa AnimalFactory ( javna statička funkcija factory ($animal) ( switch ($animal) ( case "cat": $obj = new Cat(); break; case "dog": $obj = new Dog(); break; default : throw new Exception("Tvornica životinja nije mogla stvoriti životinju vrste "" . $animal . """, 1000); ) return $obj; ) ) $cat = AnimalFactory::factory("mačka"); // object(Cat)#1 echo $cat->getSpecies(); // mačka $dog = AnimalFactory::factory("dog"); // object(Dog)#1 echo $dog->getSpecies(); // pas $hippo = AnimalFactory::factory("hippopotamus"); // Ovo će izazvati izuzetak

    Želeo bih da vam skrenem pažnju na činjenicu da je fabrička metoda takođe šablon; ona se zove fabrička metoda.

    Graditelj (graditelj)

    Dakle, već smo shvatili da je “Fabrika” automat za piće, već ima sve spremno, a vi samo kažete šta vam treba. “Builder” je pogon koji proizvodi ove napitke i koji sadrži sve složene operacije i može sastaviti složene objekte od jednostavnijih (ambalaža, etiketa, voda, arome, itd.) ovisno o zahtjevu.

    Class Bottle (javno $name; public $liters; ) /** * svi graditelji moraju */ sučelje BottleBuilderInterface (javna funkcija setName(); javna funkcija setLiters(); javna funkcija getResult(); ) klasa CocaColaBuilder implementira BottleBuilderInterface (privatno $ boca; javna funkcija __construct() ( $this->bottle = new Bottle(); ) javna funkcija setName($value) ($this->bottle->name = $value; ) javna funkcija setLiters($value) ($ this->bottle->liters = $value; ) javna funkcija getResult() (vrati $this->bottle; ) ) $juice = new CocaColaBuilder(); $juice->setName("Coca-Cola Light"); $juice->setLiters(2); $juice->getResult();

    Prototip

    Nalik na fabriku, služi i za kreiranje objekata, ali sa malo drugačijim pristupom. Zamislite sebe u baru, pili ste pivo i ponestaje vam ga, kažete šankeru - napravi mi još jedno takvo. Barmen zauzvrat gleda pivo koje pijete i pravi kopiju kako ste tražili. PHP već ima implementaciju ovog obrasca, zove se .

    $newJuice = klon $juice;

    Lazy inicijalizacija

    Na primjer, šef vidi listu izvještaja za različite vrste aktivnosti i misli da ti izvještaji već postoje, ali u stvari se prikazuju samo nazivi izvještaja, a sami izvještaji još nisu generirani, već će se samo generirati po narudžbi (na primjer, klikom na dugme Pogledaj izvještaj). Poseban slučaj lijene inicijalizacije je kreiranje objekta u vrijeme kada mu se pristupa. Zanimljivu možete pronaći na Wikipediji, ali... prema teoriji, ispravan primjer u php-u bi bila, na primjer, funkcija

    Adapter ili omot (adapter, omot)

    Ovaj obrazac u potpunosti odgovara njegovom nazivu. Da bi "sovjetski" utikač radio kroz euro utičnicu, potreban je adapter. To je upravo ono što radi "adapter" - on služi kao posredni objekat između dva druga koja ne mogu direktno raditi jedan s drugim. Uprkos definiciji, u praksi i dalje vidim razliku između Adaptera i Wrappera.

    Class MyClass ( public function methodA() () ) class MyClassWrapper (javna funkcija __construct())( $this->myClass = new MyClass(); ) javna funkcija __call($name, $arguments)( Log::info(" Upravo ćete pozvati metodu $name."); return call_user_func_array(array($this->myClass, $name), $arguments); ) ) $obj = new MyClassWrapper(); $obj->methodA();

    Injekcija zavisnosti

    Injekcija zavisnosti omogućava vam da prebacite deo odgovornosti za neke funkcionalnosti na druge objekte. Na primjer, ako trebamo zaposliti nove kadrove, onda ne možemo kreirati vlastito odjeljenje za ljudske resurse, već uvesti ovisnost o regrutnoj kompaniji, koja će, pak, na naš prvi zahtjev „treba nam osoba“ ili raditi kao HR odjel ili će pronaći drugu kompaniju (koristeći “lokator usluga”) koja će pružati ove usluge.
    “Ubrizgavanje zavisnosti” vam omogućava da pomerate i menjate pojedinačne delove kompanije bez gubitka ukupne funkcionalnosti.

    Klasa AppleJuice () // ova metoda je primitivna implementacija uzorka ubrizgavanja ovisnosti i dalje ćete vidjeti ovu funkciju getBottleJuice())( $obj = new Sok od jabuke Sok od jabuke)( return $obj; ) ) $bottleJuice = getBottleJuice();

    Sada zamislite da više ne želimo sok od jabuke, želimo sok od pomorandže.

    Klasa AppleJuice() Klasa Sok od narandže() // ova metoda implementira funkciju ubrizgavanja ovisnosti getBottleJuice())( $obj = new Sok od narandže; // provjeravamo objekt, u slučaju da su nam ispalili pivo (pivo nije sok) if($obj instanceof Sok od narandže)( return $obj; ) )

    Kao što vidite, morali smo da promenimo ne samo vrstu soka, već i proveru vrste soka, što nije baš zgodno. Mnogo je ispravnije koristiti princip inverzije zavisnosti:

    Interfejs Juice () Klasa AppleJuice implementira Juice () Klasa OrangeJuice implementira Juice () funkciju getBottleJuice())( $obj = new OrangeJuice; // provjerimo objekt, u slučaju da nam je iskliznuo pivo (pivo nije sok) if($obj instanceof Juice)( return $obj; ) )

    Inverzija zavisnosti se ponekad meša sa injekcijom zavisnosti, ali nema potrebe da ih brkate, jer Inverzija zavisnosti je princip, a ne obrazac.

    Service Locator

    "Service Locator" je metoda implementacije "Dependency Injection". Vraća različite tipove objekata ovisno o inicijalizacijskom kodu. Neka zadatak bude isporuka našeg paketa sokova, kreiranog od strane građevinara, fabrike ili nečeg drugog, gde god kupac želi. Lokatoru kažemo “daj nam dostavu” i tražimo od službe da dostavi sok na željenu adresu. Danas postoji jedna služba, a sutra može biti druga. Nije nam bitno koja je to usluga, važno nam je da znamo da će ova usluga isporučiti ono što joj kažemo i gdje joj kažemo. Zauzvrat, usluge implementiraju „Deliver<предмет>on<адрес>».

    Ako govorimo o stvarnom životu, onda bi vjerovatno dobar primjer lokatora usluga bila PDO PHP ekstenzija, jer Danas radimo sa MySQL bazom podataka, a sutra možemo raditi sa PostgreSQL. Kao što ste već shvatili, našoj klasi nije važno u koju bazu podataka šalje svoje podatke, važno je da ona to može.

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

    Razlika između injekcije zavisnosti i lokatora usluge

    Ako još niste primetili, želeo bih da objasnim. Injekcija zavisnosti Kao rezultat, vraća ne uslugu (koja može negdje isporučiti nešto) već objekt čije podatke koristi.

    Pokušaću da vam ispričam o svojoj implementaciji Registry obrasca u PHP-u. Registar je OOP zamjena za globalne varijable, dizajniran za pohranjivanje podataka i prijenos između sistemskih modula. U skladu s tim, on je obdaren standardnim svojstvima - pisanje, čitanje, brisanje. Evo tipične implementacije.

    Pa, na ovaj način dobijamo glupu zamenu metoda $key = $value - Registry::set($key, $value) $key - Registry::get($key) unset($key) - ukloni Registry::remove ($key) Samo postaje nejasno - čemu ovaj dodatni kod. Dakle, hajde da naučimo našu klasu da radi ono što globalne varijable ne mogu. Dodajte biber u to.

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

    Tipičnim zadacima obrasca dodao sam mogućnost blokiranja varijable od promjena, ovo je vrlo zgodno na velikim projektima, nećete slučajno ništa umetnuti. Na primjer, pogodan za rad s bazama podataka
    define('DB_DNS', 'mysql:host=localhost;dbname= ’);
    define('DB_USER', ' ’);
    define('DB_PASSWORD', ' ’);
    define('DB_HANDLE');

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

    Sada za objašnjenje koda, za pohranjivanje podataka koristimo statičku varijablu $data, varijabla $lock pohranjuje podatke o ključevima koji su zaključani za promjenu. U mreži provjeravamo da li je varijabla zaključana i mijenjamo je ili dodajemo u registar. Prilikom brisanja provjeravamo i zaključavanje; getter ostaje nepromijenjen, s izuzetkom zadanog opcionog parametra. Pa, vrijedi obratiti pažnju na rukovanje izuzecima, koje se iz nekog razloga rijetko koristi. Usput, već imam nacrt o izuzecima, pričekajte članak. Ispod je nacrt koda za testiranje, evo članak o testiranju, ne bi škodilo ni da ga napišem, iako nisam fan TDD-a.

    U sljedećem članku ćemo dodatno proširiti funkcionalnost dodavanjem inicijalizacije podataka i implementacijom “lijenosti”.