Az objektum inicializálásának problémája az OOP alkalmazásokban PHP-ben. Megoldás keresése a Registry, a Factory Method, a Service Locator és a Dependency Injection minták segítségével. OOP minták példákkal és leírásokkal Hiteles registro php

Az objektum inicializálásának problémája az OOP alkalmazásokban PHP-ben. Megoldás keresése a Registry, a Factory Method, a Service Locator és a Dependency Injection minták segítségével

Megesik, hogy a programozók tervezési minták formájában konszolidálják a sikeres megoldásokat. Rengeteg szakirodalom van a mintákról. A Négyek bandája Erich Gamma, Richard Helm, Ralph Johnson és John Vlissides "Design Patterns" című könyve és talán Martin Fowler "Vállalati alkalmazásarchitektúra mintái" minden bizonnyal klasszikusnak számít. A legjobb dolog, amit olvastam példákkal. PHP-ben – ez. Történt, hogy ez az egész irodalom meglehetősen összetett azok számára, akik most kezdték el az OOP-t szóval ez a cikk az első kísérletem a tervezési minták KISS stílusban történő értelmezésére.
Ma arról fogunk beszélni, hogy milyen problémák merülhetnek fel az objektum inicializálása során egy OOP-alkalmazásban, és hogyan lehet néhány népszerű tervezési mintát használni ezeknek a problémáknak a megoldására.

Példa

Egy modern OOP-alkalmazás tíz, száz és néha több ezer objektummal működik. Nos, nézzük meg közelebbről, hogyan inicializálódnak ezek az objektumok az alkalmazásainkban. Az objektum inicializálása az egyetlen szempont, ami érdekel minket ebben a cikkben, ezért úgy döntöttem, hogy kihagyok minden „extra” megvalósítást.
Tegyük fel, hogy létrehoztunk egy szuper-duper hasznos osztályt, amely GET kérést küldhet egy adott URI-ra, és HTML-kódot küldhet vissza a szerver válaszából. Hogy az osztályunk ne tűnjön túl egyszerűnek, ellenőrizze az eredményt is, és dobjon kivételt, ha a szerver „hibásan” válaszol.

Class Grabber ( nyilvános függvény get($url) (/** HTML kódot ad vissza, vagy kivételt ad */)

Hozzunk létre egy másik osztályt, amelynek objektumai a kapott HTML szűréséért lesznek felelősek. A szűrőmódszer HTML kódot és egy CSS-szelektort vesz argumentumként, és az adott szelektorhoz talált elemek tömbjét adja vissza.

Class HtmlExtractor ( nyilvános függvényszűrő ($html, $selector) (/** a szűrt elemek tömbjét adja vissza */) )

Most képzeljük el, hogy az adott kulcsszavakra keresési eredményeket kell kapnunk a Google-on. Ehhez bevezetünk egy másik osztályt, amely a Grabber osztályt fogja használni a kérés elküldésére, a HtmlExtractor osztályt pedig a szükséges tartalom kinyerésére. Tartalmazza továbbá az URI felépítésének logikáját, a kapott HTML szűrésére szolgáló szelektort és a kapott eredmények feldolgozását.

Osztály GoogleFinder ( privát $grabber; privát $szűrő; nyilvános függvény __construct() ( $this->grabber = new Grabber(); $this->filter = new HtmlExtractor(); ) nyilvános függvény find($searchString) ( /* * megalapozott eredmények tömbjét adja vissza */) )

Észrevette, hogy a Grabber és a HtmlExtractor objektumok inicializálása a GoogleFinder osztály konstruktorában van? Gondoljunk bele, mennyire jó ez a döntés.
Természetesen nem jó ötlet az objektumok létrehozásának hardkódolása egy konstruktorban. És ezért. Először is, nem tudjuk könnyen felülírni a Grabber osztályt a tesztkörnyezetben, hogy elkerüljük a valódi kérés küldését. Az igazságosság kedvéért érdemes elmondanunk, hogy ez a Reflection API használatával is megtehető. Azok. a technikai lehetőség adott, de ez messze nem a legkényelmesebb és legkézenfekvőbb mód.
Másodszor, ugyanez a probléma akkor fog fellépni, ha a GoogleFinder logikáját más Grabber és HtmlExtractor implementációkkal szeretnénk újra felhasználni. A függőségek létrehozása az osztálykonstruktorban van kódolva. A legjobb esetben pedig örökölhetjük a GoogleFinder-t, és felülírhatjuk a konstruktorát. És akkor is csak akkor, ha a grabber és a filter tulajdonságainak hatóköre védett vagy nyilvános.
Egy utolsó szempont, minden alkalommal, amikor új GoogleFinder objektumot hozunk létre, egy új függőségi objektum pár jön létre a memóriában, bár egy Grabber objektumot és egy HtmlExtractor objektumot meglehetősen könnyen használhatunk több GoogleFinder objektumban.
Azt hiszem, már érted, hogy a függőségi inicializálást az osztályon kívülre kell helyezni. Megkövetelhetjük, hogy a már előkészített függőségek átadásra kerüljenek a GoogleFinder osztály konstruktorának.

GoogleFinder osztály ( privát $grabber; privát $szűrő; nyilvános függvény __construct(Grabber $grabber, HtmlExtractor $filter) ( $this->grabber = $grabber; $this->filter = $szűrő; ) nyilvános függvény find($searchString) ( /** megalapozott eredmények tömbjét adja vissza */) )

Ha lehetőséget akarunk adni más fejlesztőknek saját Grabber és HtmlExtractor implementációik hozzáadására és használatára, akkor érdemes megfontolnunk interfészek bevezetését számukra. Ebben az esetben ez nem csak hasznos, hanem szükséges is. Úgy gondolom, hogy ha csak egy implementációt használunk egy projektben, és nem számítunk arra, hogy a jövőben újakat hozunk létre, akkor meg kell tagadnunk egy felület létrehozását. Jobb a helyzetnek megfelelően cselekedni, és egyszerű átdolgozást végezni, amikor valóban szükség van rá.
Most már minden szükséges osztályunk megvan, és használhatjuk a GoogleFinder osztályt a vezérlőben.

Osztályvezérlő ( public function action() ( /* Néhány dolog */ $finder = new GoogleFinder(new Grabber(), new HtmlExtractor()); $results = $finder->

Foglaljuk össze a közbenső eredményeket. Nagyon kevés kódot írtunk, és első ránézésre nem csináltunk semmi rosszat. De... mi van akkor, ha egy másik helyen kell használnunk egy olyan objektumot, mint a GoogleFinder? Létrehozását meg kell másolnunk. Példánkban ez csak egy sor, és a probléma nem annyira észrevehető. A gyakorlatban az objektumok inicializálása meglehetősen bonyolult lehet, és akár 10 sort is igénybe vehet, vagy még többet is. Más, a kódduplikációra jellemző problémák is felmerülnek. Ha az átalakítási folyamat során módosítania kell a használt osztály nevét vagy az objektum inicializálási logikáját, akkor manuálisan kell módosítania az összes helyet. Szerintem tudod hogy történik :)
Általában a hardcode-ot egyszerűen kezelik. Az ismétlődő értékek általában szerepelnek a konfigurációban. Ez lehetővé teszi az értékek központi módosítását minden olyan helyen, ahol használják őket.

Registry sablon.

Ezért úgy döntöttünk, hogy az objektumok létrehozását áthelyezzük a konfigurációba. Csináljuk meg.

$registry = new ArrayObject(); $registry["grabber"] = new Grabber(); $registry["szűrő"] = new HtmlExtractor(); $registry["google_finder"] = new GoogleFinder($registry["grabber"], $registry["szűrő"]);
Csak annyit kell tennünk, hogy átadjuk az ArrayObjectünket a vezérlőnek, és a probléma megoldódik.

Osztályvezérlő ( privát $registry; nyilvános függvény __construct(ArrayObject $registry) ( $this->registry = $registry; ) public function action() ( /* Néhány dolog */ $results = $this->registry["google_finder" ]->find("keresési karakterlánc"); /* Csinálj valamit az eredményekkel */ ) )

Továbbfejleszthetjük a Regisztráció ötletét. Örökölje az ArrayObject-et, zárja be az objektumok létrehozását egy új osztályba, tiltsa új objektumok hozzáadását inicializálás után stb. De véleményem szerint a megadott kód teljesen egyértelművé teszi, hogy mi a Registry sablon. Ez a minta nem generatív, de valamilyen módon megoldja problémáinkat. A Registry csak egy tároló, amelyben tárolhatjuk az objektumokat, és továbbadhatjuk őket az alkalmazáson belül. Ahhoz, hogy az objektumok elérhetővé váljanak, először létre kell hoznunk és regisztrálnunk kell őket ebben a tárolóban. Nézzük meg ennek a megközelítésnek az előnyeit és hátrányait.
Első ránézésre elértük célunkat. Leállítottuk az osztálynevek keménykódolását, és egy helyen hoztunk létre objektumokat. Az objektumokat egyetlen példányban hozzuk létre, ami garantálja azok újrafelhasználását. Ha az objektumok létrehozásának logikája megváltozik, akkor az alkalmazásban csak egy helyet kell szerkeszteni. Bónuszként megkaptuk az objektumok központi kezelésének lehetőségét a Registry-ben. Könnyedén kaphatunk listát az összes elérhető objektumról, és végrehajthatunk velük néhány manipulációt. Most nézzük meg, mi nem tetszhet nekünk ebben a sablonban.
Először is létre kell hoznunk az objektumot, mielőtt regisztrálnánk a Registry-ben. Ennek megfelelően nagy a valószínűsége a „felesleges tárgyak” létrehozásának, pl. azokat, amelyek a memóriában jönnek létre, de nem kerülnek felhasználásra az alkalmazásban. Igen, dinamikusan is hozzáadhatunk objektumokat a Registry-hez, pl. csak azokat az objektumokat hozza létre, amelyek egy adott kérés feldolgozásához szükségesek. Így vagy úgy, ezt manuálisan kell irányítanunk. Ennek megfelelően idővel nagyon nehéz lesz karbantartani.
Másodszor, van egy új vezérlőfüggőségünk. Igen, a Registry-ben statikus metóduson keresztül tudunk objektumokat fogadni, így nem kell a Registry-t átadnunk a konstruktornak. De véleményem szerint ezt nem szabad megtenned. A statikus módszerek még szorosabb kapcsolatot jelentenek, mint az objektumon belüli függőségek létrehozása és a tesztelési nehézségek (ebben a témában).
Harmadszor, a vezérlő felülete nem mond semmit arról, hogy milyen objektumokat használ. A vezérlőben a Registry-ben elérhető bármely objektumot megkaphatjuk. Nehéz lesz megmondani, hogy a vezérlő mely objektumokat használja, amíg nem ellenőrizzük az összes forráskódját.

Gyári módszer

A Registry legnagyobb problémája az, hogy egy objektumot inicializálni kell, mielőtt hozzáférhetne. Ahelyett, hogy a konfigurációban inicializálnánk egy objektumot, az objektumok létrehozásának logikáját szétválaszthatjuk egy másik osztályba, amelyet „megkérhetünk” a szükséges objektum felépítésére. Az objektumok létrehozásáért felelős osztályokat gyáraknak nevezzük. A tervezési mintát pedig gyári módszernek hívják. Nézzünk egy példa gyárat.

Class Factory ( nyilvános függvény getGoogleFinder() ( új GoogleFinder($this->getGrabber(), $this->getHtmlExtractor()); ) privát függvény getGrabber() ( új Grabber(); ) privát függvény getHtmlExtractor() ( új HtmlFiletr(); ) )

Általában olyan gyárakat készítenek, amelyek egy típusú objektum létrehozásáért felelősek. Néha egy gyár létrehozhat egy csoportot kapcsolódó objektumokból. Használhatunk gyorsítótárat egy tulajdonságba, hogy elkerüljük az objektumok újbóli létrehozását.

Class Factory ( privát $finder; nyilvános függvény getGoogleFinder() ( if (null === $this->finder) ( $this->finder = new GoogleFinder($this->getGrabber(), $this->getHtmlExtractor()) ); ) küldje vissza a $this->findert; ) )

A bejövő paramétertől függően paraméterezhetünk egy gyári metódust, és átruházhatjuk az inicializálást más gyárakra. Ez már egy Abstract Factory sablon lesz.
Ha modularizálnunk kell az alkalmazást, megkövetelhetjük, hogy minden modul saját gyárat biztosítson. A gyárak témáját továbbfejleszthetjük, de szerintem ennek a sablonnak a lényege egyértelmű. Lássuk, hogyan fogjuk használni a gyárat a vezérlőben.

Osztályvezérlő ( privát $gyári; nyilvános függvény __construct(Factory $factory) ( $this->factory = $gyári; ) public function action() ( /* Néhány dolog */ $results = $this->factory->getGoogleFinder( )->find("keresési karakterlánc"); /* Csinálj valamit az eredményekkel */ ) )

Ennek a megközelítésnek az előnyei közé tartozik az egyszerűség. Objektumaink explicit módon jönnek létre, és az IDE könnyen elvezeti Önt arra a helyre, ahol ez megtörténik. Megoldottuk a Registry problémát is, így a memóriában lévő objektumok csak akkor jönnek létre, ha erre "kérjük" a gyárat. De még nem döntöttük el, hogyan szállítjuk a szükséges gyárakat a vezérlőknek. Itt több lehetőség is van. Használhat statikus módszereket. Hagyhatjuk, hogy a kontrollerek maguk hozzák létre a szükséges gyárakat, és semmissé tegyük a copy-paste-tól való megszabadulásra tett kísérletünket. Létrehozhat egy gyárat, és csak ezt adhatja át a vezérlőnek. De az objektumok vezérlése a vezérlőbe kissé bonyolultabbá válik, és kezelnie kell a gyárak közötti függőséget. Ráadásul nem teljesen világos, hogy mit tegyünk, ha modulokat szeretnénk használni az alkalmazásunkban, hogyan kell modulgyárakat regisztrálni, hogyan kell a gyárak közötti kapcsolatokat kezelni a különböző modulokból. Általánosságban elmondható, hogy elvesztettük a gyár fő előnyét - az objektumok kifejezett létrehozását. És még mindig nem oldottuk meg az „implicit” vezérlőfelület problémáját.

Szolgáltatáskereső

A Service Locator sablon lehetővé teszi a gyárak széttagoltságának hiányát, valamint az objektumok létrehozásának automatikus és központi kezelését. Ha jobban belegondolunk, bevezethetünk egy további absztrakciós réteget, amely az alkalmazásunkban lévő objektumok létrehozásáért és az ezen objektumok közötti kapcsolatok kezeléséért lesz felelős. Ahhoz, hogy ez a réteg objektumokat tudjon létrehozni számunkra, meg kell adnunk neki a tudást, hogyan kell ezt megtenni.
Szolgáltatáskereső minta feltételei:
  • A szolgáltatás egy konténerből beszerezhető kész objektum.
  • Service Definition – szolgáltatás inicializálási logika.
  • A tároló (Service Container) egy központi objektum, amely az összes leírást tárolja, és ezek alapján szolgáltatásokat tud létrehozni.
Bármely modul regisztrálhatja a szolgáltatás leírását. Ahhoz, hogy valamilyen szolgáltatást kapjunk a konténerből, kulcsra kell kérnünk. A Service Locator megvalósítására számos lehetőség kínálkozik, a legegyszerűbb változatban az ArrayObject-et használhatjuk konténerként, zárást pedig szolgáltatásleírásként.

A Class ServiceContainer kiterjeszti az ArrayObject objektumot ( nyilvános függvény get($kulcs) ( if (is_callable($this[$key])) ( return call_user_func($this[$key]); ) dobja az új \RuntimeException("Nem található szolgáltatásdefiníció a következő alatt a kulcs [ $kulcs ]"); ) )

Ezután a Definíciók regisztrációja így fog kinézni:

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

És a vezérlőben a használat a következő:

Osztályvezérlő ( privát $container; nyilvános függvény __construct(ServiceContainer $container) ( $this->container = $container; ) public function action() ( /* Néhány dolog */ $eredmények = $this->container->get( "google_finder")->find("keresési karakterlánc"); /* Csinálj valamit az eredményekkel */ ) )

A Service Container lehet nagyon egyszerű, de lehet nagyon összetett is. Például a Symfony Service Container számos funkciót kínál: paraméterek, szolgáltatások hatóköre, szolgáltatások keresése címkék, álnevek, privát szolgáltatások alapján, a tároló módosításának lehetősége az összes szolgáltatás hozzáadása után (fordítói engedélyek) és még sok más. A DIExtraBundle tovább bővíti a szabványos implementáció képességeit.
De térjünk vissza példánkhoz. Amint látja, a Service Locator nemcsak ugyanazokat a problémákat oldja meg, mint az előző sablonok, hanem megkönnyíti a saját szolgáltatásdefiníciókkal rendelkező modulok használatát is.
Ezen kívül a keretszinten további absztrakciós szintet kaptunk. Ugyanis a ServiceContainer::get metódus megváltoztatásával például lecserélhetjük az objektumot egy proxyra. A proxy objektumok alkalmazási körének pedig csak a fejlesztő fantáziája szab határt. Itt implementálhatja az AOP paradigmát, a LazyLoadingot stb.
A legtöbb fejlesztő azonban továbbra is anti-mintázatnak tartja a Service Locatort. Mert elméletben annyi ún Container Aware osztályok (azaz olyan osztályok, amelyek hivatkozást tartalmaznak a tárolóra). Ilyen például a Vezérlőnk, amelyen belül bármilyen szolgáltatást kaphatunk.
Lássuk, miért rossz ez.
Először is, ismét tesztelni. Ahelyett, hogy csak a tesztekben használt osztályokhoz hozna létre gúnyt, a teljes tárolót meg kell gúnyolni, vagy valódi tárolót kell használnia. Az első lehetőség nem felel meg Önnek, mert... sok felesleges kódot kell írni a tesztekben, másodszor azért, mert ellentmond az egységtesztelés elveinek, és többletköltségekhez vezethet a tesztek karbantartásához.
Másodszor, nehéz lesz újra reagálnunk. Bármely szolgáltatás (vagy ServiceDefinition) megváltoztatásával a tárolóban kénytelenek leszünk minden függő szolgáltatást is ellenőrizni. És ez a probléma nem oldható meg IDE segítségével. Az ilyen helyek megtalálása az alkalmazás során nem lesz olyan egyszerű. A függő szolgáltatásokon kívül minden olyan helyet is ellenőriznie kell, ahol az átdolgozott szolgáltatást megkapják a konténerből.
Nos, a harmadik ok az, hogy a szolgáltatások ellenőrizetlen kihúzása a konténerből előbb-utóbb a kód rendetlenségéhez és szükségtelen zűrzavarhoz vezet. Ezt nehéz megmagyarázni, csak egyre több időt kell töltenie, hogy megértse, hogyan működik ez vagy az a szolgáltatás, más szóval, csak a teljes forráskód elolvasásával értheti meg teljesen, mit csinál, vagy hogyan működik egy osztály.

Függőség-injekció

Mit tehet még, hogy korlátozza a konténer használatát egy alkalmazásban? A keretrendszerre átruházhatja az összes felhasználói objektum létrehozásának vezérlését, beleértve a vezérlőket is. Más szóval, a felhasználói kód nem hívhatja meg a tároló get metódusát. Példánkban hozzáadhatunk egy definíciót a vezérlőhöz a tárolóhoz:

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

És szabaduljon meg a vezérlőben lévő tárolótól:

Osztályvezérlő ( privát $finder; nyilvános függvény __construct(GoogleFinder $finder) ( $this->finder = $finder; ) public function action() ( /* Néhány dolog */ $results = $this->finder->find( "keresési karakterlánc"); /* Csinálj valamit az eredményekkel */ ) )

Ezt a megközelítést (amikor az ügyfélosztályok nem biztosítják a szolgáltatástárolóhoz való hozzáférést) függőség-injekciónak nevezik. De ennek a sablonnak is vannak előnyei és hátrányai is. Mindaddig, amíg betartjuk az egységes felelősség elvét, a kód nagyon szépnek tűnik. Először is megszabadultunk a konténertől a kliens osztályokban, így sokkal áttekinthetőbbé és egyszerűbbé tesszük a kódjukat. A szükséges függőségek cseréjével egyszerűen tesztelhetjük a vezérlőt. Létrehozhatunk és tesztelhetünk minden osztályt másoktól függetlenül (beleértve a vezérlő osztályokat is) TDD vagy BDD megközelítéssel. Tesztek létrehozásakor elvonatkoztathatunk a tárolótól, és később hozzáadhatunk egy Definíciót, amikor konkrét példányokat kell használnunk. Mindez egyszerűbbé és áttekinthetőbbé teszi kódunkat, a tesztelést pedig átláthatóbbá.
De szükséges megemlíteni az érem másik oldalát is. A tény az, hogy a vezérlők nagyon speciális osztályok. Kezdjük azzal a ténnyel, hogy az adatkezelő általában egy sor műveletet tartalmaz, ami azt jelenti, hogy sérti az egységes felelősség elvét. Ennek eredményeként a vezérlőosztálynak sokkal több függősége lehet, mint amennyi egy adott művelet végrehajtásához szükséges. A lusta inicializálás (az objektum az első használatkor példányosodik, előtte pedig egy könnyű proxyt használ) bizonyos mértékig megoldja a teljesítményproblémát. De építészeti szempontból sem teljesen helyes sok függőséget létrehozni egy vezérlőtől. Ezenkívül a vezérlők tesztelése általában szükségtelen művelet. Természetesen minden attól függ, hogy a tesztelés hogyan van megszervezve az alkalmazásban, és attól, hogy Ön hogyan érzi magát ezzel kapcsolatban.
Az előző bekezdésből rájöttél, hogy a Dependency Injection használata nem szünteti meg teljesen az építészeti problémákat. Ezért gondolja át, hogyan lesz kényelmesebb az Ön számára, hogy tárolja-e a tárolóra mutató hivatkozást a vezérlőkben vagy sem. Itt nincs egyetlen helyes megoldás. Szerintem mindkét megközelítés jó mindaddig, amíg a vezérlőkód egyszerű marad. De határozottan ne hozzon létre Conatiner Aware szolgáltatásokat a vezérlők mellett.

következtetéseket

Nos, eljött az idő, hogy összefoglaljuk mindazt, ami elhangzott. És sok minden elhangzott... :)
Tehát az objektumok létrehozásának felépítéséhez a következő mintákat használhatjuk:
  • Iktató hivatal: A sablonnak vannak nyilvánvaló hátrányai, amelyek közül a legalapvetőbb az, hogy objektumokat kell létrehozni, mielőtt azokat egy közös tárolóba helyeznénk. Nyilvánvalóan több problémánk lesz, mint hasznunk a használatából. Nyilvánvalóan nem ez a legjobb felhasználási módja a sablonnak.
  • Gyári módszer: A minta fő előnye: az objektumok explicit módon jönnek létre. A fő hátrány: a vezérlőknek vagy attól kell törődniük, hogy maguk hozzanak létre gyárakat, ami nem oldja meg teljesen az osztálynevek keménykódolását, vagy a keretrendszernek kell gondoskodnia a vezérlők minden szükséges gyárral való ellátásáról, ami nem lesz annyira nyilvánvaló. Nincs lehetőség az objektumok létrehozásának folyamatának központi kezelésére.
  • Szolgáltatáskereső: Az objektumok létrehozásának egy fejlettebb módja. Egy további absztrakciós szint használható az objektumok létrehozásakor felmerülő gyakori feladatok automatizálására. Például:
    class ServiceContainer kiterjeszti az ArrayObject objektumot ( nyilvános függvény get($key) ( if (is_callable($this[$key])) ( $obj = call_user_func($this[$key]); if ($obj RequestAwareInterface példány) ( $obj- >setRequest($this->get("request")); ) return $obj; ) throw new \RuntimeException("Nem található szolgáltatásdefiníció a [ $kulcs ] kulcs alatt"); ) )
    A Service Locator hátránya, hogy az osztályok nyilvános API-ja már nem informatív. El kell olvasni az osztály teljes kódját, hogy megértsük, milyen szolgáltatásokat használnak benne. Egy tárolóra való hivatkozást tartalmazó osztályt nehezebb tesztelni.
  • Függőség-injekció: Lényegében ugyanazt a szolgáltatástárolót használhatjuk, mint az előző mintánál. A különbség az, hogy ezt a tartályt hogyan használják. Ha elkerüljük, hogy az osztályokat a tárolótól függővé tegyük, akkor egyértelmű és egyértelmű osztály API-t kapunk.
Ez nem minden, amit szeretnék elmondani a PHP alkalmazásokban való objektumok létrehozásának problémájáról. Létezik a Prototype minta is, nem gondoltuk a Reflection API használatát, elhagytuk a szolgáltatások lusta betöltésének problémáját és sok más árnyalatot. A cikk elég hosszúra sikeredett, úgyhogy be is zárom :)
Meg akartam mutatni, hogy a Dependency Injection és más minták nem olyan bonyolultak, mint azt általában hiszik.
Ha a Dependency Injection-ről beszélünk, akkor ennek a mintának vannak például KISS implementációi

A leendő adatbázis szerkezetének érintése. A kezdet megtörtént, és nem tudunk visszavonulni, és nem is gondolok rá.

Kicsit később visszatérünk az adatbázishoz, de egyelőre elkezdjük írni a motorunk kódját. De először egy kis hardver. Kezdődik.

Az idő kezdete

Jelenleg még csak néhány elképzelésünk és megértésenk van a rendszer működéséről, amit szeretnénk megvalósítani, de maga a megvalósítás még nincs. Nincs mit dolgoznunk: nincs funkcionalitásunk - és, mint emlékszel, két részre osztottuk: belső és külső. Az ábécé betűket igényel, de a külső funkcionalitás belső funkcionalitást igényel – ezzel kezdjük.

De nem olyan gyorsan. Ahhoz, hogy működjön, egy kicsit mélyebbre kell mennünk. A mi rendszerünk egy hierarchiát képvisel, és minden hierarchikus rendszernek van kezdete: egy csatolási pont Linuxban, egy helyi lemez a Windowsban, egy állam rendszere, egy vállalat, egy oktatási intézmény stb. Egy ilyen rendszer minden eleme alá van rendelve valakinek, több beosztottja is lehet, a szomszédok és beosztottjaik megszólítására pedig a feletteseket vagy magát a kezdetet használja. A hierarchikus rendszer jó példája a családfa: kiválasztunk egy kiindulási pontot – valami őst –, és indulunk. A rendszerünkben szükségünk van egy kiindulási pontra is, ahonnan ágakat - modulokat, bővítményeket stb. Szükségünk van valamiféle interfészre, amelyen keresztül minden modulunk „kommunikálni fog”. A további munkához meg kell ismerkednünk a koncepcióval tervezési minta" és néhány megvalósításuk.

Tervezési minták

Nagyon sok cikk van arról, hogy mi ez, és milyen fajtái vannak, a téma meglehetősen elcsépelt, és nem mondok újat. A kedvenc Wikimben van információ erről a témáról: egy hintó csúszdával és még egy kicsit.

A tervezési mintákat gyakran tervezési mintáknak vagy egyszerűen mintáknak is nevezik (az angol minta szóból, ami „mintát” jelent). A továbbiakban a cikkekben, amikor a mintákról beszélek, a tervezési mintákra gondolok.

A mindenféle ijesztő (és nem is annyira ijesztő) mintanevek hatalmas listájából eddig csak kettő érdekel minket: a registry és a singleton.

Iktató hivatal (vagy regisztrálj) egy olyan minta, amely egy bizonyos tömbön működik, amelybe hozzáadhat és eltávolíthat egy bizonyos objektumkészletet, és hozzáférhet ezekhez és annak képességeihez.

Magányos (vagy szingli) egy olyan minta, amely biztosítja, hogy egy osztálynak csak egy példánya létezhet. Nem lehet másolni, elaltatni vagy felébreszteni (a PHP mágiáról beszélve: __clone(), __sleep(), __wakeup()). A Singletonnak van egy globális hozzáférési pontja.

A definíciók nem teljesek vagy általánosítottak, de ez elég a megértéshez. Különben nincs szükségünk rájuk. Érdekelnek bennünket ezeknek a mintáknak a képességei, de egy osztályban: egy ilyen mintát hívnak singleton registry vagy Singleton Registry.

Mit fog ez adni nekünk?
  • Garantáltan egyetlen példányunk lesz a registry-ből, amelybe bármikor hozzáadhatunk objektumokat, és a kódban bárhonnan használhatjuk őket;
  • lehetetlen lesz lemásolni és használni a PHP nyelv egyéb nem kívánt (ebben az esetben) varázslatát.

Ebben a szakaszban elég megérteni, hogy egyetlen regisztrációs adatbázis lehetővé teszi a rendszer moduláris felépítésének megvalósítását, amit a célok megbeszélésekor szerettünk volna a -ban, a többit pedig a fejlesztés előrehaladtával érteni fogja.

Nos, elég a szóból, alkossunk!

Első sorok

Mivel ez az osztály a kernel funkcióihoz kapcsolódik, először a projektünk gyökerében létrehozunk egy core nevű mappát, amelyben a kernelmodulok összes osztályát elhelyezzük. Kezdjük a registry-vel, ezért nevezzük a registry.php fájlt

Nem vagyunk kíváncsiak arra, hogy egy kíváncsi felhasználó beírja a fájlunkhoz tartozó közvetlen címet a böngésző sorába, ezért védekeznünk kell ettől. A cél eléréséhez csak meg kell határoznunk egy bizonyos állandót a fő futtatható fájlban, amelyet ellenőrizni fogunk. Az ötlet nem új, ha jól emlékszem, a Joomlában használták. Ez egy egyszerű és működő módszer, így itt bicikli nélkül is megtehetjük.

Mivel védünk valamit, ami csatlakoztatva van, a _PLUGSECURE_ konstanst hívjuk:

If (!defined("_PLUGSECURE_")) ( die("Közvetlen modulhívás tilos!"); )

Ha most megpróbálja közvetlenül elérni ezt a fájlt, semmi hasznos nem fog kijönni, ami azt jelenti, hogy a célt elértük.

Ezt követően azt javaslom, hogy rögzítsünk egy bizonyos szabványt minden modulunkhoz. Szeretnék minden modulhoz ellátni egy függvényt, amely bizonyos információkat ad vissza róla, például a modul nevét, és ezt a függvényt kötelezővé kell tenni az osztályban. A cél elérése érdekében a következőket írjuk:

Tárolóobjektum interfész ( nyilvános statikus függvény getClassName(); )

Mint ez. Most, ha összekapcsolunk egy függvény nélküli osztályt getClassName() hibaüzenetet fogunk látni. Egyelőre nem foglalkozom ezzel, később hasznos lesz számunkra, legalább teszteléshez és hibakereséshez.

Itt az ideje a kislemez-nyilvántartásunk osztályának. Kezdjük azzal, hogy deklaráljuk az osztályt és néhány változóját:

A Class Registry implementálja a StorableObject-et ( //modulnév olvasható privát statikus $className = "Registry"; //nyilvántartási példány privát statikus $példány; //objektumok tömbje privát statikus $objects = array();

Eddig minden logikus és érthető. Most, ahogy emlékszel, van egy regisztrációs adatbázisunk egyetlen tulajdonságokkal, ezért azonnal írjunk egy függvényt, amely lehetővé teszi számunkra, hogy így dolgozzunk a rendszerleíró adatbázissal:

Nyilvános statikus függvény singleton() ( if(!isset(self::$példány)) ( $obj = __CLASS__; self::$instance = new $obj; ) return self::$instance; )

Szó szerint: a függvény ellenőrzi, hogy létezik-e a rendszerleíró adatbázisunk egy példánya: ha nem, akkor létrehozza és visszaadja; ha már létezik, akkor egyszerűen visszaadja. Ebben az esetben nincs szükségünk varázslatra, ezért a védelem érdekében privátnak nyilvánítjuk:

Privát függvény __construct()() privát függvény __klón()() privát függvény __wakeup()() privát függvény __sleep() ()

Most szükségünk van egy függvényre, amellyel objektumot adhatunk a rendszerleíró adatbázisunkhoz – ezt a függvényt setternek hívják, és úgy döntöttem, hogy kétféleképpen valósítom meg, hogy megmutassam, hogyan használhatjuk a mágiát, és hogyan adhatunk alternatív módot egy objektum hozzáadására. Az első metódus egy szabványos függvény, a második az elsőt hajtja végre a __set() varázslatával.

//$object - elérési út a csatlakoztatott objektumhoz //$key - hozzáférési kulcs az objektumhoz a regiszterben nyilvános függvény addObject($key, $object) ( request_once($object); //objektum létrehozása objektumok tömbjében self::$objects[ $key] = new $key(self::$példány); ) //egy alternatív metódus mágikus nyilvános függvényen keresztül __set($key, $object) ( $this->addObject($key, $ tárgy); )

Most, hogy objektumot adjunk a rendszerleíró adatbázisunkhoz, kétféle bejegyzést használhatunk (tegyük fel, hogy már létrehoztunk egy regisztrációs példányt $registry, és szeretnénk hozzáadni a config.php fájlt):

$registry->addObject("config", "/core/config.php"); //normál metódus $registry->config = "/core/config.php"; //a PHP magic függvényen keresztül __set()

Mindkét bejegyzés ugyanazt a funkciót fogja ellátni - összekapcsolják a fájlt, létrehozzák az osztály példányát, és a kulccsal elhelyezik a regiszterben. Itt van egy fontos pont, erről a jövőben sem szabad megfeledkeznünk: a regiszterben lévő objektumkulcsnak meg kell egyeznie a csatlakoztatott objektumban lévő osztálynévvel. Ha újra megnézi a kódot, megérti, miért.

Ön dönti el, hogy melyik felvételt használja. Jobban szeretem a mágikus módszerrel történő felvételt - ez "szebb" és rövidebb.

Tehát megoldottuk az objektum hozzáadását, most szükségünk van egy funkcióra, amellyel egy összekapcsolt objektum kulcson keresztül érhető el - egy getter. Két funkcióval is megvalósítottam, hasonlóan a setterhez:

//objektum lekérése a regiszterből //$key - a tömb kulcsa nyilvános függvény getObject($key) ( //ellenőrizze, hogy a változó objektum-e if (is_object(self::$objects[$key])) ( //ha igen, akkor ezt az objektumot adjuk vissza self::$objects[$key]; ) ) //hasonló módszer a mágikus nyilvános függvényen keresztül __get($key) ( if (is_object(self::$objects[$ kulcs])) ( önmagát adja vissza: :$objects[$key]; ) )

Akárcsak a beállítónál, az objektumhoz való hozzáféréshez 2 egyenértékű bejegyzésünk lesz:

$registry->getObject("config"); //normál metódus $registry->config; //a PHP magic függvényen keresztül __get()

A figyelmes olvasó azonnal felteszi a kérdést: a __set() magic függvényben miért csak egy rendes (nem magic) objektum hozzáadás függvényt hívok meg, de a __get() getterben a getObject() függvénykódot másolom be ugyanazon hívás helyett?Őszintén szólva, erre a kérdésre nem tudok elég pontosan válaszolni, csak annyit mondok, hogy más modulokban a __get() varázslattal dolgozva voltak problémáim, de a kód „fejjel” átírásakor nincs ilyen probléma.

Talán ezért is láttam gyakran a cikkekben a PHP mágikus módszerekkel szembeni kifogásokat és tanácsokat ezek használatának elkerülésére.

"Minden varázslatnak ára van." © Rumplestiltskin

Ebben a szakaszban már készen áll a rendszerleíró adatbázisunk fő funkciója: létrehozhatjuk a registry egyetlen példányát, hozzáadhatunk objektumokat és elérhetjük azokat hagyományos módszerekkel és a PHP nyelv varázslatos módszereivel. – Mi a helyzet a törléssel?– erre a funkcióra egyelőre nem lesz szükségünk, és nem vagyok benne biztos, hogy a jövőben bármi is változni fog. Végül mindig hozzáadhatjuk a szükséges funkciókat. De ha most megpróbálunk létrehozni egy példányt a rendszerleíró adatbázisunkból,

$registry = Registry::singleton();

hibát kapunk:

Fatális hiba: Az osztálynyilvántartás 1 absztrakt metódust tartalmaz, ezért absztraktnak kell nyilvánítani, vagy a többi metódust (StorableObject::getClassName) implementálni kell a ...

Mindez azért, mert elfelejtettünk egy kötelező függvényt írni. Emlékszel, a legelején beszéltem egy függvényről, ami visszaadja a modul nevét? Ez az, amit még hozzá kell adni a teljes funkcionalitás érdekében. Ez egyszerű:

Nyilvános statikus függvény getClassName() ( return self::$className; )

Most már nem lehetnek hibák. Javaslom még egy funkció hozzáadását, nem kötelező, de előbb-utóbb jól jöhet, a jövőben ellenőrzésre, hibakeresésre fogjuk használni. A függvény visszaadja a rendszerleíró adatbázisunkhoz hozzáadott összes objektum (modul) nevét:

Nyilvános függvény getObjectsList() ( //a tömb, amelyet a $names = array() visszaadunk obj->getClassName() ; ) //adja hozzá a regisztermodul nevét a tömbhöz array_push($names, self::getClassName()); //és visszatér $names; )

Ez minden. Ezzel teljes a nyilvántartás. Ellenőrizzük a munkáját? Ellenőrzéskor csatlakoztatnunk kell valamit - legyen egy konfigurációs fájl. Hozzon létre egy új core/config.php fájlt, és adja hozzá a rendszerleíró adatbázisunk által igényelt minimális tartalmat:

//ne felejtsd el ellenőrizni az állandót if (!defined("_PLUGSECURE_")) ( die("Közvetlen modulhívás tiltva!"); ) class Config ( //modulnév, olvasható privát statikus $className = "Konfig "; nyilvános statikus függvény getClassName() ( return self::$className; ) )

Valami hasonló. Most folytassuk magával az ellenőrzéssel. A projektünk gyökerében hozzon létre egy index.php fájlt, és írja be a következő kódot:

Define("_PLUGSECURE_", true); //meghatározott egy konstanst az objektumokhoz való közvetlen hozzáférés elleni védelem érdekében. request_once "/core/registry.php"; //csatlakoztatta a regisztert $registry = Registry::singleton(); //regiszter egyszeri példányt hozott létre $registry->config = "/core/config.php"; //csatlakoztassa az eddig haszontalan konfigurációnkat //megjeleníti a csatlakoztatott modulok nevét echo " csatlakoztatva"; foreach ($registry->

  • " . $names ."
  • "; }

    Vagy ha továbbra is kerüli a varázslatot, akkor az 5. sort helyettesítheti egy alternatív módszerrel:

    Define("_PLUGSECURE_", true); //meghatározott egy konstanst az objektumokhoz való közvetlen hozzáférés elleni védelem érdekében. request_once "/core/registry.php"; //csatlakoztatta a regisztert $registry = Registry::singleton(); //egy regiszter példányt hozott létre $registry->addObject("config", "/core/config.php"); //csatlakoztassa az eddig haszontalan konfigurációnkat //megjeleníti a csatlakoztatott modulok nevét echo " csatlakoztatva"; foreach ($registry->getObjectsList() mint $names) ( echo "

  • " . $names ."
  • "; }

    Most nyissa meg a böngészőt, és írja be a címsorba a http://localhost/index.php vagy egyszerűen a http://localhost/ címet. (releváns, ha szabványos Open Server vagy hasonló webszerver beállításokat használ)

    Ennek eredményeként valami ilyesmit kell látnunk:

    Amint látja, nincs hiba, ami azt jelenti, hogy minden működik, amihez gratulálok :)

    Ma ennél megállunk. A következő cikkben visszatérünk az adatbázishoz, és írunk egy osztályt a MySQL SUDB-vel való munkavégzéshez, csatlakoztatjuk a registry-hez, és a gyakorlatban teszteljük a munkát. Találkozunk!

    Ez a minta, akárcsak a Singleton, ritkán vált ki pozitív reakciót a fejlesztők részéről, mivel ugyanazokat a problémákat veti fel az alkalmazások tesztelésekor. Ennek ellenére szidják, de aktívan használják. A Singletonhoz hasonlóan a Registry minta számos alkalmazásban megtalálható, és így vagy úgy, nagyban leegyszerűsíti bizonyos problémák megoldását.

    Vegyük sorra mindkét lehetőséget.

    Az úgynevezett „tiszta registry” vagy egyszerűen csak Registry egy statikus interfésszel rendelkező osztály megvalósítása. A fő különbség a Singleton mintától az, hogy blokkolja az osztály legalább egy példányának létrehozását. Ennek fényében nincs értelme a __clone() és __wakeup() mágikus metódusokat a privát vagy védett módosító mögé rejteni.

    Nyilvántartási osztály két statikus metódussal kell rendelkeznie - getterrel és setterrel. A beállító az átadott objektumot a tárolóba helyezi az adott kulcshoz kötötten. A getter ennek megfelelően visszaad egy tárgyat az üzletből. A bolt nem más, mint egy asszociatív kulcs-érték tömb.

    A rendszerleíró adatbázis teljes ellenőrzéséhez egy másik interfész-elem kerül bevezetésre - egy olyan módszer, amely lehetővé teszi egy objektum törlését a tárolóból.

    A Singleton-mintával megegyező problémákon kívül van még kettő:

    • egy másik típusú függőség bevezetése - a rendszerleíró kulcsokon;
    • két különböző rendszerleíró kulcs hivatkozhat ugyanarra az objektumra

    Az első esetben lehetetlen elkerülni a további függőséget. Bizonyos mértékig kötődünk a kulcsnevekhez.

    A második problémát úgy oldjuk meg, hogy bevezetünk egy ellenőrzést a Registry::set() metódusba:

    Nyilvános statikus függvénykészlet($kulcs, $elem) ( if (!array_key_exists($key, self::$_registry)) ( foreach (self::$_registry mint $val) ( if ($val === $elem) ( throw new Exception("Elem már létezik"); ) ) self::$_registry[$key] = $item; ) )

    « Tiszta Registry minta"egy másik problémát vet fel - növeli a függőséget, mivel a settert és a gettert az osztálynéven keresztül kell elérni. Nem hozhat létre hivatkozást egy objektumra, és nem dolgozhat vele, mint a Singleton-minta esetében, amikor ez a megközelítés elérhető volt:

    $példány = Singleton::getInstance(); $példány->Foo();

    Itt lehetőségünk van például egy Singleton-példányra mutató hivatkozást elmenteni az aktuális osztály egy tulajdonságába, és az OOP ideológiája által megkívánt módon dolgozni vele: paraméterként átadni az összesített objektumoknak, vagy használni a leszármazottakban.

    A probléma megoldására van Singleton Registry megvalósítás, amit sokan nem szeretnek, mert felesleges kódnak tűnik. Szerintem ennek a hozzáállásnak az oka az OOP alapelveinek valamilyen félreértése vagy azok szándékos figyelmen kívül hagyása.

    _regiszter[$kulcs] = $objektum; ) static public function get($key) ( return self::getInstance()->_registry[$key]; ) private function __wakeup() ( ) private function __construct() ( ) private function __clone() ( ) ) ?>

    Pénzmegtakarítás céljából szándékosan kihagytam a megjegyzésblokkokat a metódusoknál és tulajdonságoknál. Szerintem nem szükségesek.

    Ahogy már mondtam, az alapvető különbség az, hogy most már el lehet menteni egy hivatkozást a registry kötetre, és nem kell minden alkalommal nehézkes hívásokat statikus metódusokhoz. Ez a lehetőség számomra valamivel helyesebbnek tűnik. Az, hogy egyetértek-e a véleményemmel, nem sokat számít, csakúgy, mint maga a véleményem. A megvalósítás egyetlen finomsága sem tudja megszüntetni a mintát az említett hátrányok közül.

    Úgy döntöttem, röviden írok az életünkben gyakran használt mintákról, több példa, kevesebb víz, gyerünk.

    Szingli

    A „szingli” lényege az, hogy amikor azt mondod, hogy „telefonközpontra van szükségem”, akkor azt mondják neked, hogy „Ott már megépült”, nem pedig „Építsük újra”. A „magányos” mindig egyedül van.

    Class Singleton ( privát statikus $példány = null; privát függvény __construct())( /* ... @return Singleton */ ) // Védelem a létrehozás ellen az új Singleton privát függvényen keresztül __clone() ( /* ... @return Singleton * / ) // Védelem a létrehozás ellen klónozással privát függvény __wakeup() ( /* ... @return Singleton */ ) // Létrehozás elleni védelem unserialize nyilvános statikus függvény getInstance() ( if (is_null(self::$instance) ) ) ( self::$példány = új én; ) return self::$példány; ) )

    Nyilvántartás (nyilvántartás, bejegyzési napló)

    Ahogy a neve is sugallja, ez a minta a benne elhelyezett rekordok tárolására szolgál, és ennek megfelelően (név szerint) visszaküldi ezeket a rekordokat, ha szükséges. A telefonközpont példájában a lakosok telefonszámaira vonatkozó nyilvántartás.

    Osztálynyilvántartás ( private $registry = array(); public function set($key, $object) ( $this->registry[$key] = $objektum; ) public function get($key) ( return $this->registry [$kulcs]; ) )

    Singleton Registry- ne keverje össze)

    A „nyilvántartó” gyakran „magányos”, de ennek nem kell mindig így lennie. Például a számviteli osztályon több naplót is létrehozhatunk, az egyik alkalmazottban „A”-tól „M-ig”, a másikban „N”-től „Z-ig”. Minden ilyen folyóirat „nyilvántartás” lesz, de nem „egyetlen”, mert már 2 folyóirat létezik.

    Class SingletonRegistry ( privát statikus $példány = null; privát $registry = array(); privát függvény __construct() ( /* ... @return Singleton */ ) // Védelem a létrehozástól az új Singleton privát függvényen keresztül __clone() ( / * ... @return Singleton */ ) // Létrehozás elleni védelem klónozással privát függvény __wakeup() ( /* ... @return Singleton */ ) // Létrehozás elleni védelem unserialize nyilvános statikus függvény getInstance() ( if () is_null(self::$példány)) ( self::$példány = új én; ) return self::$példány; ) nyilvános függvénykészlet($kulcs, $objektum) ( $this->registry[$key] = $ objektum; ) nyilvános függvény get($kulcs) ( return $this->registry[$key]; ) )

    Multiton („szinglik”) vagy más szóvalRegistry Singleton ) - ne keverje össze a Singleton Registry-vel

    A „regisztert” gyakran kifejezetten „szinglik” tárolására használják. Hanem azért, mert a „nyilvántartási” minta nem „generatív minta”, hanem a „regisztert” szeretném figyelembe venni a „singleton” kapcsán.Ezért kitaláltunk egy mintát Multiton, ami szerintLényegében ez egy „nyilvántartás”, amely több „szinglit” tartalmaz, amelyek mindegyikének megvan a saját „neve”, amellyel elérhető.

    Rövid: lehetővé teszi ennek az osztálynak az objektumok létrehozását, de csak akkor, ha elnevezi az objektumot. Valós példa nincs, de a következő példát találtam az interneten:

    Osztályadatbázis ( privát statikus $példányok = array(); privát függvény __construct() ( ) privát függvény __clone() ( ) nyilvános statikus függvény getInstance($key) ( if(!array_key_exists($key, self::$példányok)) ( self::$instances[$key] = new self(); ) return self::$instances[$key]; ) ) $master = Adatbázis::getInstance("master"); var_dump($master); // object(Database)#1 (0) ( ) $logger = Adatbázis::getInstance("logger"); var_dump($logger); // object(Database)#2 (0) ( ) $masterDupe = Adatbázis::getInstance("master"); var_dump($masterDupe); // object(Database)#1 (0) ( ) // Végzetes hiba: Privát adatbázis hívása::__construct() érvénytelen kontextusból $dbFatalError = new Database(); // Végzetes PHP hiba: Privát adatbázis hívása::__clone() $dbCloneError = $masterDupe klónozás;

    Tárgykészlet

    Lényegében ez a minta „nyilvántartás”, amely csak objektumokat tárol, nem karakterláncokat, tömböket stb. adattípusok.

    Gyár

    A minta lényegét szinte teljesen leírja a neve. Ha bizonyos tárgyakat, például gyümölcsleves dobozokat kell beszereznie, nem kell tudnia, hogyan készülnek a gyárban. Egyszerűen azt mondod: „adj egy karton narancslevet”, és a „gyár” visszaküldi Önnek a szükséges csomagot. Hogyan? Mindezt maga a gyár dönti el, például egy már létező szabványt „lemásol”. A „gyár” fő célja, hogy szükség esetén lehetővé tegye a gyümölcslécsomag „megjelenésének” folyamatának megváltoztatását, és erről magának a fogyasztónak semmit sem kell közölni, hogy azt kérhesse. mint azelőtt. Általános szabály, hogy egy gyár csak egyfajta „termék” „gyártásával” foglalkozik. Nem ajánlott „légyárat” létrehozni, figyelembe véve az autógumik gyártását. Ahogy az életben, a gyári mintát is gyakran egyetlen személy hozza létre.

    Absztrakt osztály AnimalAbstract ( védett $faj; nyilvános függvény getSpecies() ($this->species; ) ) osztály Cat kiterjeszti AnimalAbstract ( védett $species = "cat"; ) osztály Kutya kiterjeszt AnimalAbstract ( védett $faj = "kutya"); ) class AnimalFactory ( nyilvános statikus függvény factory($animal) ( switch ($animal) ( case "cat": $obj = new Cat(); break; case "dog": $obj = new Dog(); break; default : throw new Exception("Az állatgyár nem tudta létrehozni a "" fajhoz tartozó állatot . $állat . """, 1000); ) return $obj; ) ) $cat = AnimalFactory::factory("cat"); // object(Cat)#1 echo $cat->getSpecies(); // macska $kutya = AnimalFactory::factory("kutya"); // object(Dog)#1 echo $dog->getSpecies(); // kutya $hippo = AnimalFactory::factory("víziló"); // Ez kivételt fog dobni

    Szeretném felhívni a figyelmet arra, hogy a gyári módszer egyben minta is, ezt hívják Gyári módszernek.

    Építő (építő)

    Tehát már megértettük, hogy a „Factory” egy italautomata, már minden készen van, és csak azt mondod, amire szükséged van. A „Builder” egy olyan üzem, amely ezeket az italokat gyártja, és minden összetett műveletet magába foglal, és igény szerint egyszerűbbekből (csomagolás, címke, víz, aromák stb.) összetett tárgyakat tud összeállítani.

    Osztály Bottle ( public $name; public $liter flakon; nyilvános függvény __construct() ( $this->bottle = new Bottle(); ) public function setName($value) ($this->bottle->name = $value; ) public function setLiters($value) ($ ez->palack->liter = $érték; ) nyilvános függvény getResult() ($ez->palack visszaküldése; ) ) $lé = new CocaColaBuilder(); $juice->setName("Coca-Cola Light"); $juice->setLiters(2); $lé->getResult();

    Prototípus

    Gyárra hasonlító, tárgyalkotásra is szolgál, de kicsit más megközelítéssel. Képzeld magad egy bárban, sört ittál, és kifogytál, azt mondod a csaposnak – csinálj nekem még egy hasonlót. A csapos viszont ránéz a sörre, amit iszol, és kérésed szerint másolatot készít. A PHP-ben már van ennek a mintának a megvalósítása, az úgynevezett .

    $újJuice = $lé klónozása;

    Lusta inicializálás

    Például egy főnök látja a különböző típusú tevékenységekhez tartozó jelentések listáját, és azt hiszi, hogy ezek a jelentések már léteznek, de valójában csak a jelentések nevei jelennek meg, és maguk a jelentések még nem jöttek létre, és csak generálásra kerülnek. megrendeléskor (például a Jelentés megtekintése gombra kattintva). A lusta inicializálás speciális esete egy objektum létrehozása az elérésekor. A Wikipédián találsz érdekeset, de... az elmélet szerint a php-ben a helyes példa például egy függvény lenne

    Adapter vagy burkolat (adapter, burkolat)

    Ez a minta teljes mértékben megfelel a nevének. Ahhoz, hogy a „szovjet” dugó az Euro-aljzaton keresztül működjön, adapterre van szükség. Pontosan ezt teszi egy „adapter” – közbenső objektumként szolgál két másik között, amelyek nem tudnak közvetlenül együttműködni egymással. A definíció ellenére a gyakorlatban még mindig látom a különbséget az Adapter és a Wrapper között.

    Class MyClass ( public function methodA() () ) class MyClassWrapper ( nyilvános függvény __construct())( $this->myClass = new MyClass(); ) public function __call($name, $arguments)( Log::info(" A(z) $name metódus meghívására készül."); return call_user_func_array(array($this->myClass, $name), $arguments); ) ) $obj = new MyClassWrapper(); $obj->methodA();

    Függőség injekció

    A függőségi befecskendezés lehetővé teszi, hogy bizonyos funkciókért a felelősség egy részét más objektumokra hárítsa át. Például, ha új munkaerőt kell felvennünk, akkor nem hozhatunk létre saját HR-osztályt, hanem bevezethetjük a toborzó cégtől való függőséget, amely viszont első kérésünkre „szükségünk van egy emberre” vagy mint egy munkaerő-kölcsönző működik. maga a HR osztály, vagy keres egy másik céget (egy „szolgáltatáskereső” segítségével), amely ezeket a szolgáltatásokat nyújtja.
    A „függőségi befecskendezés” lehetővé teszi a vállalat egyes részeinek áthelyezését és cseréjét anélkül, hogy elveszítené az általános funkcionalitást.

    Osztály AppleJuice () // ez a módszer a Dependency injekciós minta primitív megvalósítása, és a továbbiakban látni fogja ezt a függvényt getBottleJuice())( $obj = new AppleJuice AppleJuice)( return $obj; ) ) $bottleJuice = getBottleJuice();

    Most képzeljük el, hogy már nem almalevet akarunk, hanem narancslét.

    Osztály AppleJuice() Osztály Narancslé() // ez a metódus megvalósítja a függőségi befecskendezési függvényt getBottleJuice())( $obj = new Narancslé; // ellenőrizze az objektumot, hátha elcsúsztattak nekünk sört (a sör nem gyümölcslé) if($obj instanceof Narancslé)( $obj visszaküldése; ) )

    Mint látható, nemcsak a lé típusát kellett megváltoztatnunk, hanem a lé típusának ellenőrzését is, ami nem túl kényelmes. Sokkal helyesebb a függőségi inverzió elve használata:

    Interface Juice () OrangeJuice osztály a Juice () OrangeJuice osztály implementálja a Juice () függvényt getBottleJuice())( $obj = new OrangeJuice; // ellenőrizze az objektumot, hátha elcsúsztattak nekünk sört (a sör nem gyümölcslé) if($obj Például az Gyümölcslé)( $obj visszaküldése; ) )

    A függőségi inverziót néha összekeverik a függőségi injekcióval, de nem kell összekeverni őket, mert A függőségi inverzió elv, nem minta.

    Szolgáltatáskereső

    A „Szolgáltatáskereső” a „Függőséginjektálás” megvalósítási módja. Az inicializálási kódtól függően különböző típusú objektumokat ad vissza. Legyen az a feladat, hogy építő, gyári vagy valami más által készített gyümölcslé csomagunkat oda szállítsuk, ahová a vásárló akarja. Azt mondjuk a lokátornak, hogy „adjon házhozszállítást”, és megkérjük a szervizt, hogy szállítsa ki a gyümölcslevet a kívánt címre. Ma egy szolgáltatás van, holnap pedig egy másik. Számunkra nem mindegy, hogy konkrétan milyen szolgáltatásról van szó, fontos, hogy tudjuk, hogy ez a szolgáltatás azt nyújtja, amit mondunk neki, és hol mondjuk. A szolgáltatások viszont megvalósítják a „Szállítás<предмет>tovább<адрес>».

    Ha a való életről beszélünk, akkor valószínűleg jó példa a Service Locatorra a PDO PHP kiterjesztése, mert Ma MySQL adatbázissal dolgozunk, holnap pedig PostgreSQL-lel. Ahogy már megértetted, osztályunknak nem mindegy, hogy melyik adatbázisba küldi az adatait, az a fontos, hogy meg tudja tenni.

    $db = új OEM(" mysql:dbname=test;host=localhost", $felhasználó, $pass); $db = new PDO(" pgsql:dbname=test host=localhost", $felhasználó, $pass);

    A különbség a függőségi injekció és a szolgáltatáskereső között

    Ha még nem vetted volna észre, szeretném elmagyarázni. Függőség injekció Ennek eredményeként nem egy szolgáltatást ad vissza (ami tud valamit szállítani valahova), hanem egy objektumot, amelynek az adatait használja.

    Megpróbálok mesélni arról, hogyan implementáltam a Registry mintát PHP-ben. A Registry a globális változók OOP helyettesítője, amelyet adatok tárolására és rendszermodulok közötti átvitelére terveztek. Ennek megfelelően szabványos tulajdonságokkal rendelkezik - írás, olvasás, törlés. Itt van egy tipikus megvalósítás.

    Nos, így a $kulcs = $érték - Registry::set($key, $value) $key - Registry::get($key) unset($key) - Remove Registry::remove metódusok hülye cseréjét kapjuk ($key ) Egyszerűen nem világos, miért ez az extra kód. Tehát tanítsuk meg az osztályunkat arra, amire a globális változók nem képesek. Adjunk hozzá borsot.

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

    A minta tipikus feladataihoz hozzáadtam a változó változásának blokkolását, ez nagyon kényelmes nagy projekteknél, véletlenül nem kell beszúrni semmit. Például kényelmes az adatbázisokkal való munkához
    define('DB_DNS', 'mysql:host=localhost;dbname= ’);
    define('DB_USER', ' ’);
    define('DB_PASSWORD',' ’);
    define('DB_HANDLE');

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

    Most a kód magyarázatához, az adatok tárolására a $data statikus változót használjuk, a $lock változó pedig a változtatás miatt zárolt kulcsokról tárol adatokat. A hálózatban ellenőrizzük, hogy a változó zárolva van-e, és módosítjuk vagy hozzáadjuk a regiszterhez. Törléskor a zárolást is ellenőrizzük, a getter az alapértelmezett opcionális paraméter kivételével változatlan marad. Nos, érdemes odafigyelni a kivételkezelésre,amit valamiért ritkán használnak.Egyébként már van egy tervezetem a kivételekről,várd a cikket. Alább egy kódvázlat a teszteléshez, itt egy cikk a tesztelésről, azt sem ártana megírni, bár nem vagyok a TDD híve.

    A következő cikkben tovább bővítjük a funkcionalitást az adatok inicializálásával és a „lustaság” megvalósításával.