Problemi i inicializimit të objektit në aplikacionet OOP në PHP. Gjetja e një zgjidhjeje duke përdorur modelet e regjistrit, metodës së fabrikës, vendndodhjes së shërbimit dhe injektimit të varësisë. Modele OOP me shembuj dhe përshkrime Regjistër autoritar php

Problemi i inicializimit të objektit në aplikacionet OOP në PHP. Gjetja e një zgjidhjeje duke përdorur modelet e regjistrit, metodës së fabrikës, lokalizuesit të shërbimit dhe injektimit të varësisë

Ndodh që programuesit të konsolidojnë zgjidhje të suksesshme në formën e modeleve të projektimit. Ka shumë literaturë për modelet. Libri i Gang of Four "Design Patterns" nga Erich Gamma, Richard Helm, Ralph Johnson dhe John Vlissides" dhe, ndoshta, "Modelet e Arkitekturës së Aplikimit të Ndërmarrjeve" nga Martin Fowler sigurisht që konsiderohen klasikë. Gjëja më e mirë që kam lexuar me shembuj në PHP - kjo. Ndodh që e gjithë kjo literaturë është mjaft komplekse për njerëzit që sapo kanë filluar të zotërojnë OOP. Kështu që më lindi ideja të paraqes disa nga modelet që më duken më të dobishme në një formë shumë të thjeshtuar. Në të tjera fjalë, ky artikull është përpjekja ime e parë për të interpretuar modelet e dizajnit në stilin KISS.
Sot do të flasim për problemet që mund të lindin me inicializimin e objekteve në një aplikacion OOP dhe si mund të përdorni disa modele të njohura të dizajnit për të zgjidhur këto probleme.

Shembull

Një aplikacion modern OOP punon me dhjetëra, qindra dhe ndonjëherë mijëra objekte. Epo, le të hedhim një vështrim më të afërt se si inicializohen këto objekte në aplikacionet tona. Inicializimi i objektit është i vetmi aspekt që na intereson në këtë artikull, kështu që vendosa të heq të gjithë zbatimin "ekstra".
Le të themi se kemi krijuar një klasë të dobishme super-duper që mund të dërgojë një kërkesë GET në një URI specifike dhe të kthejë HTML nga përgjigja e serverit. Në mënyrë që klasa jonë të mos duket shumë e thjeshtë, le të kontrollojë gjithashtu rezultatin dhe të bëjë një përjashtim nëse serveri përgjigjet "gabimisht".

Klasa Grabber (funksioni publik get($url) (/** kthen kodin HTML ose hedh një përjashtim */) )

Le të krijojmë një klasë tjetër, objektet e së cilës do të jenë përgjegjëse për filtrimin e HTML-së së marrë. Metoda e filtrit merr kodin HTML dhe një përzgjedhës CSS si argumente, dhe kthen një grup elementësh të gjetur për përzgjedhësin e dhënë.

Klasa HtmlExtractor ( filtri i funksionit publik ($html, $zgjedhësi) (/** kthen grup elementësh të filtruar */) )

Tani, imagjinoni se duhet të marrim rezultate kërkimi në Google për fjalë kyçe të dhëna. Për ta bërë këtë, ne do të prezantojmë një klasë tjetër që do të përdorë klasën Grabber për të dërguar një kërkesë dhe klasën HtmlExtractor për të nxjerrë përmbajtjen e nevojshme. Ai gjithashtu do të përmbajë logjikën për ndërtimin e URI-së, një përzgjedhës për filtrimin e HTML-së së marrë dhe përpunimin e rezultateve të marra.

Klasa GoogleFinder ( $grabber privat; $filter privat; funksioni publik __construct() ( $this->grabber = new Grabber(); $this->filter = i ri HtmlExtractor(); ) funksioni publik find($searchString) ( /* * kthen grupin e rezultateve të themeluara */) )

A e keni vënë re se inicializimi i objekteve Grabber dhe HtmlExtractor është në konstruktorin e klasës GoogleFinder? Le të mendojmë se sa i mirë është ky vendim.
Natyrisht, kodimi i fortë i krijimit të objekteve në një konstruktor nuk është një ide e mirë. Dhe kjo është arsyeja pse. Së pari, ne nuk do të jemi në gjendje të anashkalojmë lehtësisht klasën Grabber në mjedisin e testimit për të shmangur dërgimin e një kërkese reale. Për të qenë të drejtë, ia vlen të thuhet se kjo mund të bëhet duke përdorur API-në Reflection. Ato. ekziston mundësia teknike, por kjo është larg nga mënyra më e përshtatshme dhe e dukshme.
Së dyti, i njëjti problem do të lindë nëse duam të ripërdorim logjikën e GoogleFinder me implementime të tjera Grabber dhe HtmlExtractor. Krijimi i varësive është i koduar në konstruktorin e klasës. Dhe në rastin më të mirë, ne do të jemi në gjendje të trashëgojmë GoogleFinder dhe të anashkalojmë konstruktorin e tij. Dhe edhe atëherë, vetëm nëse objekti i vetive të rrëmbyesit dhe filtrit është i mbrojtur ose publik.
Një pikë e fundit, sa herë që krijojmë një objekt të ri GoogleFinder, një palë e re e objekteve të varësisë do të krijohen në memorie, megjithëse mund të përdorim mjaft lehtë një objekt Grabber dhe një objekt HtmlExtractor në disa objekte GoogleFinder.
Unë mendoj se ju tashmë e kuptoni se inicializimi i varësisë duhet të zhvendoset jashtë klasës. Mund të kërkojmë që varësitë e përgatitura tashmë t'i kalojnë konstruktorit të klasës GoogleFinder.

Klasa GoogleFinder ( $grabber privat; $filter privat; funksioni publik __construct(Grabber $grabber, HtmlExtractor $filter) ( $this->grabber = $grabber; $this->filter = $filter; ) funksioni publik gjeni ($searchString) ( /** kthen grupin e rezultateve të themeluara */) )

Nëse duam t'u japim zhvilluesve të tjerë mundësinë për të shtuar dhe përdorur implementimet e tyre Grabber dhe HtmlExtractor, atëherë duhet të konsiderojmë prezantimin e ndërfaqeve për ta. Në këtë rast, kjo nuk është vetëm e dobishme, por edhe e nevojshme. Unë besoj se nëse përdorim vetëm një zbatim në një projekt dhe nuk presim të krijojmë të reja në të ardhmen, atëherë duhet të refuzojmë të krijojmë një ndërfaqe. Është më mirë të veprosh sipas situatës dhe të bësh rifaktorim të thjeshtë kur ka realisht nevojë për të.
Tani kemi të gjitha klasat e nevojshme dhe mund të përdorim klasën GoogleFinder në kontrollues.

Kontrolluesi i klasës ( veprim i funksionit publik() ( /* Disa gjëra */ $finder = GoogleFinder i ri(Grabber i ri(), HtmlExtractor i ri()); $rezultat = $finder->

Le të përmbledhim rezultatet e ndërmjetme. Shkruam shumë pak kod dhe në shikim të parë nuk bëmë asgjë të keqe. Por... çka nëse na duhet të përdorim një objekt si GoogleFinder në një vend tjetër? Ne do të duhet të dyfishojmë krijimin e tij. Në shembullin tonë, kjo është vetëm një rresht dhe problemi nuk është aq i dukshëm. Në praktikë, inicializimi i objekteve mund të jetë mjaft kompleks dhe mund të marrë deri në 10 rreshta, ose edhe më shumë. Gjithashtu lindin probleme të tjera tipike të dyfishimit të kodit. Nëse gjatë procesit të rifaktorimit ju duhet të ndryshoni emrin e klasës së përdorur ose logjikën e inicializimit të objektit, do t'ju duhet të ndryshoni manualisht të gjitha vendet. Unë mendoj se ju e dini se si ndodh :)
Zakonisht, kodi i ngurtë trajtohet thjesht. Vlerat e kopjuara zakonisht përfshihen në konfigurim. Kjo ju lejon të ndryshoni vlerat në qendër në të gjitha vendet ku ato përdoren.

Modeli i regjistrit.

Pra, vendosëm të zhvendosim krijimin e objekteve në konfigurim. Le ta bejme ate.

$regjistri = ArrayObject i ri(); $registry["grabber"] = i ri Grabber(); $registry["filter"] = i ri HtmlExtractor(); $registry["google_finder"] = GoogleFinder i ri ($registry["grabber"], $registry["filter"]);
Gjithçka që duhet të bëjmë është të kalojmë ArrayObject tonë te kontrolluesi dhe problemi zgjidhet.

Kontrolluesi i klasës ( $registry privat; funksioni publik __construct(ArrayObject $registry) ( $this->registry = $registry; ) veprimi i funksionit publik() ( /* Disa gjëra */ $results = $this->registry["google_finder" ]-> find("search string"); /* Bëj diçka me rezultatet */ ) )

Mund ta zhvillojmë më tej idenë e Regjistrit. Trashëgoni ArrayObject, kapsuloni krijimin e objekteve brenda një klase të re, ndaloni shtimin e objekteve të reja pas inicializimit, etj. Por për mendimin tim, kodi i dhënë e bën plotësisht të qartë se çfarë është shablloni i Regjistrit. Ky model nuk është gjenerues, por shkon në një farë mënyre për të zgjidhur problemet tona. Regjistri është vetëm një kontejner në të cilin ne mund të ruajmë objekte dhe t'i kalojmë ato brenda aplikacionit. Në mënyrë që objektet të bëhen të disponueshme, fillimisht duhet t'i krijojmë ato dhe t'i regjistrojmë në këtë kontejner. Le të shohim avantazhet dhe disavantazhet e kësaj qasjeje.
Në pamje të parë, ne ia kemi arritur qëllimit. Ne ndaluam kodimin e emrave të klasave dhe krijuam objekte në një vend. Ne krijojmë objekte në një kopje të vetme, e cila garanton ripërdorimin e tyre. Nëse logjika për krijimin e objekteve ndryshon, atëherë vetëm një vend në aplikacion do të duhet të modifikohet. Si bonus, ne morëm mundësinë për të menaxhuar në mënyrë qendrore objektet në Regjistr. Mund të marrim lehtësisht një listë të të gjitha objekteve të disponueshme dhe të kryejmë disa manipulime me to. Le të shohim tani se çfarë mund të mos na pëlqejë në lidhje me këtë shabllon.
Së pari, ne duhet të krijojmë objektin përpara se ta regjistrojmë atë në Regjistrin. Prandaj, ekziston një probabilitet i lartë për të krijuar "objekte të panevojshme", d.m.th. ato që do të krijohen në memorie, por nuk do të përdoren në aplikacion. Po, ne mund të shtojmë objekte në Regjistrim në mënyrë dinamike, d.m.th. krijoni vetëm ato objekte që nevojiten për të përpunuar një kërkesë specifike. Në një mënyrë apo tjetër, ne do të duhet ta kontrollojmë këtë me dorë. Prandaj, me kalimin e kohës do të bëhet shumë e vështirë për t'u ruajtur.
Së dyti, ne kemi një varësi të re të kontrolluesit. Po, ne mund të marrim objekte përmes një metode statike në Registry në mënyrë që të mos na duhet t'ia kalojmë Regjistrin konstruktorit. Por për mendimin tim, ju nuk duhet ta bëni këtë. Metodat statike janë një lidhje edhe më e ngushtë sesa krijimi i varësive brenda një objekti dhe vështirësitë në testim (në këtë temë).
Së treti, ndërfaqja e kontrolluesit nuk na tregon asgjë për objektet që përdor. Ne mund të marrim çdo objekt të disponueshëm në Regjistrin në kontrollues. Do të jetë e vështirë për ne të themi se cilat objekte përdor kontrolluesi derisa të kontrollojmë të gjithë kodin e tij burimor.

Metoda e fabrikës

Problemi ynë më i madh me Regjistrin është se një objekt duhet të inicializohet përpara se të mund të aksesohet. Në vend që të inicializojmë një objekt në konfigurim, ne mund ta ndajmë logjikën për krijimin e objekteve në një klasë tjetër, të cilën mund ta "kërkojmë" të ndërtojë objektin që na nevojitet. Klasat që janë përgjegjëse për krijimin e objekteve quhen fabrika. Dhe modeli i dizajnit quhet Metoda e Fabrikës. Le të shohim një fabrikë shembull.

Fabrika e klasës ( funksioni publik getGoogleFinder() ( ktheni GoogleFinder të ri($this->getGrabber(), $this->getHtmlExtractor()); ) funksioni privat getGrabber() (kthimi i ri Grabber(); ) funksioni privat getHtmlExtractor() ( ktheni HtmlFiletr (); ) )

Si rregull, bëhen fabrika që janë përgjegjëse për krijimin e një lloji objekti. Ndonjëherë një fabrikë mund të krijojë një grup objektesh të lidhura. Ne mund të përdorim caching në një pronë për të shmangur rikrijimin e objekteve.

Fabrika e klasës ( $finder privat; funksioni publik getGoogleFinder() ( nëse (null === $this->finder) ( $this->finder = GoogleFinder i ri($this->getGrabber(), $this->getHtmlExtractor() ); ) ktheni $this->finder; ))

Ne mund të parametrizojmë një metodë fabrike dhe të delegojmë inicializimin në fabrika të tjera në varësi të parametrit në hyrje. Ky do të jetë tashmë një shabllon i Fabrikës Abstrakte.
Nëse duhet të modularizojmë aplikacionin, mund të kërkojmë që secili modul të sigurojë fabrikat e veta. Ne mund të zhvillojmë më tej temën e fabrikave, por mendoj se thelbi i këtij shablloni është i qartë. Le të shohim se si do ta përdorim fabrikën në kontrollues.

Kontrolluesi i klasës ( $factory privat; funksioni publik __construct(Fabrika $fabrika) ( $this->factory = $factory; ) veprimi i funksionit publik() ( /* Disa gjëra */ $results = $this->factory->getGoogleFinder( )-> find("search string"); /* Bëj diçka me rezultatet */ ) )

Përparësitë e kësaj qasje përfshijnë thjeshtësinë e saj. Objektet tona janë krijuar në mënyrë eksplicite dhe IDE-ja juaj do t'ju çojë lehtësisht në vendin ku ndodh kjo. Ne kemi zgjidhur gjithashtu problemin e Regjistrit në mënyrë që objektet në memorie të krijohen vetëm kur "i kërkojmë" fabrikës ta bëjë këtë. Por ne nuk kemi vendosur ende se si t'i furnizojmë kontrollorët fabrikat e nevojshme. Këtu ka disa opsione. Ju mund të përdorni metoda statike. Ne mund t'i lejojmë kontrollorët të krijojnë vetë fabrikat e nevojshme dhe të anulojnë të gjitha përpjekjet tona për të hequr qafe copy-paste. Ju mund të krijoni një fabrikë fabrikash dhe t'ia kaloni vetëm atë kontrolluesit. Por marrja e objekteve në kontrollues do të bëhet pak më e ndërlikuar dhe do t'ju duhet të menaxhoni varësitë midis fabrikave. Përveç kësaj, nuk është plotësisht e qartë se çfarë të bëjmë nëse duam të përdorim module në aplikacionin tonë, si të regjistrojmë fabrikat e moduleve, si të menaxhojmë lidhjet midis fabrikave nga module të ndryshme. Në përgjithësi, ne kemi humbur avantazhin kryesor të fabrikës - krijimin e qartë të objekteve. Dhe ne ende nuk e kemi zgjidhur problemin e ndërfaqes së kontrolluesit "të nënkuptuar".

Gjetësi i shërbimit

Modeli i Shërbimit Locator ju lejon të zgjidhni mungesën e fragmentimit të fabrikave dhe të menaxhoni krijimin e objekteve në mënyrë automatike dhe qendrore. Nëse mendojmë për këtë, mund të prezantojmë një shtresë shtesë abstraksioni që do të jetë përgjegjëse për krijimin e objekteve në aplikacionin tonë dhe menaxhimin e marrëdhënieve midis këtyre objekteve. Në mënyrë që kjo shtresë të jetë në gjendje të krijojë objekte për ne, ne do të duhet t'i japim asaj njohuri se si ta bëjmë këtë.
Kushtet e modelit të Lokatorit të Shërbimit:
  • Shërbimi është një objekt i gatshëm që mund të merret nga një kontejner.
  • Përkufizimi i Shërbimit – logjika e inicializimit të shërbimit.
  • Një kontejner (Service Container) është një objekt qendror që ruan të gjitha përshkrimet dhe mund të krijojë shërbime në bazë të tyre.
Çdo modul mund të regjistrojë përshkrimet e tij të shërbimit. Për të marrë një shërbim nga kontejneri, do të duhet ta kërkojmë me çelës. Ka shumë opsione për zbatimin e Shërbimit Locator; në versionin më të thjeshtë, ne mund të përdorim ArrayObject si një kontejner dhe një mbyllje si përshkrim të shërbimeve.

Klasa ServiceContainer zgjeron ArrayObject ( funksioni publik get($key) ( if (is_callable($this[$key]))) (ktheje call_user_func($this[$key]); ) hedh të ri \RuntimeException("Nuk mund të gjendet përkufizimi i shërbimit nën çelësi [ $key ]"); ))

Pastaj regjistrimi i Përkufizimeve do të duket kështu:

$container = New ServiceContainer(); $container["grabber"] = funksion () (ktheje Grabber të ri(); ); $container["html_filter"] = funksioni () ( ktheni HtmlExtractor (); ); $container["google_finder"] = funksioni() use ($container) (ktheje GoogleFinder të ri($container->get("grabber"), $container->get("html_filter")); );

Dhe përdorimi në kontrollues është si ky:

Kontrolluesi i klasës ( $container privat; funksioni publik __construct(ServiceContainer $container) ( $this->container = $container; ) veprimi i funksionit publik() ( /* Disa gjëra */ $results = $this->container->get( "google_finder")->find("search string"); /* Bëj diçka me rezultatet */ ) )

Një kontejner shërbimi mund të jetë shumë i thjeshtë, ose mund të jetë shumë kompleks. Për shembull, Symfony Service Container ofron shumë veçori: parametra, shtrirje shërbimesh, kërkim të shërbimeve sipas etiketave, pseudonimeve, shërbime private, aftësinë për të bërë ndryshime në kontejner pas shtimit të të gjitha shërbimeve (kalime përpiluesi) dhe shumë më tepër. DIExtraBundle zgjeron më tej aftësitë e zbatimit standard.
Por le të kthehemi te shembulli ynë. Siç mund ta shihni, Shërbimi Locator jo vetëm që zgjidh të njëjtat probleme si shabllonet e mëparshme, por gjithashtu e bën të lehtë përdorimin e moduleve me përkufizimet e tyre të shërbimit.
Përveç kësaj, në nivelin e kornizës kemi marrë një nivel shtesë të abstraksionit. Gjegjësisht, duke ndryshuar metodën ServiceContainer::get ne, për shembull, mund ta zëvendësojmë objektin me një proxy. Dhe fushëveprimi i aplikimit të objekteve proxy është i kufizuar vetëm nga imagjinata e zhvilluesit. Këtu mund të zbatoni paradigmën AOP, LazyLoading, etj.
Por shumica e zhvilluesve ende e konsiderojnë Shërbimin Locator një anti-model. Sepse, në teori, mund të kemi sa më shumë të ashtuquajturat Klasat Container Aware (d.m.th. klasa që përmbajnë një referencë për kontejnerin). Për shembull, Kontrolluesi ynë, brenda të cilit mund të marrim çdo shërbim.
Le të shohim pse kjo është e keqe.
Së pari, testimi përsëri. Në vend që të krijoni tallje vetëm për klasat e përdorura në teste, do t'ju duhet të tallni të gjithë kontejnerin ose të përdorni një enë të vërtetë. Opsioni i parë nuk ju përshtatet, sepse... ju duhet të shkruani shumë kode të panevojshme në teste, së dyti, sepse ai bie ndesh me parimet e testimit të njësisë dhe mund të çojë në kosto shtesë për mirëmbajtjen e testeve.
Së dyti, do të jetë e vështirë për ne të rifaktojmë. Duke ndryshuar çdo shërbim (ose Definition Shërbimi) në kontejner, ne do të detyrohemi të kontrollojmë gjithashtu të gjitha shërbimet e varura. Dhe ky problem nuk mund të zgjidhet me ndihmën e një IDE. Gjetja e vendeve të tilla në të gjithë aplikacionin nuk do të jetë aq e lehtë. Përveç shërbimeve të varura, do t'ju duhet gjithashtu të kontrolloni të gjitha vendet ku merret shërbimi i rifaktoruar nga kontejneri.
Epo, arsyeja e tretë është se tërheqja e pakontrolluar e shërbimeve nga kontejneri do të çojë herët a vonë në një rrëmujë në kod dhe konfuzion të panevojshëm. Kjo është e vështirë për t'u shpjeguar, thjesht do t'ju duhet të shpenzoni gjithnjë e më shumë kohë për të kuptuar se si funksionon ky apo ai shërbim, me fjalë të tjera, mund të kuptoni plotësisht se çfarë bën ose si funksionon një klasë vetëm duke lexuar të gjithë kodin burimor.

Injeksioni i varësisë

Çfarë tjetër mund të bëni për të kufizuar përdorimin e një kontejneri në një aplikacion? Ju mund të transferoni kontrollin e krijimit të të gjitha objekteve të përdoruesit, përfshirë kontrolluesit, në kornizë. Me fjalë të tjera, kodi i përdoruesit nuk duhet të thërrasë metodën e marrjes së kontejnerit. Në shembullin tonë, ne mund të shtojmë një Përkufizim për kontrolluesin në kontejner:

$container["google_finder"] = funksioni() use ($container) (ktheje Kontrolluesin e ri(Grabber $grabber); );

Dhe hiqni qafe enën në kontrollues:

Kontrolluesi i klasës ( $finder privat; funksioni publik __construct(GoogleFinder $finder) ( $this->finder = $finder; ) veprimi i funksionit publik() ( /* Disa gjëra */ $results = $this->finder->find( "string i kërkimit"); /* Bëni diçka me rezultate */ ))

Kjo qasje (kur qasja në Kontejnerin e Shërbimit nuk u ofrohet klasave të klientit) quhet Injeksioni i Varësisë. Por ky shabllon ka gjithashtu avantazhe dhe disavantazhe. Për sa kohë që ne i përmbahemi parimit të përgjegjësisë së vetme, kodi duket shumë i bukur. Para së gjithash, ne hoqëm qafe kontejnerin në klasat e klientëve, duke e bërë kodin e tyre shumë më të qartë dhe më të thjeshtë. Ne mund ta testojmë lehtësisht kontrolluesin duke zëvendësuar varësitë e nevojshme. Ne mund të krijojmë dhe testojmë çdo klasë në mënyrë të pavarur nga të tjerët (duke përfshirë klasat e kontrolluesve) duke përdorur një qasje TDD ose BDD. Kur krijojmë teste, ne mund të abstragojmë nga kontejneri dhe më vonë të shtojmë një Përkufizim kur duhet të përdorim shembuj të veçantë. E gjithë kjo do ta bëjë kodin tonë më të thjeshtë dhe më të qartë, dhe testimin më transparent.
Por është e nevojshme të përmendet ana tjetër e medaljes. Fakti është se kontrollorët janë klasa shumë specifike. Le të fillojmë me faktin se kontrolluesi, si rregull, përmban një sërë veprimesh, që do të thotë se shkel parimin e përgjegjësisë së vetme. Si rezultat, klasa e kontrolluesit mund të ketë shumë më tepër varësi sesa janë të nevojshme për të ekzekutuar një veprim specifik. Përdorimi i inicializimit dembel (objekti instantohet në kohën e përdorimit të tij të parë, dhe para kësaj përdoret një përfaqësues i lehtë) zgjidh çështjen e performancës në një farë mase. Por nga pikëpamja arkitekturore, krijimi i shumë varësive nga një kontrollues nuk është gjithashtu plotësisht i saktë. Përveç kësaj, testimi i kontrollorëve është zakonisht një operacion i panevojshëm. Gjithçka, natyrisht, varet nga mënyra se si është organizuar testimi në aplikacionin tuaj dhe nga mënyra se si ndiheni për të.
Nga paragrafi i mëparshëm, keni kuptuar se përdorimi i Dependency Injection nuk eliminon plotësisht problemet arkitekturore. Prandaj, mendoni se si do të jetë më i përshtatshëm për ju, nëse do të ruani një lidhje me kontejnerin në kontrollues apo jo. Këtu nuk ka asnjë zgjidhje të vetme të saktë. Unë mendoj se të dyja qasjet janë të mira për sa kohë që kodi i kontrolluesit mbetet i thjeshtë. Por, definitivisht, nuk duhet të krijoni shërbime Conatiner Aware përveç kontrollorëve.

konkluzionet

Epo, ka ardhur koha për të përmbledhur gjithçka që është thënë. Dhe shumë është thënë... :)
Pra, për të strukturuar punën e krijimit të objekteve, mund të përdorim modelet e mëposhtme:
  • Regjistri: Shablloni ka disavantazhe të dukshme, më themelore prej të cilave është nevoja për të krijuar objekte përpara se t'i vendosni në një enë të përbashkët. Natyrisht, ne do të kemi më shumë probleme sesa përfitime nga përdorimi i tij. Ky nuk është qartë përdorimi më i mirë i shabllonit.
  • Metoda e fabrikës: Avantazhi kryesor i modelit: objektet krijohen në mënyrë eksplicite. Disavantazhi kryesor: kontrollorët ose duhet të shqetësohen për krijimin e vetë fabrikave, gjë që nuk e zgjidh plotësisht problemin e kodimit të emrave të klasave, ose korniza duhet të jetë përgjegjëse për t'u siguruar kontrolluesve të gjitha fabrikat e nevojshme, të cilat nuk do të jenë aq të dukshme. Nuk ka mundësi për të menaxhuar në mënyrë qendrore procesin e krijimit të objekteve.
  • Gjetësi i shërbimit: Një mënyrë më e avancuar për të kontrolluar krijimin e objekteve. Një nivel shtesë abstraksioni mund të përdoret për të automatizuar detyrat e zakonshme që hasen gjatë krijimit të objekteve. Për shembull:
    Klasa ServiceContainer zgjeron ArrayObject ( funksioni publik get($key) ( if (is_callable($this[$key]))) ($obj = call_user_func($this[$key]); if ($obj instanceof RequestAwareInterface) ( $obj- >setRequest($this->get("kërkesë")); ) kthe $obj; ) hedh të ri \RuntimeException("Nuk mund të gjej përkufizimin e shërbimit nën çelësin [ $key ]"); ) )
    Disavantazhi i Shërbimit Locator është se API publike e klasave pushon së qeni informues. Është e nevojshme të lexohet i gjithë kodi i klasës për të kuptuar se cilat shërbime përdoren në të. Një klasë që përmban një referencë për një kontejner është më e vështirë për t'u testuar.
  • Injeksioni i varësisë: Në thelb ne mund të përdorim të njëjtin kontejner shërbimi si për modelin e mëparshëm. Dallimi është se si përdoret kjo enë. Nëse shmangim varësinë e klasave nga kontejneri, do të marrim një API të qartë dhe të qartë të klasës.
Kjo nuk është gjithçka që do të doja t'ju tregoja për problemin e krijimit të objekteve në aplikacionet PHP. Ekziston edhe modeli Prototip, ne nuk e morëm parasysh përdorimin e API-së Reflection, lamë mënjanë problemin e ngarkimit dembel të shërbimeve dhe shumë nuanca të tjera. Artikulli doli të ishte mjaft i gjatë, kështu që unë do ta përfundoj :)
Doja të tregoja se Injeksioni i Varësisë dhe modelet e tjera nuk janë aq të ndërlikuara sa besohet zakonisht.
Nëse flasim për Dependency Injection, atëherë ka zbatime KISS të këtij modeli, për shembull

Duke prekur strukturën e bazës së të dhënave të ardhshme. Është bërë një fillim dhe ne nuk mund të tërhiqemi dhe as që e mendoj.

Ne do të kthehemi në bazën e të dhënave pak më vonë, por tani për tani do të fillojmë të shkruajmë kodin për motorin tonë. Por së pari, pak harduer. Filloni.

Fillimi i kohes

Për momentin, ne kemi vetëm disa ide dhe mirëkuptim për funksionimin e sistemit që duam t'i zbatojmë, por nuk ka ende zbatim vetë. Ne nuk kemi asgjë për të punuar: ne nuk kemi asnjë funksionalitet - dhe, siç e mbani mend, e kemi ndarë në 2 pjesë: të brendshme dhe të jashtme. Alfabeti kërkon shkronja, por funksionaliteti i jashtëm kërkon funksionalitet të brendshëm - këtu do të fillojmë.

Por jo aq shpejt. Që të funksionojë, duhet të shkoni pak më thellë. Sistemi ynë përfaqëson një hierarki dhe çdo sistem hierarkik ka një fillim: një pikë montimi në Linux, një disk lokal në Windows, një sistem të një shteti, një kompani, një institucion arsimor, etj. Çdo element i një sistemi të tillë është në varësi të dikujt dhe mund të ketë disa vartës, dhe për t'iu drejtuar fqinjëve dhe vartësve të tyre ai përdor eprorët ose vetë fillimin. Një shembull i mirë i një sistemi hierarkik është një pemë familjare: zgjidhet një pikënisje - një paraardhës - dhe ne ikim. Në sistemin tonë, na duhet gjithashtu një pikënisje nga e cila do të rritim degët - module, shtojca, etj. Ne kemi nevojë për një lloj ndërfaqeje përmes së cilës të gjitha modulet tona do të "komunikojnë". Për punë të mëtejshme, duhet të njihemi me konceptin " model dizajni" dhe disa nga zbatimet e tyre.

Modelet e Dizajnit

Ka shumë artikuj rreth asaj se çfarë është dhe çfarë varietetesh ekzistojnë; tema është mjaft e çuditshme dhe nuk do t'ju them asgjë të re. Në Wiki tim të preferuar ka informacione për këtë temë: një karrocë me një rrëshqitje dhe pak më shumë.

Modelet e dizajnit shpesh quhen gjithashtu modele dizajni ose thjesht modele (nga fjala angleze model, e përkthyer që do të thotë "model"). Më tej në artikuj, kur flas për modelet, do të nënkuptoj modelet e dizajnit.

Nga lista e madhe e të gjitha llojeve të emrave të modeleve të frikshëm (dhe jo aq të frikshëm), ne jemi të interesuar vetëm për dy deri më tani: regjistri dhe singleton.

Regjistri (ose regjistrohu) është një model që funksionon në një grup të caktuar në të cilin mund të shtoni dhe hiqni një grup të caktuar objektesh dhe të fitoni akses në cilindo prej tyre dhe aftësitë e tij.

I vetmuar (ose teke) është një model që siguron që vetëm një shembull i një klase mund të ekzistojë. Nuk mund të kopjohet, të vihet në gjumë ose të zgjohet (duke folur për magjinë PHP: __clone(), __sleep(), __wakeup()). Singleton ka një pikë aksesi global.

Përkufizimet nuk janë të plota apo të përgjithësuara, por kjo mjafton për t'u kuptuar. Gjithsesi nuk na duhen veçmas. Ne jemi të interesuar për aftësitë e secilit prej këtyre modeleve, por në një klasë: një model i tillë quhet regjistri singleton ose Regjistri Singleton.

Çfarë do të na japë kjo?
  • Ne do të garantojmë të kemi një shembull të vetëm të regjistrit, në të cilin mund të shtojmë objekte në çdo kohë dhe t'i përdorim ato nga kudo në kod;
  • do të jetë e pamundur ta kopjoni atë dhe të përdorni magji të tjera të padëshiruara (në këtë rast) të gjuhës PHP.

Në këtë fazë, mjafton të kuptojmë se një regjistër i vetëm do të na lejojë të zbatojmë një strukturë modulare të sistemit, gjë që ne donim kur diskutonim qëllimet në , dhe pjesën tjetër do ta kuptoni ndërsa zhvillimi përparon.

Epo, mjaft fjalë, le të krijojmë!

Linjat e para

Meqenëse kjo klasë do të lidhet me funksionalitetin e kernelit, ne do të fillojmë duke krijuar një dosje në rrënjën e projektit tonë të quajtur core në të cilën do të vendosim të gjitha klasat e moduleve të kernelit. Fillojmë me regjistrin, kështu që le ta quajmë skedarin registry.php

Ne nuk jemi të interesuar për mundësinë që një përdorues kurioz të futë një adresë të drejtpërdrejtë të skedarit tonë në linjën e shfletuesit, kështu që ne duhet të mbrohemi nga kjo. Për të arritur këtë qëllim, mjafton të përcaktojmë një konstante të caktuar në skedarin kryesor të ekzekutueshëm, të cilin do ta kontrollojmë. Ideja nuk është e re; me sa mbaj mend, është përdorur në Joomla. Kjo është një metodë e thjeshtë dhe funksionale, kështu që ne mund të bëjmë pa biçikleta këtu.

Meqenëse po mbrojmë diçka që është e lidhur, ne do ta quajmë konstanten _PLUGSECURE_:

Nëse (!defined("_PLUGSECURE_")) ( die("Thirrja e drejtpërdrejtë e modulit është e ndaluar!"); )

Tani, nëse përpiqeni të hyni drejtpërdrejt në këtë skedar, asgjë e dobishme nuk do të dalë, që do të thotë se qëllimi është arritur.

Më pas, unë propozoj të përcaktojmë një standard të caktuar për të gjitha modulet tona. Unë dua t'i siguroj secilit modul një funksion që do të kthejë disa informacione rreth tij, siç është emri i modulit, dhe ky funksion duhet të kërkohet në klasë. Për të arritur këtë qëllim shkruajmë sa vijon:

Ndërfaqja StorableObject (funksioni statik publik getClassName();

Si kjo. Tani, nëse lidhim ndonjë klasë pa funksion getClassName() do të shohim një mesazh gabimi. Nuk do të përqendrohem në këtë tani për tani, do të jetë e dobishme për ne më vonë, të paktën për testimin dhe korrigjimin e gabimeve.

Është koha për vetë klasën e regjistrit tonë beqarë. Ne do të fillojmë duke deklaruar klasën dhe disa nga variablat e saj:

Regjistri i klasës implementon StorableObject ( //emri i modulit i lexueshëm privat static $className = "Registry"; //instancë e regjistrit private static $instance; //array objektesh private static $objects = array();

Deri këtu gjithçka është logjike dhe e kuptueshme. Tani, siç e mbani mend, ne kemi një regjistër me vetitë singleton, kështu që le të shkruajmë menjëherë një funksion që do të na lejojë të punojmë me regjistrin në këtë mënyrë:

Funksioni publik statik singleton() ( if(!isset(self::$instance)) ( $obj = __CLASS__; vet::$instance = new $obj; ) kthen veten::$instance; )

Fjalë për fjalë: funksioni kontrollon nëse ekziston një shembull i regjistrit tonë: nëse jo, ai e krijon atë dhe e kthen; nëse ekziston tashmë, thjesht e kthen atë. Në këtë rast, ne nuk kemi nevojë për ndonjë magji, kështu që për mbrojtje do ta deklarojmë atë private:

Funksioni privat __construct()() funksioni privat __clone()() funksioni privat __wakeup()() funksioni privat __sleep() ()

Tani na duhet një funksion për të shtuar një objekt në regjistrin tonë - ky funksion quhet vendosës dhe vendosa ta zbatoj në dy mënyra për të treguar se si mund të përdorim magjinë dhe të ofrojmë një mënyrë alternative për të shtuar një objekt. Metoda e parë është një funksion standard, e dyta ekzekuton të parin përmes magjisë së __set().

//$object - shteg drejt objektit të lidhur //$key - çelësi i hyrjes në objektin në funksionin publik të regjistrit addObject($key, $object) ( require_once($object); //krijoni një objekt në një grup objektesh self::$objects[ $key] = i ri $key(self::$instance); ) //një metodë alternative përmes funksionit magjik publik __set($key, $object) ( $this->addObject($key, $ Objekt); )

Tani, për të shtuar një objekt në regjistrin tonë, ne mund të përdorim dy lloje hyrjesh (le të themi se kemi krijuar tashmë një shembull regjistri $registry dhe duam të shtojmë skedarin config.php):

$registry->addObject("config", "/core/config.php"); //metodë e rregullt $registry->config = "/core/config.php"; //përmes funksionit magjik PHP __set()

Të dy hyrjet do të kryejnë të njëjtin funksion - ata do të lidhin skedarin, do të krijojnë një shembull të klasës dhe do ta vendosin atë në regjistër me çelës. Ekziston një pikë e rëndësishme këtu, nuk duhet ta harrojmë në të ardhmen: çelësi i objektit në regjistër duhet të përputhet me emrin e klasës në objektin e lidhur. Nëse shikoni përsëri kodin, do ta kuptoni pse.

Cili regjistrim të përdorni varet nga ju. Unë preferoj regjistrimin përmes metodës magjike - është "më e bukur" dhe më e shkurtër.

Pra, ne kemi renditur shtimin e një objekti, tani na duhet një funksion për të hyrë në një objekt të lidhur me çelës - një marrës. Unë gjithashtu e zbatova atë me dy funksione, të ngjashme me vendosësin:

//merr objektin nga regjistri //$key - çelësi në funksionin publik të grupit getObject($key) ( //kontrollo nëse ndryshorja është objekt nëse (is_object(self::$objects[$key])) ( //nëse po, atëherë ne e kthejmë këtë objekt që kthen veten::$objektet[$key];) ) //një metodë e ngjashme përmes funksionit magjik publik __get($key) ( if (is_object(self::$objects[$ çelësi])) (ktheje veten: :$objects[$key]; ) )

Ashtu si me vendosësin, për të fituar akses në objekt do të kemi 2 hyrje ekuivalente:

$registry->getObject("config"); //metoda e rregullt $registry->config; //përmes funksionit magjik PHP __get()

Lexuesi i vëmendshëm do të bëjë menjëherë pyetjen: pse në funksionin magjik __set() thjesht thërras një funksion të shtimit të objektit të rregullt (jo magjik), por në marrësin __get() kopjoj kodin e funksionit getObject() në vend të të njëjtës thirrje? Sinqerisht, nuk mund t'i përgjigjem kësaj pyetjeje me saktësi të mjaftueshme, thjesht do të them se kisha probleme kur punoja me magjinë __get() në module të tjera, por kur rishkruani kodin "me kokë" nuk ka probleme të tilla.

Ndoshta kjo është arsyeja pse shpesh kam parë në artikuj qortime ndaj metodave magjike PHP dhe këshilla për të shmangur përdorimin e tyre.

"E gjithë magjia vjen me një çmim." © Rumplestiltskin

Në këtë fazë, funksionaliteti kryesor i regjistrit tonë është tashmë gati: ne mund të krijojmë një shembull të vetëm të regjistrit, të shtojmë objekte dhe t'i qasemi atyre si duke përdorur metoda konvencionale ashtu edhe përmes metodave magjike të gjuhës PHP. "Po fshirja?"— Nuk do të kemi nevojë për këtë funksion tani për tani, dhe nuk jam i sigurt se diçka do të ndryshojë në të ardhmen. Në fund, ne gjithmonë mund të shtojmë funksionalitetin e nevojshëm. Por nëse tani përpiqemi të krijojmë një shembull të regjistrit tonë,

$registry = Regjistri::singleton();

do të marrim një gabim:

Gabim fatal: Regjistri i klasave përmban 1 metodë abstrakte dhe për këtë arsye duhet të deklarohet abstrakte ose të zbatojë metodat e mbetura (StorableObject::getClassName) në ...

Të gjitha sepse kemi harruar të shkruajmë një funksion të kërkuar. E mbani mend që në fillim fola për një funksion që kthen emrin e modulit? Kjo është ajo që mbetet për t'u shtuar për funksionalitet të plotë. Është e thjeshtë:

Funksioni statik publik getClassName() ( kthen veten::$className; )

Tani nuk duhet të ketë gabime. Unë propozoj shtimin e një funksioni tjetër, nuk kërkohet, por herët a vonë mund të jetë i dobishëm; ne do ta përdorim atë në të ardhmen për të kontrolluar dhe korrigjuar. Funksioni do të kthejë emrat e të gjitha objekteve (moduleve) të shtuara në regjistrin tonë:

Funksioni publik getObjectsList() ( // grupi që do të kthejmë $names = array(); //merrni emrin e secilit objekt nga grupi i objekteve foreach(self::$objects si $obj) ( $names = $ obj->getClassName() ;) //shtoni emrin e modulit të regjistrit në vargun array_push($names, self::getClassName()); //dhe ktheni $names;)

Kjo eshte e gjitha. Kjo plotëson regjistrin. Le të kontrollojmë punën e tij? Kur kontrollojmë, do të duhet të lidhim diçka - le të ketë një skedar konfigurimi. Krijoni një skedar të ri core/config.php dhe shtoni përmbajtjen minimale që kërkon regjistri ynë:

//mos harroni të kontrolloni konstanten if (!defined("_PLUGSECURE_")) ( die("Thirrja e drejtpërdrejtë e modulit është e ndaluar!"); ) class Config ( //emri i modulit, static privat i lexueshëm $className = "Config "; funksioni publik statik getClassName() (ktheje veten::$className; ) )

Diçka e tillë. Tani le të vazhdojmë me vetë verifikimin. Në rrënjën e projektit tonë, krijoni një skedar index.php dhe shkruani kodin e mëposhtëm në të:

Define ("_PLUGSECURE_", e vërtetë); //përcaktoi një konstante për të mbrojtur kundër aksesit të drejtpërdrejtë në objektet Kërko_once "/core/registry.php"; //lidhi regjistrin $registry = Regjistri::singleton(); //krijoi një instancë regjistri singleton $registry->config = "/core/config.php"; //lidhni konfigurimin tonë, deri tani të padobishëm, //shfaqni emrat e moduleve të lidhura echo " Lidhur"; foreach ($registry->

  • " . $names ."
  • "; }

    Ose, nëse ende shmangni magjinë, atëherë rreshti i 5-të mund të zëvendësohet me një metodë alternative:

    Define ("_PLUGSECURE_", e vërtetë); //përcaktoi një konstante për të mbrojtur kundër aksesit të drejtpërdrejtë në objektet Kërko_once "/core/registry.php"; //lidhi regjistrin $registry = Regjistri::singleton(); //krijoi një instancë regjistri singleton $registry->addObject("config", "/core/config.php"); //lidhni konfigurimin tonë, deri tani të padobishëm, //shfaqni emrat e moduleve të lidhura echo " Lidhur"; foreach ($registry->getObjectsList() si $names) (echo "

  • " . $names ."
  • "; }

    Tani hapni shfletuesin dhe shkruani http://localhost/index.php ose thjesht http://localhost/ në shiritin e adresave (e rëndësishme nëse jeni duke përdorur serverin standard të hapur ose cilësime të ngjashme të serverit në internet)

    Si rezultat, ne duhet të shohim diçka të tillë:

    Siç mund ta shihni, nuk ka gabime, që do të thotë se gjithçka funksionon, për të cilën ju përgëzoj :)

    Sot do të ndalemi në këtë. Në artikullin tjetër, do të kthehemi në bazën e të dhënave dhe do të shkruajmë një klasë për të punuar me MySQL SUDB, do ta lidhim atë me regjistrin dhe do ta testojmë punën në praktikë. Shihemi!

    Ky model, si Singleton, rrallë shkakton një reagim pozitiv nga zhvilluesit, pasi krijon të njëjtat probleme gjatë testimit të aplikacioneve. Sidoqoftë, ata qortojnë, por përdorin në mënyrë aktive. Ashtu si Singleton, modeli i Regjistrit gjendet në shumë aplikacione dhe, në një mënyrë ose në një tjetër, thjeshton shumë zgjidhjen e problemeve të caktuara.

    Le t'i shqyrtojmë të dyja opsionet me radhë.

    Ajo që quhet "regjistër i pastër" ose thjesht Regjistri është një zbatim i një klase me një ndërfaqe statike. Dallimi kryesor nga modeli Singleton është se ai bllokon aftësinë për të krijuar të paktën një shembull të një klase. Duke pasur parasysh këtë, nuk ka kuptim të fshehim metodat magjike __clone() dhe __wakeup() pas modifikuesit privat ose të mbrojtur.

    Klasa e regjistrit duhet të ketë dy metoda statike - një marrës dhe një vendosës. Vendosësi e vendos objektin e kaluar në ruajtje me një lidhje me çelësin e dhënë. Marrësi, në përputhje me rrethanat, kthen një objekt nga dyqani. Një dyqan nuk është gjë tjetër veçse një grup shoqërues me vlerë kyçe.

    Për kontroll të plotë mbi regjistrin, futet një element tjetër i ndërfaqes - një metodë që ju lejon të fshini një objekt nga ruajtja.

    Përveç problemeve identike me modelin Singleton, ka edhe dy të tjera:

    • futja e një lloji tjetër të varësisë - nga çelësat e regjistrit;
    • dy çelësa të ndryshëm regjistri mund të kenë një referencë për të njëjtin objekt

    Në rastin e parë, është e pamundur të shmanget varësia shtesë. Në një farë mase, ne lidhemi me emrat kryesorë.

    Problemi i dytë zgjidhet duke futur një kontroll në metodën Registry::set():

    Set i funksionit statik publik ($key, $item) ( nëse (!array_key_exists($key, self::$_registry)) (përpara (self::$_registry si $val) (nëse ($val === $item) ( hedh një përjashtim të ri ("Artikulli tashmë ekziston"); ) ) vetë::$_registry[$key] = $item; ) )

    « Pastroni modelin e regjistrit"lind një problem tjetër - rritja e varësisë për shkak të nevojës për të hyrë në setter dhe marrës përmes emrit të klasës. Ju nuk mund të krijoni një referencë për një objekt dhe të punoni me të, siç ishte rasti me modelin Singleton, kur kjo qasje ishte e disponueshme:

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

    Këtu kemi mundësinë të ruajmë një referencë për një shembull Singleton, për shembull, në një veti të klasës aktuale dhe të punojmë me të siç kërkohet nga ideologjia OOP: kalojmë atë si parametër për objektet e grumbulluara ose përdorim atë në pasardhës.

    Për të zgjidhur këtë çështje ekziston Implementimi i Regjistrit Singleton, që shumë njerëzve nuk e pëlqejnë sepse duket si kod i tepërt. Mendoj se arsyeja e këtij qëndrimi është një keqkuptim i parimeve të OOP ose një shpërfillje e qëllimshme ndaj tyre.

    _regjistri [$kyç] = $objekt; ) funksioni publik statik get($key) ( return self::getInstance()->_registry[$key]; ) funksion privat __wakeup() ( ) funksion privat __construct() ( ) funksion privat __clone() ( ) ) ?>

    Për të kursyer para, kam hequr qëllimisht blloqet e komenteve për metodat dhe vetitë. Nuk mendoj se janë të nevojshme.

    Siç thashë tashmë, ndryshimi themelor është se tani është e mundur të ruani një referencë në vëllimin e regjistrit dhe të mos përdorni thirrje të rënda drejt metodave statike çdo herë. Ky opsion më duket disi më i saktë. Dakord apo mospajtim me mendimin tim nuk ka shumë rëndësi, ashtu si vetë mendimi im. Asnjë hollësi e zbatimit nuk mund ta eliminojë modelin nga një numër i disavantazheve të përmendura.

    Vendosa të shkruaj shkurtimisht për modelet e përdorura shpesh në jetën tonë, më shumë shembuj, më pak ujë, le të shkojmë.

    Singleton

    Pika kryesore e "singlit" është se kur thoni "Kam nevojë për një central telefonik", ata do t'ju thoshin "Aty tashmë është ndërtuar" dhe jo "Le ta ndërtojmë përsëri". Një "i vetmuar" është gjithmonë vetëm.

    Klasa Singleton ( static privat $instance = null; funksioni privat __construct())( /* ... @return Singleton */ ) // Mbrojtja kundër krijimit nëpërmjet funksionit të ri privat Singleton __clone() ( /* ... @return Singleton * / ) // Mbroje kundër krijimit nëpërmjet klonimit të funksionit privat __wakeup() ( /* ... @return Singleton */ ) // Mbroje kundër krijimit nëpërmjet unserializimit të funksionit statik publik getInstance() ( if (is_null(self::$instance ) ) ( vet::$instance = vetja e re; ) ktheje veten::$instance; ) )

    Regjistri (regjistri, ditari i regjistrimeve)

    Siç sugjeron emri, ky model është krijuar për të ruajtur të dhënat që janë vendosur në të dhe, në përputhje me rrethanat, për t'i kthyer këto të dhëna (me emër) nëse ato kërkohen. Në shembullin e një centrali telefonik, ai është një regjistër në lidhje me numrat e telefonit të banorëve.

    Regjistri i klasës ( regjistri $ privat = grupi (); grupi i funksioneve publike ($key, $object) ( $this->registry[$key] = $object; ) funksioni publik get($key) (kthimi i regjistrit $this-> [$kyç];))

    Regjistri Singleton- mos u ngatërroni me)

    "Regjistri" është shpesh një "i vetmuar", por jo gjithmonë duhet të jetë kështu. Për shembull, ne mund të krijojmë disa revista në departamentin e kontabilitetit, në një punonjës nga "A" në "M", në tjetrin nga "N" në "Z". Çdo revistë e tillë do të jetë një "regjistër", por jo një "i vetëm", ​​sepse ka tashmë 2 revista.

    Klasa SingletonRegistry ( static privat $instance = null; privat $registry = array(); funksion privat __construct() ( /* ... @return Singleton */ ) // Mbrojtja nga krijimi nëpërmjet funksionit të ri privat Singleton __clone() ( / * ... @return Singleton */ ) // Mbrojtja kundër krijimit nëpërmjet klonimit të funksionit privat __wakeup() ( /* ... @return Singleton */ ) // Mbrojtja kundër krijimit nëpërmjet unserializimit të funksionit statik publik getInstance() ( nëse ( is_null(self::$instance)) (self::$instance = vetja e re; ) return self::$instance; ) set funksioni publik ($key, $object) ( $this->registry[$key] = $ objekt; ) funksioni publik get($key) (ktheje $this->registry[$key]; ) )

    Multiton (peshinë "beqarësh") ose me fjalë të tjeraRegjistri Singleton ) - mos e ngatërroni me Singleton Registry

    Shpesh "regjistri" përdoret posaçërisht për të ruajtur "beqarët". Por sepse modeli i "regjistrit" nuk është një "model gjenerues", por unë do të doja të merrja në konsideratë "regjistrin" në lidhje me "singleton".Kjo është arsyeja pse ne dolëm me një model Multiton, e cila sipasNë thelbin e tij, ai është një "regjistër" që përmban disa "single", secila prej të cilave ka "emrin" e vet me të cilin mund të aksesohet.

    I shkurtër: ju lejon të krijoni objekte të kësaj klase, por vetëm nëse emërtoni objektin. Nuk ka asnjë shembull të jetës reale, por unë gjeta shembullin e mëposhtëm në internet:

    Baza e të dhënave të klasës ( $instances private statike = array(); funksioni privat __construct() ( ) funksioni privat __clone() ( ) funksioni statik publik getInstance($key) ( if(!array_key_exists($key, self::$instances)) ( vet::$instancat[$key] = vetja e re(); ) kthen veten::$instancat[$key]; ) ) $master = Baza e të dhënave::getInstance("master"); var_dump ($master); // objekt (Baza e të dhënave)#1 (0) ( ) $logger = Baza e të dhënave::getInstance("logger"); var_dump ($logger); // objekt (Baza e të dhënave)#2 (0) ( ) $masterDupe = Baza e të dhënave::getInstance("master"); var_dump ($masterDupe); // objekt(Baza e të dhënave)#1 (0) ( ) // Gabim fatal: Thirrje në bazën e të dhënave private::__konstrukt() nga konteksti i pavlefshëm $dbFatalError = baza e re e të dhënave(); // PHP Gabim fatal: Thirrje në bazën e të dhënave private::__clone() $dbCloneError = klon $masterDupe;

    Pishina e objekteve

    Në thelb ky model është një "regjistër" që ruan vetëm objekte, pa vargje, vargje, etj. llojet e të dhënave.

    Fabrika

    Thelbi i modelit përshkruhet pothuajse plotësisht me emrin e tij. Kur ju duhet të merrni disa objekte, të tilla si kuti lëngjesh, nuk keni nevojë të dini se si prodhohen në një fabrikë. Thjesht thoni, "më jep një kuti me lëng portokalli" dhe "fabrika" ju kthen paketën e kërkuar. Si? E gjithë kjo vendoset nga vetë fabrika, për shembull, ajo "kopjon" një standard tashmë ekzistues. Qëllimi kryesor i "fabrikës" është të bëjë të mundur, nëse është e nevojshme, ndryshimin e procesit të "paraqitjes" së një pakete lëngu dhe vetë konsumatorit nuk ka nevojë t'i thuhet asgjë për këtë, në mënyrë që ai ta kërkojë atë. si më parë. Si rregull, një fabrikë është e angazhuar në "prodhimin" e vetëm një lloji "produkti". Nuk rekomandohet krijimi i një "fabrike lëngjesh" duke marrë parasysh prodhimin e gomave të makinave. Ashtu si në jetë, modeli i fabrikës shpesh krijohet nga një person i vetëm.

    Klasa abstrakte AnimalAbstract ( mbrojtur $specie; funksioni publik getSpecies() ( return $this->species; ) ) class Cat extensions AnimalAbstract ( protected $species = "cat"; ) class Dog extensions AnimalAbstract ( protected $species = "dog"; ) klasa AnimalFactory ( fabrika e funksionit statik publik ($kafshë) ( ndërprerësi ($kafshë) ( rasti "cat": $obj = Cat i ri (); pushim; rasti "qen": $obj = qeni i ri (); pushim; parazgjedhje : hedh new Përjashtim("Fabrika e kafshëve nuk mundi të krijonte kafshë të specieve "" . $kafshë . """, 1000); ) return $obj; ) ) $cat = AnimalFactory::fabrika("mace"); // objekti(Cat)#1 echo $cat->getSpecies(); // cat $dog = Fabrika e kafshëve::fabrika ("qen"); // objekti(Qeni)#1 echo $dog->getSpecies(); // qeni $hippo = Fabrika e kafshëve::fabrika ("hipopotami"); // Kjo do të hedhë një Përjashtim

    Do të doja të tërhiqja vëmendjen tuaj për faktin se metoda e fabrikës është gjithashtu një model; ajo quhet metoda e fabrikës.

    Ndërtues (ndërtues)

    Pra, ne e kemi kuptuar tashmë që "Fabrika" është një makinë shitëse pijesh, tashmë ka gjithçka gati, dhe ju thjesht thoni atë që ju nevojitet. “Builder” është një fabrikë që prodhon këto pije dhe përmban të gjitha operacionet komplekse dhe mund të montojë objekte komplekse nga ato më të thjeshta (paketim, etiketë, ujë, shije etj.) në varësi të kërkesës.

    Shishja e klasës ( $emri publik; $litra publik;) /** * të gjithë ndërtuesit duhet */ ndërfaqen BottleBuilderInterface ( funksioni publik setName(); funksioni publik setLiters(); funksioni publik getResult(); ) klasa CocaColaBuilder zbaton BottleBuilderInterface ( private $ shishe; funksioni publik __construct() ($this->shishe = shishe e re(); ) funksioni publik setName($value) ($this->bottle->name = $value; ) funksioni publik setLiters($value) ($ this->shishe->litra = $value; ) funksioni publik getResult() (kthimi $this->shishe; ) ) $juice = New CocaColaBuilder(); $juice->setName("Coca-Cola Light"); $juice->setLiters(2); $juice->getResult();

    Prototip

    I ngjan një fabrike, shërben edhe për të krijuar objekte, por me një qasje pak më ndryshe. Imagjinoni veten në një lokal, po pini birrë dhe po ju mbaron, i thoni banakierit - më bëj një tjetër të të njëjtit lloj. Baristi nga ana e tij shikon birrën që po pini dhe bën një kopje siç e keni kërkuar. PHP tashmë ka një implementim të këtij modeli, që quhet .

    $newJuice = klon $lëng;

    Inicializimi dembel

    Për shembull, një shef sheh një listë raportesh për lloje të ndryshme aktivitetesh dhe mendon se këto raporte tashmë ekzistojnë, por në fakt shfaqen vetëm emrat e raporteve dhe vetë raportet nuk janë gjeneruar ende dhe vetëm do të gjenerohen. sipas porosisë (për shembull, duke klikuar butonin Shiko raportin). Një rast i veçantë i inicializimit dembel është krijimi i një objekti në momentin kur aksesohet. Mund të gjeni një interesante në Wikipedia, por... sipas teorisë, shembulli i saktë në php do të ishte, për shembull, një funksion

    Përshtatës ose mbështjellës (përshtatës, mbështjellës)

    Ky model korrespondon plotësisht me emrin e tij. Për të bërë që një prizë "sovjetike" të funksionojë përmes një prize Euro, kërkohet një përshtatës. Kjo është pikërisht ajo që bën një "përshtatës" - ai shërben si një objekt i ndërmjetëm midis dy të tjerëve që nuk mund të punojnë drejtpërdrejt me njëri-tjetrin. Pavarësisht përkufizimit, në praktikë unë ende shoh ndryshimin midis përshtatësit dhe mbështjellësit.

    Klasa MyClass ( metoda e funksionit publikA() () ) klasa MyClassWrapper (funksioni publik __construct())( $this->myClass = myClass i ri(); ) funksioni publik __call($name, $arguments)( Log::info(" Jeni gati të thërrisni metodën $name."); ktheni call_user_func_array(array($this->myClass, $name), $arguments); ) ) $obj = i ri MyClassWrapper(); $obj->metodA();

    Injeksion varësie

    Injeksioni i varësisë ju lejon të zhvendosni një pjesë të përgjegjësisë për disa funksione tek objektet e tjera. Për shembull, nëse na duhet të punësojmë personel të ri, atëherë nuk mund të krijojmë departamentin tonë të burimeve njerëzore, por të vendosim varësinë nga një kompani rekrutimi, e cila, nga ana tjetër, me kërkesën tonë të parë "ne kemi nevojë për një person", ose do të funksionojë si një Vetë departamenti i HR, ose do të gjejë një kompani tjetër (duke përdorur një "lokator shërbimi") që do t'i ofrojë këto shërbime.
    "Injeksioni i varësisë" ju lejon të zhvendosni dhe shkëmbeni pjesë individuale të kompanisë pa humbur funksionalitetin e përgjithshëm.

    Klasa AppleJuice () // kjo metodë është një zbatim primitiv i modelit të injektimit të varësisë dhe më tej do të shihni këtë funksion getBottleJuice())( $obj = i ri Lëng molle Lëng molle)( kthe $obj; ) ) $bottleJuice = getBottleJuice();

    Tani imagjinoni që ne nuk duam më lëng molle, duam lëng portokalli.

    Klasa AppleJuice() Klasa Lëng portokalli() // kjo metodë zbaton funksionin e injektimit të varësisë getBottleJuice())( $obj = i ri Lëng portokalli; // kontrolloni objektin, në rast se na kanë rrëshqitur birrë (birra nuk është lëng) nëse ($obj instanceof Lëng portokalli)( kthe $obj; ) )

    Siç mund ta shihni, na u desh të ndryshonim jo vetëm llojin e lëngut, por edhe kontrollin për llojin e lëngut, i cili nuk është shumë i përshtatshëm. Është shumë më e saktë të përdoret parimi i përmbysjes së varësisë:

    Lëngu i ndërfaqes () Klasa AppleJuice zbaton Juice () Klasa OrangeJuice zbaton funksionin Juice () getBottleJuice())( $obj = OrangeJuice i ri; // kontrolloni objektin, në rast se na kanë rrëshqitur birrë (birra nuk është lëng) nëse($obj shembull i Lëng)( kthe $obj; ) )

    Përmbysja e varësisë ndonjëherë ngatërrohet me injektimin e varësisë, por nuk ka nevojë t'i ngatërroni ato, sepse Përmbysja e varësisë është një parim, jo ​​një model.

    Gjetësi i shërbimit

    "Service Locator" është një metodë e zbatimit të "Dependency Injection". Ai kthen lloje të ndryshme objektesh në varësi të kodit të inicializimit. Le të jetë detyra që të dorëzojmë paketën tonë të lëngjeve, të krijuar nga një ndërtues, fabrikë apo diçka tjetër, kudo që blerësi dëshiron. Ne i themi lokalizuesit "na jep një shërbim shpërndarjeje" dhe i kërkojmë shërbimit të dorëzojë lëngun në adresën e dëshiruar. Sot ka një shërbim, dhe nesër mund të ketë një tjetër. Nuk ka rëndësi për ne se çfarë shërbimi specifik është, është e rëndësishme që ne të dimë se ky shërbim do të japë atë që ne i themi dhe ku i themi. Nga ana tjetër, shërbimet zbatojnë “Deliver<предмет>në<адрес>».

    Nëse flasim për jetën reale, atëherë ndoshta një shembull i mirë i një Lokatori të Shërbimit do të ishte zgjerimi PDO PHP, sepse Sot ne punojmë me një bazë të dhënash MySQL, dhe nesër mund të punojmë me PostgreSQL. Siç e keni kuptuar tashmë, për klasën tonë nuk ka rëndësi se në cilën bazë të dhënash i dërgon të dhënat e saj, është e rëndësishme që ajo ta bëjë këtë.

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

    Dallimi midis injektimit të varësisë dhe lokalizuesit të shërbimit

    Nëse nuk e keni vënë re ende, unë do të doja të shpjegoj. Injeksion varësie Si rezultat, ai nuk kthen një shërbim (i cili mund të japë diçka diku), por një objekt të dhënat e të cilit përdor.

    Do të përpiqem t'ju tregoj për zbatimin tim të modelit të Regjistrit në PHP. Regjistri është një zëvendësim OOP për variablat globale, i krijuar për të ruajtur të dhënat dhe për t'i transferuar ato midis moduleve të sistemit. Prandaj, ajo është e pajisur me veti standarde - shkruani, lexoni, fshini. Këtu është një zbatim tipik.

    Epo, në këtë mënyrë marrim një zëvendësim budalla të metodave $key = $value - Registry::set($key, $value) $key - Registry::get($key) unset($key) - remove Registry::remove ($key ) Thjesht bëhet e paqartë - pse ky kod shtesë. Pra, le ta mësojmë klasën tonë të bëjë atë që variablat globale nuk mund ta bëjnë. Le t'i shtojmë piper.

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

    Në detyrat tipike të modelit, unë shtova aftësinë për të bllokuar një ndryshore nga ndryshimet, kjo është shumë e përshtatshme në projekte të mëdha, nuk do të futni aksidentalisht asgjë. Për shembull, i përshtatshëm për të punuar me bazat e të dhënave
    define('DB_DNS', 'mysql:host=localhost;dbname= ’);
    define('DB_USER', ' ’);
    define('DB_PASSWORD', ' ’);
    define('DB_HANDLE');

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

    Tani për një shpjegim të kodit, për të ruajtur të dhënat, ne përdorim variablin statik $data, ndryshorja $lock ruan të dhëna për çelësat që janë të kyçur për ndryshim. Në rrjet, ne kontrollojmë nëse ndryshorja është e bllokuar dhe e ndryshojmë ose e shtojmë atë në regjistër. Gjatë fshirjes, ne kontrollojmë gjithashtu bllokimin; marrësi mbetet i pandryshuar, me përjashtim të parametrit opsional të paracaktuar. Epo, ia vlen t'i kushtohet vëmendje trajtimit të përjashtimeve, i cili për ndonjë arsye përdoret rrallë. Nga rruga, unë tashmë kam një draft mbi përjashtimet, prisni artikullin. Më poshtë është një draft kod për testim, këtu është një artikull për testimin, nuk do të dëmtonte as ta shkruaja, megjithëse nuk jam adhurues i TDD.

    Në artikullin vijues do të zgjerojmë më tej funksionalitetin duke shtuar inicializimin e të dhënave dhe duke zbatuar "dembelizmin".