Problemet med objektinitialisering i OOP-applikasjoner i PHP. Finne en løsning ved å bruke register, fabrikkmetode, servicelokalisering og avhengighetsinjeksjonsmønstre. OOP-mønstre med eksempler og beskrivelser Autoritative registro php

Problemet med objektinitialisering i OOP-applikasjoner i PHP. Finne en løsning ved å bruke register, fabrikkmetode, servicelokalisering og avhengighetsinjeksjonsmønstre

Det hender bare at programmerere konsoliderer vellykkede løsninger i form av designmønstre. Det finnes mye litteratur om mønstre. The Gang of Fours bok "Design Patterns" av Erich Gamma, Richard Helm, Ralph Johnson og John Vlissides" og kanskje "Patterns of Enterprise Application Architecture" av Martin Fowler regnes som klassikere. Det beste jeg har lest med eksempler i PHP - dette. Tilfeldigvis er all denne litteraturen ganske kompleks for folk som nettopp har begynt å mestre OOP. Så jeg hadde ideen om å presentere noen av mønstrene som jeg finner mest nyttige i en svært forenklet form. I andre ord, denne artikkelen er mitt første forsøk på å tolke designmønstre i KISS-stilen.
I dag skal vi snakke om hvilke problemer som kan oppstå med objektinitialisering i en OOP-applikasjon og hvordan du kan bruke noen populære designmønstre for å løse disse problemene.

Eksempel

En moderne OOP-applikasjon fungerer med titalls, hundrevis og noen ganger tusenvis av objekter. Vel, la oss se nærmere på hvordan disse objektene initialiseres i applikasjonene våre. Objektinitialisering er det eneste aspektet som interesserer oss i denne artikkelen, så jeg bestemte meg for å utelate all den "ekstra" implementeringen.
La oss si at vi opprettet en super-duper nyttig klasse som kan sende en GET-forespørsel til en spesifikk URI og returnere HTML fra serversvaret. For at klassen ikke skal virke for enkel, la den også sjekke resultatet og gi et unntak hvis serveren svarer "feil".

Class Grabber (offentlig funksjon get($url) (/** returnerer HTML-kode eller kaster et unntak */))

La oss lage en annen klasse hvis objekter vil være ansvarlige for å filtrere den mottatte HTML-en. Filtermetoden tar HTML-kode og en CSS-velger som argumenter, og den returnerer en rekke elementer funnet for den gitte velgeren.

Klasse HtmlExtractor ( offentlig funksjonsfilter ($html, $selector) (/** returnerer en rekke filtrerte elementer */) )

Tenk deg nå at vi må få søkeresultater på Google for gitte søkeord. For å gjøre dette vil vi introdusere en annen klasse som vil bruke Grabber-klassen til å sende en forespørsel, og HtmlExtractor-klassen for å trekke ut nødvendig innhold. Den vil også inneholde logikken for å konstruere URIen, en velger for å filtrere den mottatte HTML-en og behandle de oppnådde resultatene.

Klasse GoogleFinder ( privat $grabber; privat $filter; offentlig funksjon __construct() ( $this->grabber = new Grabber(); $this->filter = new HtmlExtractor(); ) public function find($searchString) ( /* * returnerer en rekke baserte resultater */) )

La du merke til at initialiseringen av Grabber- og HtmlExtractor-objektene er i konstruktøren til GoogleFinder-klassen? La oss tenke på hvor god denne avgjørelsen er.
Selvfølgelig er det ikke en god idé å hardkode opprettelsen av objekter i en konstruktør. Og det er derfor. For det første vil vi ikke enkelt kunne overstyre Grabber-klassen i testmiljøet for å unngå å sende en reell forespørsel. For å være rettferdig er det verdt å si at dette kan gjøres ved hjelp av Reflection API. De. den tekniske muligheten finnes, men dette er langt fra den mest praktiske og opplagte måten.
For det andre vil det samme problemet oppstå hvis vi ønsker å gjenbruke GoogleFinder-logikk med andre Grabber- og HtmlExtractor-implementeringer. Oppretting av avhengigheter er hardkodet i klassekonstruktøren. Og i beste fall vil vi kunne arve GoogleFinder og overstyre konstruktøren. Og selv da, bare hvis omfanget av grabber- og filteregenskapene er beskyttet eller offentlig.
Et siste punkt, hver gang vi oppretter et nytt GoogleFinder-objekt, vil et nytt par med avhengighetsobjekter bli opprettet i minnet, selv om vi ganske enkelt kan bruke ett Grabber-objekt og ett HtmlExtractor-objekt i flere GoogleFinder-objekter.
Jeg tror du allerede forstår at initialisering av avhengighet må flyttes utenfor klassen. Vi kan kreve at allerede forberedte avhengigheter sendes til konstruktøren av GoogleFinder-klassen.

Klasse GoogleFinder ( privat $grabber; privat $filter; offentlig funksjon __construct(Grabber $grabber, HtmlExtractor $filter) ( $this->grabber = $grabber; $this->filter = $filter; ) offentlig funksjon finn($searchString) ( /** returnerer en rekke baserte resultater */) )

Hvis vi ønsker å gi andre utviklere muligheten til å legge til og bruke sine egne Grabber- og HtmlExtractor-implementeringer, bør vi vurdere å introdusere grensesnitt for dem. I dette tilfellet er dette ikke bare nyttig, men også nødvendig. Jeg mener at hvis vi bare bruker én implementering i et prosjekt og ikke forventer å lage nye i fremtiden, så bør vi nekte å lage et grensesnitt. Det er bedre å handle i henhold til situasjonen og gjøre enkel refaktorering når det er et reelt behov for det.
Nå har vi alle nødvendige klasser og vi kan bruke GoogleFinder-klassen i kontrolleren.

Klassekontroller ( public function action() ( /* Noen ting */ $finder = new GoogleFinder(new Grabber(), new HtmlExtractor()); $results = $finder->

La oss oppsummere mellomresultatene. Vi skrev veldig lite kode, og ved første øyekast gjorde vi ikke noe galt. Men ... hva om vi trenger å bruke et objekt som GoogleFinder et annet sted? Vi må duplisere opprettelsen. I vårt eksempel er dette bare én linje, og problemet er ikke så merkbart. I praksis kan initialisering av objekter være ganske komplisert og kan ta opptil 10 linjer, eller enda mer. Andre problemer som er typiske for kodeduplisering oppstår også. Hvis du under refactoring-prosessen må endre navnet på klassen som brukes eller objektinitieringslogikken, må du manuelt endre alle stedene. Jeg tror du vet hvordan det skjer :)
Vanligvis håndteres hardcode enkelt. Dupliserte verdier er vanligvis inkludert i konfigurasjonen. Dette lar deg endre verdier sentralt alle steder der de brukes.

Registermal.

Så vi bestemte oss for å flytte opprettelsen av objekter inn i konfigurasjonen. La oss gjøre det.

$registry = new ArrayObject(); $registry["grabber"] = new Grabber(); $registry["filter"] = ny HtmlExtractor(); $registry["google_finder"] = ny GoogleFinder($registry["grabber"], $registry["filter"]);
Alt vi trenger å gjøre er å sende ArrayObject til kontrolleren og problemet er løst.

Klassekontroller ( privat $registry; public function __construct(ArrayObject $registry) ( $this->registry = $registry; ) public function action() ( /* Noen ting */ $results = $this->register["google_finder" ]->find("søkestreng"); /* Gjør noe med resultater */ ) )

Vi kan videreutvikle ideen om Registry. Arv ArrayObject, kapsle inn opprettelsen av objekter i en ny klasse, forby å legge til nye objekter etter initialisering, etc. Men etter min mening gjør den gitte koden det helt klart hva registermalen er. Dette mønsteret er ikke generativt, men det løser problemene våre til en viss grad. Registry er bare en beholder der vi kan lagre objekter og sende dem rundt i applikasjonen. For at objekter skal bli tilgjengelige, må vi først opprette dem og registrere dem i denne beholderen. La oss se på fordelene og ulempene med denne tilnærmingen.
Ved første øyekast har vi nådd målet vårt. Vi sluttet å hardkode klassenavn og lage objekter på ett sted. Vi lager objekter i en enkelt kopi, som garanterer gjenbruk. Hvis logikken for å lage objekter endres, må bare ett sted i applikasjonen redigeres. Som en bonus fikk vi muligheten til å sentralt administrere objekter i registeret. Vi kan enkelt få en liste over alle tilgjengelige objekter og utføre noen manipulasjoner med dem. La oss nå se på hva vi kanskje ikke liker med denne malen.
Først må vi opprette objektet før vi registrerer det i registeret. Følgelig er det stor sannsynlighet for å lage "unødvendige gjenstander", dvs. de som vil bli opprettet i minnet, men som ikke vil bli brukt i applikasjonen. Ja, vi kan legge til objekter til registeret dynamisk, dvs. opprette bare de objektene som er nødvendige for å behandle en spesifikk forespørsel. På en eller annen måte må vi kontrollere dette manuelt. Følgelig vil det over tid bli svært vanskelig å vedlikeholde.
For det andre har vi en ny kontrolleravhengighet. Ja, vi kan motta objekter gjennom en statisk metode i Registry slik at vi ikke trenger å sende Registry til konstruktøren. Men etter min mening bør du ikke gjøre dette. Statiske metoder er en enda tettere forbindelse enn å skape avhengigheter inne i et objekt, og vanskeligheter med å teste (om dette emnet).
For det tredje forteller ikke kontrollergrensesnittet oss noe om hvilke objekter det bruker. Vi kan få et hvilket som helst objekt tilgjengelig i registeret i kontrolleren. Det vil være vanskelig for oss å si hvilke objekter kontrolleren bruker før vi sjekker all kildekoden.

Fabrikkmetode

Vår største kritikk med Registry er at et objekt må initialiseres før det kan nås. I stedet for å initialisere et objekt i konfigurasjonen, kan vi separere logikken for å lage objekter i en annen klasse, som vi kan "be" om å bygge objektet vi trenger. Klasser som er ansvarlige for å lage objekter kalles fabrikker. Og designmønsteret kalles Factory Method. La oss se på et eksempel på en fabrikk.

Class Factory ( offentlig funksjon getGoogleFinder() ( returner ny GoogleFinder($this->getGrabber(), $this->getHtmlExtractor()); ) privat funksjon getGrabber() ( returner ny Grabber(); ) privat funksjon getHtmlExtractor() ( returner ny HtmlFiletr(); ) )

Som regel lages det fabrikker som er ansvarlige for å lage én type objekt. Noen ganger kan en fabrikk opprette en gruppe relaterte objekter. Vi kan bruke caching i en egenskap for å unngå å gjenskape objekter.

Class Factory ( privat $finder; offentlig funksjon getGoogleFinder() ( if (null === $this->finder) ( $this->finder = new GoogleFinder($this->getGrabber(), $this->getHtmlExtractor() ); ) returner $this->finder; ) )

Vi kan parameterisere en fabrikkmetode og delegere initialisering til andre fabrikker avhengig av innkommende parameter. Dette vil allerede være en abstrakt fabrikkmal.
Hvis vi trenger å modularisere applikasjonen, kan vi kreve at hver modul har sine egne fabrikker. Vi kan videreutvikle temaet fabrikker, men jeg tror at essensen i denne malen er tydelig. La oss se hvordan vi vil bruke fabrikken i kontrolleren.

Klassekontroller ( privat $factory; public function __construct(Factory $factory) ( $this->factory = $factory; ) public function action() ( /* Noen ting */ $results = $this->factory->getGoogleFinder( )->find("søkestreng"); /* Gjør noe med resultater */ ) )

Fordelene med denne tilnærmingen inkluderer dens enkelhet. Objektene våre er opprettet eksplisitt, og din IDE vil lett føre deg til stedet der dette skjer. Vi har også løst registerproblemet slik at objekter i minnet bare blir opprettet når vi "ber" fabrikken om å gjøre det. Men vi har ennå ikke bestemt hvordan vi skal levere de nødvendige fabrikkene til kontrollerene. Det er flere alternativer her. Du kan bruke statiske metoder. Vi kan la kontrollere lage de nødvendige fabrikkene selv og oppheve alle våre forsøk på å bli kvitt copy-paste. Du kan lage en fabrikk med fabrikker og bare sende det til kontrolleren. Men å få objekter i kontrolleren vil bli litt mer komplisert, og du må administrere avhengigheter mellom fabrikker. I tillegg er det ikke helt klart hva vi skal gjøre hvis vi ønsker å bruke moduler i applikasjonen vår, hvordan registrere modulfabrikker, hvordan administrere koblinger mellom fabrikker fra ulike moduler. Generelt har vi mistet hovedfordelen med fabrikken - den eksplisitte opprettelsen av gjenstander. Og vi har fortsatt ikke løst problemet med det "implisitte" kontrollergrensesnittet.

Service Locator

Service Locator-malen lar deg løse mangelen på fragmentering av fabrikker og administrere opprettelsen av objekter automatisk og sentralt. Hvis vi tenker på det, kan vi introdusere et ekstra abstraksjonslag som vil være ansvarlig for å lage objekter i applikasjonen vår og administrere relasjonene mellom disse objektene. For at dette laget skal kunne lage objekter for oss, må vi gi det kunnskap om hvordan dette skal gjøres.
Service Locator-mønstervilkår:
  • Service er en ferdig gjenstand som kan hentes fra en container.
  • Tjenestedefinisjon – logikk for tjenesteinitialisering.
  • En container (Service Container) er et sentralt objekt som lagrer alle beskrivelser og kan lage tjenester basert på dem.
Enhver modul kan registrere sine tjenestebeskrivelser. For å få service fra containeren, må vi be om det med nøkkel. Det er mange alternativer for å implementere Service Locator; i den enkleste versjonen kan vi bruke ArrayObject som en beholder og en lukking som en beskrivelse av tjenester.

Class ServiceContainer utvider ArrayObject ( public function get($key) ( if (is_callable($this[$key])) (retur call_user_func($this[$key]); ) throw new \RuntimeException("Kan ikke finne tjenestedefinisjonen under nøkkelen [ $key ]"); ) )

Da vil definisjonsregistreringen se slik ut:

$container = ny ServiceContainer(); $container["grabber"] = funksjon () (retur ny Grabber(); ); $container["html_filter"] = funksjon () (retur ny HtmlExtractor(); ); $container["google_finder"] = funksjon() bruk ($container) (retur ny GoogleFinder($container->get("grabber"), $container->get("html_filter")); );

Og bruken i kontrolleren er slik:

Klassekontroller ( privat $container; public function __construct(ServiceContainer $container) ( $this->container = $container; ) public function action() ( /* Noen ting */ $results = $this->container->get( "google_finder")->finn("søkestreng"); /* Gjør noe med resultater */ ) )

En Service Container kan være veldig enkel, eller den kan være veldig kompleks. For eksempel gir Symfony Service Container mange funksjoner: parametere, omfang av tjenester, søk etter tjenester etter tagger, aliaser, private tjenester, muligheten til å gjøre endringer i containeren etter å ha lagt til alle tjenester (kompilatorpasseringer) og mye mer. DIExtraBundle utvider funksjonene til standardimplementeringen ytterligere.
Men la oss gå tilbake til vårt eksempel. Som du kan se, løser Service Locator ikke bare alle de samme problemene som de tidligere malene, men gjør det også enkelt å bruke moduler med egne tjenestedefinisjoner.
I tillegg fikk vi på rammenivået et ekstra abstraksjonsnivå. Nemlig ved å endre ServiceContainer::get-metoden kan vi for eksempel erstatte objektet med en proxy. Og omfanget av bruken av proxy-objekter er bare begrenset av utviklerens fantasi. Her kan du implementere AOP-paradigmet, LazyLoading, etc.
Men de fleste utviklere anser fortsatt Service Locator som et antimønster. Fordi vi i teorien kan ha så mange såkalte Container Aware-klasser (dvs. klasser som inneholder en referanse til containeren). For eksempel vår Controller, hvor vi kan få hvilken som helst tjeneste.
La oss se hvorfor dette er dårlig.
Først tester du igjen. I stedet for å lage hån bare for klassene som brukes i tester, må du håne hele beholderen eller bruke en ekte beholder. Det første alternativet passer ikke deg, fordi... du må skrive mye unødvendig kode i tester, for det andre fordi det strider mot prinsippene for enhetstesting, og kan føre til ekstra kostnader for å vedlikeholde tester.
For det andre vil det være vanskelig for oss å refaktorere. Ved å endre en hvilken som helst tjeneste (eller ServiceDefinition) i beholderen, vil vi bli tvunget til å sjekke alle avhengige tjenester også. Og dette problemet kan ikke løses ved hjelp av en IDE. Å finne slike steder gjennom hele applikasjonen vil ikke være så lett. I tillegg til avhengige tjenester, må du også sjekke alle stedene der den refaktorerte tjenesten er hentet fra containeren.
Vel, den tredje grunnen er at ukontrollert trekking av tjenester fra containeren før eller siden vil føre til rot i koden og unødvendig forvirring. Dette er vanskelig å forklare, du trenger bare å bruke mer og mer tid på å forstå hvordan denne eller den tjenesten fungerer, med andre ord, du kan fullt ut forstå hva den gjør eller hvordan en klasse fungerer bare ved å lese hele kildekoden.

Avhengighetsinjeksjon

Hva annet kan du gjøre for å begrense bruken av en beholder i en applikasjon? Du kan overføre kontroll over opprettelsen av alle brukerobjekter, inkludert kontrollere, til rammeverket. Med andre ord, brukerkode skal ikke kalle containerens get-metode. I vårt eksempel kan vi legge til en definisjon for kontrolleren til beholderen:

$container["google_finder"] = funksjon() bruk ($container) (retur ny kontroller(Grabber $grabber); );

Og bli kvitt beholderen i kontrolleren:

Klassekontroller ( privat $finder; public function __construct(GoogleFinder $finder) ( $this->finder = $finder; ) public function action() ( /* Noen ting */ $results = $this->finder->finn( "søkestreng"); /* Gjør noe med resultater */ ) )

Denne tilnærmingen (når tilgang til tjenestebeholderen ikke er gitt til klientklasser) kalles Dependency Injection. Men denne malen har også både fordeler og ulemper. Så lenge vi holder oss til prinsippet om enkeltansvar, ser koden veldig vakker ut. Først av alt ble vi kvitt containeren i klientklassene, noe som gjorde koden deres mye klarere og enklere. Vi kan enkelt teste kontrolleren ved å erstatte de nødvendige avhengighetene. Vi kan lage og teste hver klasse uavhengig av andre (inkludert kontrollerklasser) ved å bruke en TDD- eller BDD-tilnærming. Når vi lager tester, kan vi abstrahere bort fra beholderen, og senere legge til en definisjon når vi trenger å bruke spesifikke forekomster. Alt dette vil gjøre koden vår enklere og tydeligere, og testingen mer gjennomsiktig.
Men det er nødvendig å nevne den andre siden av mynten. Faktum er at kontrollere er veldig spesifikke klasser. La oss starte med det faktum at kontrolløren som regel inneholder et sett med handlinger, noe som betyr at det bryter med prinsippet om enkelt ansvar. Som et resultat kan kontrollerklassen ha mange flere avhengigheter enn det som er nødvendig for å utføre en spesifikk handling. Bruk av lat initialisering (objektet blir instansiert ved første gangs bruk, og før det brukes en lett proxy) løser ytelsesproblemet til en viss grad. Men fra et arkitektonisk synspunkt er det heller ikke helt riktig å skape mange avhengigheter til en kontroller. I tillegg er testing av kontrollere vanligvis en unødvendig operasjon. Alt avhenger selvfølgelig av hvordan testingen er organisert i søknaden din og hvordan du selv føler det.
Fra forrige avsnitt innså du at bruk av Dependency Injection ikke helt eliminerer arkitektoniske problemer. Tenk derfor på hvordan det vil være mer praktisk for deg, om du vil lagre en lenke til beholderen i kontrollere eller ikke. Det er ingen enkelt riktig løsning her. Jeg tror begge tilnærmingene er gode så lenge kontrollerkoden forblir enkel. Men definitivt, du bør ikke lage Conatiner Aware-tjenester i tillegg til kontrollere.

konklusjoner

Vel, tiden er inne for å oppsummere alt som har blitt sagt. Og mye er sagt... :)
Så for å strukturere arbeidet med å lage objekter, kan vi bruke følgende mønstre:
  • Register: Malen har åpenbare ulemper, den mest grunnleggende av disse er behovet for å lage objekter før de legges i en felles beholder. Det er klart at vi vil få flere problemer enn fordeler ved å bruke den. Dette er helt klart ikke den beste bruken av malen.
  • Fabrikkmetode: Hovedfordelen med mønsteret: objekter lages eksplisitt. Den største ulempen: kontrollerene må enten bekymre seg for å lage fabrikker selv, noe som ikke helt løser problemet med at klassenavn blir hardkodet, eller rammeverket må være ansvarlig for å gi kontrollerene alle nødvendige fabrikker, noe som ikke vil være så åpenbart. Det er ingen mulighet til sentralt å administrere prosessen med å lage objekter.
  • Service Locator: En mer avansert måte å kontrollere opprettelsen av objekter på. Et ekstra abstraksjonsnivå kan brukes til å automatisere vanlige oppgaver som oppstår når du oppretter objekter. For eksempel:
    klasse ServiceContainer utvider ArrayObject ( offentlig funksjon get($key) ( if (is_callable($this[$key])) ( $obj = call_user_func($this[$key]); if ($obj instans av RequestAwareInterface) ( $obj- >setRequest($this->get("request")); ) returner $obj; ) throw new \RuntimeException("Kan ikke finne tjenestedefinisjonen under nøkkelen [ $key ]"); ) )
    Ulempen med Service Locator er at den offentlige API-en til klasser slutter å være informativ. Det er nødvendig å lese hele koden for klassen for å forstå hvilke tjenester som brukes i den. En klasse som inneholder en referanse til en beholder er vanskeligere å teste.
  • Avhengighetsinjeksjon: I hovedsak kan vi bruke den samme tjenestebeholderen som for det forrige mønsteret. Forskjellen er hvordan denne beholderen brukes. Hvis vi unngår å gjøre klasser avhengige av containeren, vil vi få et tydelig og eksplisitt klasse-API.
Dette er ikke alt jeg vil fortelle deg om problemet med å lage objekter i PHP-applikasjoner. Det er også prototypemønsteret, vi vurderte ikke bruken av Reflection API, vi la til side problemet med lat lasting av tjenester og mange andre nyanser. Artikkelen ble ganske lang, så jeg avslutter den :)
Jeg ønsket å vise at avhengighetsinjeksjon og andre mønstre ikke er så kompliserte som man vanligvis tror.
Hvis vi snakker om Dependency Injection, så finnes det for eksempel KISS-implementeringer av dette mønsteret

Berøring av strukturen til den fremtidige databasen. En start har blitt gjort, og vi kan ikke trekke oss tilbake, og jeg tenker ikke engang på det.

Vi kommer tilbake til databasen litt senere, men foreløpig begynner vi å skrive koden for motoren vår. Men først, litt maskinvare. Begynne.

Tidenes morgen

For øyeblikket har vi bare noen ideer og forståelse for driften av systemet som vi ønsker å implementere, men det er ingen selve implementeringen ennå. Vi har ingenting å jobbe med: vi har ingen funksjonalitet - og, som du husker, delte vi den inn i 2 deler: intern og ekstern. Alfabetet krever bokstaver, men ekstern funksjonalitet krever intern funksjonalitet – det er der vi starter.

Men ikke så fort. For at det skal fungere må du gå litt dypere. Systemet vårt representerer et hierarki, og ethvert hierarkisk system har en begynnelse: et monteringspunkt i Linux, en lokal disk i Windows, et system for en stat, et selskap, en utdanningsinstitusjon, etc. Hvert element i et slikt system er underordnet noen og kan ha flere underordnede, og for å henvende seg til sine naboer og deres underordnede bruker det overordnede eller selve begynnelsen. Et godt eksempel på et hierarkisk system er et slektstre: et utgangspunkt er valgt - en forfedre - og vi går. I systemet vårt trenger vi også et utgangspunkt som vi skal vokse grener fra - moduler, plugins osv. Vi trenger et slags grensesnitt som alle modulene våre vil "kommunisere" gjennom. For videre arbeid må vi bli kjent med konseptet " design mønster" og et par av deres implementeringer.

Design mønstre

Det er veldig mange artikler om hva det er og hvilke varianter det er; emnet er ganske forvirret og jeg vil ikke fortelle deg noe nytt. På min favoritt Wiki er det informasjon om dette emnet: en vogn med sklie og litt mer.

Designmønstre kalles også ofte designmønstre eller ganske enkelt mønstre (fra det engelske ordet mønster, oversatt som betyr "mønster"). Videre i artiklene, når jeg snakker om mønstre, vil jeg mene designmønstre.

Fra den enorme listen over alle slags skumle (og ikke så skumle) mønsternavn er vi bare interessert i to så langt: registry og singleton.

Register (eller registrere deg) er et mønster som opererer på en bestemt matrise der du kan legge til og fjerne et bestemt sett med objekter og få tilgang til noen av dem og dets muligheter.

Ensom ulv (eller singleton) er et mønster som sikrer at bare én forekomst av en klasse kan eksistere. Den kan ikke kopieres, legges i dvale eller vekkes (snakker om PHP-magi: __clone(), __sleep(), __wakeup()). Singleton har et globalt tilgangspunkt.

Definisjonene er ikke fullstendige eller generaliserte, men dette er nok for forståelse. Vi trenger dem uansett ikke separat. Vi er interessert i egenskapene til hvert av disse mønstrene, men i en klasse: et slikt mønster kalles singleton registry eller Singleton Registry.

Hva vil dette gi oss?
  • Vi vil garantert ha en enkelt forekomst av registret, der vi kan legge til objekter når som helst og bruke dem fra hvor som helst i koden;
  • det vil være umulig å kopiere det og bruke annen uønsket (i dette tilfellet) magi av PHP-språket.

På dette stadiet er det nok å forstå at et enkelt register vil tillate oss å implementere en modulær struktur av systemet, som er det vi ønsket når vi diskuterte målene i , og du vil forstå resten etter hvert som utviklingen skrider frem.

Vel, nok ord, la oss skape!

Første linjer

Siden denne klassen vil forholde seg til funksjonaliteten til kjernen, vil vi starte med å lage en mappe i roten av prosjektet vårt kalt kjerne der vi vil plassere alle klasser av kjernemoduler. Vi starter med registret, så la oss kalle filen registry.php

Vi er ikke interessert i muligheten for at en nysgjerrig bruker vil legge inn en direkte adresse til filen vår i nettleserlinjen, så vi må beskytte oss mot dette. For å oppnå dette målet trenger vi bare å definere en viss konstant i den kjørbare hovedfilen, som vi vil sjekke. Ideen er ikke ny; så vidt jeg husker, ble den brukt i Joomla. Dette er en enkel og fungerende metode, så vi klarer oss uten sykler her.

Siden vi beskytter noe som er tilkoblet, vil vi kalle konstanten _PLUGSECURE_:

If (!defined("_PLUGSECURE_")) (die("Direkte modulanrop er forbudt!"); )

Nå, hvis du prøver å få tilgang til denne filen direkte, vil ingenting nyttig komme ut, noe som betyr at målet er oppnådd.

Deretter foreslår jeg å fastsette en viss standard for alle modulene våre. Jeg ønsker å gi hver modul en funksjon som vil returnere noe informasjon om den, for eksempel navnet på modulen, og denne funksjonen må være påkrevd i klassen. For å nå dette målet skriver vi følgende:

Interface StorableObject ( offentlig statisk funksjon getClassName(); )

Som dette. Nå, hvis vi kobler til en klasse uten en funksjon getClassName() vi vil se en feilmelding. Jeg vil ikke fokusere på dette foreløpig, det vil være nyttig for oss senere, i det minste for testing og feilsøking.

Det er tid for selve klassen i singelregisteret vårt. Vi starter med å erklære klassen og noen av dens variabler:

Class Registry implementerer StorableObject ( //modulnavn, lesbar privat statisk $className = "Registry"; //registerforekomst privat statisk $instance; //array of objects private static $objects = array();

Så langt er alt logisk og forståelig. Nå, som du husker, har vi et register med singleton-egenskaper, så la oss umiddelbart skrive en funksjon som lar oss jobbe med registeret på denne måten:

Offentlig statisk funksjon singleton() ( if(!isset(self::$instance)) ( $obj = __KLASSE__; self::$instance = new $obj; ) returner self::$instance; )

Bokstavelig talt: funksjonen sjekker om en forekomst av registeret vårt eksisterer: hvis ikke, oppretter den det og returnerer det; hvis det allerede eksisterer, returnerer det det ganske enkelt. I dette tilfellet trenger vi ikke noe magi, så for beskyttelse vil vi erklære det privat:

Privat funksjon __construct()() privat funksjon __clone()() privat funksjon __wakeup()() privat funksjon __sleep() ()

Nå trenger vi en funksjon for å legge til et objekt i registeret vårt - denne funksjonen kalles en setter og jeg bestemte meg for å implementere den på to måter for å vise hvordan vi kan bruke magi og gi en alternativ måte å legge til et objekt på. Den første metoden er en standardfunksjon, den andre utfører den første gjennom magien til __set().

//$object - bane til det tilkoblede objektet //$key - tilgangsnøkkel til objektet i registerets offentlige funksjon addObject($key, $object) ( require_once($object); //opprett et objekt i en rekke objekter self::$objects[ $key] = ny $key(self::$instance); ) //en alternativ metode gjennom magisk offentlig funksjon __set($key, $object) ( $this->addObject($key, $ objekt);)

Nå, for å legge til et objekt i registeret vårt, kan vi bruke to typer oppføringer (la oss si at vi allerede har opprettet en registerforekomst $registry og vi vil legge til config.php-filen):

$registry->addObject("config", "/core/config.php"); //vanlig metode $registry->config = "/core/config.php"; //via den magiske PHP-funksjonen __set()

Begge oppføringene vil utføre samme funksjon - de vil koble filen, opprette en forekomst av klassen og plassere den i registeret med nøkkelen. Det er ett viktig poeng her, vi må ikke glemme det i fremtiden: objektnøkkelen i registeret må samsvare med klassenavnet i det tilkoblede objektet. Hvis du ser på koden igjen, vil du forstå hvorfor.

Hvilket opptak du skal bruke er opp til deg. Jeg foretrekker å ta opp gjennom den magiske metoden - den er "penere" og kortere.

Så vi har sortert ut å legge til et objekt, nå trenger vi en funksjon for å få tilgang til et tilkoblet objekt med nøkkel - en getter. Jeg implementerte det også med to funksjoner, som ligner på setteren:

//hent objektet fra registeret //$key - nøkkelen i den offentlige matrisen getObject($key) ( //sjekk om variabelen er et objekt if (is_object(self::$objects[$key])) ( //hvis ja, så returnerer vi dette objektet returnerer selv::$objekter[$nøkkel]; ) ) //en lignende metode gjennom magisk offentlig funksjon __get($nøkkel) (if (er_objekt(selv::$objekter[$ nøkkel])) ( returner selv: :$objekter[$nøkkel]; ) )

Som med setteren, vil vi ha to tilsvarende oppføringer for å få tilgang til objektet:

$registry->getObject("config"); //vanlig metode $registry->config; //via den magiske PHP-funksjonen __get()

Den oppmerksomme leseren vil umiddelbart stille spørsmålet: hvorfor i den magiske funksjonen __set() kaller jeg bare en vanlig (ikke-magisk) objekttilføyingsfunksjon, men i __get()-getteren kopierer jeg getObject()-funksjonskoden i stedet for det samme kallet?Ærlig talt, jeg kan ikke svare nøyaktig nok på dette spørsmålet, jeg vil bare si at jeg hadde problemer når jeg jobbet med __get()-magien i andre moduler, men når du skriver om koden "head-on" er det ingen slike problemer.

Kanskje det er derfor jeg ofte så i artikler bebreidelser mot PHP-magiske metoder og råd for å unngå å bruke dem.

"All magi kommer med en pris." © Rumplestiltskin

På dette stadiet er hovedfunksjonaliteten til registret vårt allerede klar: vi kan lage en enkelt forekomst av registret, legge til objekter og få tilgang til dem både ved hjelp av konvensjonelle metoder og gjennom de magiske metodene til PHP-språket. "Hva med sletting?"— Vi trenger ikke denne funksjonen foreløpig, og jeg er ikke sikker på at noe vil endre seg i fremtiden. Til slutt kan vi alltid legge til den nødvendige funksjonaliteten. Men hvis vi nå prøver å lage en forekomst av registeret vårt,

$registry = Register::singleton();

vi får en feilmelding:

Fatal feil: Klasseregisteret inneholder 1 abstrakt metode og må derfor erklæres abstrakt eller implementere de resterende metodene (StorableObject::getClassName) i ...

Alt fordi vi glemte å skrive en påkrevd funksjon. Husker du helt i begynnelsen at jeg snakket om en funksjon som returnerer navnet på modulen? Dette er det som gjenstår å legge til for full funksjonalitet. Det er enkelt:

Offentlig statisk funksjon getClassName() ( return self::$className; )

Nå skal det ikke være noen feil. Jeg foreslår å legge til en funksjon til, den er ikke nødvendig, men før eller siden kan den komme til nytte; vi vil bruke den i fremtiden for å sjekke og feilsøke. Funksjonen vil returnere navnene på alle objekter (moduler) lagt til vårt register:

Offentlig funksjon getObjectsList() ( //arrayen som vi vil returnere $names = array(); //få navnet på hvert objekt fra arrayen av objekter foreach(self::$objects som $obj) ( $names = $ obj->getClassName(); ) //legg til navnet på registermodulen til arrayen array_push($names, self::getClassName()); //og returner $names; )

Det er alt. Dette fullfører registeret. La oss sjekke arbeidet hans? Når du sjekker, må vi koble til noe - la det være en konfigurasjonsfil. Opprett en ny core/config.php-fil og legg til minimumsinnholdet som registeret vårt krever:

//ikke glem å sjekke konstanten if (!defined("_PLUGSECURE_")) (die("Direkte modulanrop er forbudt!"); ) class Config ( //modulnavn, lesbar privat statisk $className = "Config "; offentlig statisk funksjon getClassName() (retur self::$className; ) )

Noe sånt. La oss nå gå videre til selve bekreftelsen. I roten til prosjektet vårt, lag en fil index.php og skriv følgende kode inn i den:

Define("_PLUGSECURE_", true); //definerte en konstant for å beskytte mot direkte tilgang til objekter require_once "/core/registry.php"; //koblet til registeret $registry = Register::singleton(); //opprettet en register singleton-instans $registry->config = "/core/config.php"; //koble til vår, så langt ubrukelige, konfigurasjon //vis navnene på de tilkoblede modulene echo " Tilkoblet"; foreach ($registry->

  • " . $navn ."
  • "; }

    Eller, hvis du fortsatt unngår magi, kan den femte linjen erstattes med en alternativ metode:

    Define("_PLUGSECURE_", true); //definerte en konstant for å beskytte mot direkte tilgang til objekter require_once "/core/registry.php"; //koblet til registeret $registry = Register::singleton(); //opprettet en register singleton-instans $registry->addObject("config", "/core/config.php"); //koble til vår, så langt ubrukelige, konfigurasjon //vis navnene på de tilkoblede modulene echo " Tilkoblet"; foreach ($registry->getObjectsList() som $navn) ( echo "

  • " . $navn ."
  • "; }

    Åpne nå nettleseren og skriv http://localhost/index.php eller ganske enkelt http://localhost/ i adressefeltet (relevant hvis du bruker standard Open Server eller lignende webserverinnstillinger)

    Som et resultat bør vi se noe slikt:

    Som du kan se, er det ingen feil, noe som betyr at alt fungerer, noe jeg gratulerer deg med :)

    I dag stopper vi ved dette. I neste artikkel vil vi gå tilbake til databasen og skrive en klasse for arbeid med MySQL SUDB, koble den til registeret og teste arbeidet i praksis. Ser deg!

    Dette mønsteret, i likhet med Singleton, forårsaker sjelden en positiv reaksjon fra utviklere, da det gir opphav til de samme problemene ved testing av applikasjoner. Likevel skjeller de, men bruker aktivt. I likhet med Singleton finnes registermønsteret i mange applikasjoner, og på en eller annen måte forenkler det å løse visse problemer.

    La oss vurdere begge alternativene i rekkefølge.

    Det som kalles et "rent register" eller ganske enkelt Registry er en implementering av en klasse med et statisk grensesnitt. Hovedforskjellen fra Singleton-mønsteret er at det blokkerer muligheten til å lage minst én forekomst av en klasse. I lys av dette er det ingen vits i å skjule de magiske metodene __clone() og __wakeup() bak den private eller beskyttede modifikatoren.

    Registerklasse må ha to statiske metoder - en getter og en setter. Setteren legger det passerte objektet til lagring med en binding til den gitte nøkkelen. Getteren returnerer følgelig en gjenstand fra butikken. En butikk er ikke noe mer enn en assosiativ nøkkelverdi-matrise.

    For fullstendig kontroll over registret introduseres et annet grensesnittelement - en metode som lar deg slette et objekt fra lagringen.

    I tillegg til problemer som er identiske med Singleton-mønsteret, er det to til:

    • introdusere en annen type avhengighet - på registernøkler;
    • to forskjellige registernøkler kan ha en referanse til samme objekt

    I det første tilfellet er det umulig å unngå ytterligere avhengighet. Til en viss grad blir vi knyttet til nøkkelnavn.

    Det andre problemet løses ved å introdusere en sjekk i Registry::set()-metoden:

    Offentlig statisk funksjonssett($key, $item) ( if (!array_key_exists($key, self::$_registry)) (foreach (self::$_registry as $val) ( if ($val === $item) ( throw new Exception("Element eksisterer allerede"); ) ) self::$_registry[$key] = $item; ) )

    « Rengjør registermønster"gir opphav til et annet problem - å øke avhengigheten på grunn av behovet for å få tilgang til setter og getter gjennom klassenavnet. Du kan ikke opprette en referanse til et objekt og jobbe med det, slik tilfellet var med Singleton-mønsteret, da denne tilnærmingen var tilgjengelig:

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

    Her har vi muligheten til å lagre en referanse til en Singleton-forekomst, for eksempel i en egenskap for gjeldende klasse, og jobbe med den som kreves av OOP-ideologien: gi den som en parameter til aggregerte objekter eller bruk den i etterkommere.

    For å løse dette problemet er det Singleton Registry implementering, som mange mennesker ikke liker fordi det virker som overflødig kode. Jeg tror grunnen til denne holdningen er en misforståelse av prinsippene til OOP eller en bevisst ignorering av dem.

    _registry[$key] = $objekt; ) statisk offentlig funksjon get($key) ( return self::getInstance()->_registry[$key]; ) privat funksjon __wakeup() ( ) privat funksjon __construct() ( ) privat funksjon __clone() ( ) ) ?>

    For å spare penger har jeg bevisst utelatt kommentarblokker for metoder og egenskaper. Jeg tror ikke de er nødvendige.

    Som jeg allerede har sagt, er den grunnleggende forskjellen at nå er det mulig å lagre en referanse til registervolumet og ikke bruke tungvinte anrop til statiske metoder hver gang. Dette alternativet synes jeg er noe mer riktig. Å være enig eller uenig i min mening spiller ingen rolle, akkurat som min mening i seg selv. Ingen implementeringsfinesser kan eliminere mønsteret fra en rekke av de nevnte ulempene.

    Jeg bestemte meg for å skrive kort om mønstre som ofte brukes i livene våre, flere eksempler, mindre vann, la oss gå.

    Singleton

    Hovedpoenget med "singelen" er at når du sier "Jeg trenger en telefonsentral", ville de si til deg "Den er allerede bygget der," og ikke "La oss bygge den igjen." En "loner" er alltid alene.

    Klasse Singleton ( privat statisk $instance = null; privat funksjon __construct())( /* ... @return Singleton */ ) // Beskytt mot opprettelse via ny Singleton privat funksjon __clone() ( /* ... @return Singleton * / ) // Beskytt mot opprettelse via kloning av privat funksjon __wakeup() ( /* ... @return Singleton */ ) // Beskytt mot opprettelse via unserialize offentlig statisk funksjon getInstance() ( if (is_null(self::$instance ) ) ( self::$instance = new self; ) return self::$instance; ) )

    Register (register, journal over oppføringer)

    Som navnet antyder, er dette mønsteret designet for å lagre poster som er plassert i det, og følgelig returnere disse postene (etter navn) hvis de er nødvendige. I eksempelet med en telefonsentral er det et register i forhold til telefonnumrene til beboere.

    Klasseregister (privat $registry = array(); offentlig funksjonssett($key, $object) ( $this->registry[$key] = $object;) offentlig funksjon get($key) (retur $this->register [$nøkkel]; ) )

    Singleton-registeret- ikke forveksle med)

    "Registret" er ofte en "ensom", men det trenger ikke alltid være slik. For eksempel kan vi opprette flere journaler i regnskapsavdelingen, i den ene er det ansatte fra "A" til "M", i den andre fra "N" til "Z". Hvert slikt tidsskrift vil være et "register", men ikke et "enkelt", fordi det allerede er 2 tidsskrifter.

    Klasse SingletonRegistry ( privat statisk $instance = null; privat $registry = array(); privat funksjon __construct() ( /* ... @return Singleton */ ) // Beskytt mot opprettelse via ny Singleton privat funksjon __clone() ( / * ... @return Singleton */ ) // Beskytt mot opprettelse via kloning av privat funksjon __wakeup() ( /* ... @return Singleton */ ) // Beskytt mot opprettelse via unserialize offentlig statisk funksjon getInstance() ( if ( is_null(self::$instance)) ( self::$instance = new self; ) return self::$instance; ) public function set($key, $object) ( $this->registry[$key] = $ objekt; ) offentlig funksjon get($key) (retur $this->registry[$key]; ))

    Multiton (pool av "singler") eller med andre ordRegister Singleton ) - Ikke forveksle med Singleton Registry

    Ofte brukes "registeret" spesifikt for å lagre "singler". Men fordi "registeret"-mønsteret er ikke et "generativt mønster", men jeg vil gjerne vurdere "registeret" i forbindelse med "singelen".Derfor kom vi opp med et mønster Multiton, som ihtI kjernen er det et "register" som inneholder flere "singler", som hver har sitt eget "navn" som det kan nås med.

    Kort: lar deg lage objekter av denne klassen, men bare hvis du navngir objektet. Det er ikke noe virkelighetseksempel, men jeg fant følgende eksempel på Internett:

    Klassedatabase (privat statisk $instances = array(); privat funksjon __construct() ( ) privat funksjon __clone() ( ) offentlig statisk funksjon 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); // objekt(Database)#1 (0) ( ) $logger = Database::getInstance("logger"); var_dump($logger); // object(Database)#2 (0) ( ) $masterDupe = Database::getInstance("master"); var_dump($masterDupe); // object(Database)#1 (0) ( ) // Fatal error: Call to private Database::__construct() fra ugyldig kontekst $dbFatalError = new Database(); // PHP Fatal feil: Call to private Database::__clone() $dbCloneError = clone $masterDupe;

    Objektbasseng

    I hovedsak er dette mønsteret et "register" som bare lagrer objekter, ingen strenger, arrays osv. datatyper.

    Fabrikk

    Essensen av mønsteret er nesten fullstendig beskrevet av navnet. Når du skal få tak i noen gjenstander, for eksempel juicebokser, trenger du ikke vite hvordan de er laget på en fabrikk. Du sier ganske enkelt "gi meg en kartong appelsinjuice", og "fabrikken" returnerer den nødvendige pakken til deg. Hvordan? Alt dette bestemmes av fabrikken selv, for eksempel "kopierer" den en allerede eksisterende standard. Hovedformålet med "fabrikken" er å gjøre det mulig, om nødvendig, å endre prosessen med "utseende" til en juicepakke, og forbrukeren selv trenger ikke å bli fortalt noe om dette, slik at han kan be om det som før. Som regel er en fabrikk engasjert i "produksjon" av bare en type "produkt". Det anbefales ikke å lage en "juicefabrikk" som tar hensyn til produksjonen av bildekk. Som i livet er fabrikkmønsteret ofte skapt av en enkelt person.

    Abstrakt klasse AnimalAbstract ( protected $species; public function getSpecies() ( return $this->species; ) ) class Cat extends AnimalAbstract ( protected $species = "cat"; ) class Dog extends AnimalAbstract ( protected $species = "dog"; ) klasse AnimalFactory ( public static function factory($animal) ( switch ($animal) ( case "cat": $obj = new Cat(); break; case "dog": $obj = new Dog(); break; default : throw new Exception("Dyrefabrikken kunne ikke lage dyr av arten "" . $dyr . """, 1000); ) return $obj; ) ) $cat = AnimalFactory::factory("katt"); // object(Cat)#1 echo $cat->getSpecies(); // katt $dog = AnimalFactory::factory("hund"); // objekt(Hund)#1 echo $dog->getSpecies(); // hund $flodhest = AnimalFactory::factory("flodhest"); // Dette vil gi et unntak

    Jeg vil gjerne gjøre deg oppmerksom på at fabrikkmetoden også er et mønster, den kalles fabrikkmetoden.

    Byggmester (bygger)

    Så vi har allerede forstått at "Factory" er en drikkeautomat, den har allerede alt klart, og du sier bare det du trenger. "Builder" er et anlegg som produserer disse drikkene og inneholder alle de komplekse operasjonene og kan sette sammen komplekse gjenstander fra enklere (emballasje, etikett, vann, smaker, etc.) avhengig av forespørselen.

    Class Bottle ( public $name; public $liters; ) /** * alle byggere må */ grensesnitt BottleBuilderInterface ( public function setName(); public function setLiters(); public function getResult(); ) klasse CocaColaBuilder implementerer BottleBuilderInterface ( private $ flaske; offentlig funksjon __construct() ( $this->bottle = new Bottle(); ) offentlig funksjon setName($value) ($this->bottle->name = $value; ) offentlig funksjon setLiters($value) ($ this->bottle->liters = $value; ) offentlig funksjon getResult() (retur $this->bottle; ) ) $juice = new CocaColaBuilder(); $juice->setName("Coca-Cola Light"); $juice->setLiters(2); $juice->getResult();

    Prototype

    Den ligner en fabrikk og tjener også til å lage objekter, men med en litt annen tilnærming. Se for deg selv i en bar, du drakk øl og du går tom for det, sier du til bartenderen - lag meg en til av samme sort. Bartenderen ser på sin side på ølet du drikker og lager en kopi slik du spurte. PHP har allerede en implementering av dette mønsteret, det kalles .

    $newJuice = klone $juice;

    Lat initialisering

    For eksempel ser en sjef en liste over rapporter for ulike typer aktiviteter og tror at disse rapportene allerede eksisterer, men faktisk vises bare navnene på rapportene, og selve rapportene er ennå ikke generert, og vil kun bli generert ved bestilling (for eksempel ved å klikke på Vis rapport-knappen). Et spesielt tilfelle av lat initialisering er opprettelsen av et objekt på det tidspunktet det åpnes. Du kan finne en interessant på Wikipedia, men... i følge teorien vil det riktige eksempelet i php være for eksempel en funksjon

    Adapter eller wrapper (adapter, wrapper)

    Dette mønsteret samsvarer fullt ut med navnet. For å få en "sovjetisk" plugg til å fungere gjennom en Euro-kontakt, kreves en adapter. Det er nettopp dette en "adapter" gjør - den fungerer som et mellomobjekt mellom to andre som ikke kan jobbe direkte med hverandre. Til tross for definisjonen ser jeg i praksis fortsatt forskjellen mellom Adapter og Wrapper.

    Class MyClass ( public function methodA() () ) class MyClassWrapper ( public function __construct())( $this->myClass = new MyClass(); ) public function __call($name, $arguments)( Log::info(" Du er i ferd med å kalle $name-metoden."); return call_user_func_array(array($this->myClass, $name), $arguments); ) ) $obj = new MyClassWrapper(); $obj->metodeA();

    Avhengighetsinjeksjon

    Avhengighetsinjeksjon lar deg flytte deler av ansvaret for enkelte funksjoner til andre objekter. For eksempel, hvis vi trenger å ansette nytt personell, kan vi ikke opprette vår egen HR-avdeling, men introdusere avhengighet av et rekrutteringsselskap, som igjen, på vår første forespørsel "vi trenger en person", enten vil fungere som en HR-avdelingen selv, eller vil finne et annet selskap (ved hjelp av en "service locator") som vil tilby disse tjenestene.
    "Dependency injection" lar deg skifte og bytte ut individuelle deler av selskapet uten å miste den generelle funksjonaliteten.

    Klasse AppleJuice () // denne metoden er en primitiv implementering av Dependency-injeksjonsmønsteret og videre vil du se denne funksjonen getBottleJuice())( $obj = new Eplejuice Eplejuice)( return $obj; ) ) $bottleJuice = getBottleJuice();

    Tenk deg nå at vi ikke lenger vil ha eplejuice, vi vil ha appelsinjuice.

    Klasse AppleJuice() Klasse Appelsinjuice() // denne metoden implementerer getBottleJuice())( $obj = new Appelsinjuice; // sjekk objektet, i tilfelle de la oss øl (øl er ikke juice) if($obj instanceof Appelsinjuice)( return $obj; ) )

    Som du kan se, måtte vi endre ikke bare typen juice, men også sjekken for juicetypen, noe som ikke er veldig praktisk. Det er mye mer riktig å bruke:

    Grensesnitt Juice () Klasse AppleJuice implementerer Juice () Klasse OrangeJuice implementerer Juice () funksjonen getBottleJuice())( $obj = new OrangeJuice; // sjekk objektet, i tilfelle de la oss øl (øl er ikke juice) if($obj tilfelle av Juice)( return $obj; ) )

    Avhengighetsinversjon forveksles noen ganger med avhengighetsinjeksjon, men det er ikke nødvendig å forveksle dem, fordi Avhengighetsinversjon er et prinsipp, ikke et mønster.

    Service Locator

    "Service Locator" er en metode for å implementere "Dependency Injection". Den returnerer forskjellige typer objekter avhengig av initialiseringskoden. La oppgaven være å levere juicepakken vår, laget av en byggmester, fabrikk eller annet, hvor kjøperen måtte ønske. Vi forteller lokalisatoren "gi oss en leveringstjeneste", og ber tjenesten levere juicen til ønsket adresse. I dag er det én gudstjeneste, og i morgen kan det være en annen. Det spiller ingen rolle for oss hvilken spesifikk tjeneste det er, det er viktig for oss å vite at denne tjenesten vil levere det vi forteller den og hvor vi forteller den. På sin side implementerer tjenestene «Lever<предмет>på<адрес>».

    Hvis vi snakker om det virkelige liv, vil sannsynligvis et godt eksempel på en Service Locator være PUD PHP-utvidelsen, fordi I dag jobber vi med en MySQL-database, og i morgen kan vi jobbe med PostgreSQL. Som du allerede har forstått, spiller det ingen rolle for klassen vår hvilken database den sender dataene sine til, det er viktig at den kan gjøre det.

    $db = ny PUD(" mysql:dbname=test;host=localhost", $user, $pass); $db = new PUD(" pgsql:dbname=test vert=localhost", $user, $pass);

    Forskjellen mellom Dependency injection og Service Locator

    Hvis du ikke har lagt merke til det ennå, vil jeg gjerne forklare. Avhengighetsinjeksjon Som et resultat returnerer den ikke en tjeneste (som kan levere noe et sted), men et objekt hvis data den bruker.

    Jeg vil prøve å fortelle deg om min implementering av registermønsteret i PHP. Registry er en OOP-erstatning for globale variabler, designet for å lagre data og overføre dem mellom systemmoduler. Følgelig er den utstyrt med standardegenskaper - skriv, les, slett. Her er en typisk implementering.

    Vel, på denne måten får vi en dum erstatning av metodene $key = $value - Registry::set($key, $value) $key - Registry::get($key) unset($key) - remove Registry::remove ($key ) Det blir bare uklart - hvorfor denne ekstra koden. Så la oss lære klassen vår å gjøre det globale variabler ikke kan gjøre. La oss legge pepper til det.

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

    Til de typiske oppgavene til mønsteret la jeg til muligheten til å blokkere en variabel fra endringer, dette er veldig praktisk på store prosjekter, du vil ikke ved et uhell sette inn noe. For eksempel praktisk for å jobbe med databaser
    define('DB_DNS', 'mysql:host=localhost;dbname= ’);
    define('DB_USER', ' ’);
    define('DB_PASSWORD', ' ’);
    define('DB_HANDLE');

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

    Nå for en forklaring av koden, for å lagre data, bruker vi den statiske variabelen $data, $lock-variabelen lagrer data om nøkler som er låst for endring. I nettverket sjekker vi om variabelen er låst og endrer eller legger den til i registeret. Ved sletting sjekker vi også låsen; getteren forblir uendret, med unntak av standard valgfri parameter. Vel, det er verdt å ta hensyn til unntakshåndtering, som av en eller annen grunn sjelden brukes. Forresten, jeg har allerede et utkast til unntak, vent på artikkelen. Nedenfor er et utkast til kode for testing, her er en artikkel om testing, det ville ikke skade å skrive det heller, selv om jeg ikke er en fan av TDD.

    I den neste artikkelen vil vi utvide funksjonaliteten ytterligere ved å legge til datainitialisering og implementere "latskap".