Ang problema ng pagsisimula ng object sa mga OOP application sa PHP. Paghahanap ng solusyon gamit ang Registry, Factory Method, Service Locator at Dependency Injection pattern. OOP pattern na may mga halimbawa at paglalarawan Authoritative registro php

Ang problema ng pagsisimula ng object sa mga OOP application sa PHP. Paghahanap ng solusyon gamit ang Registry, Factory Method, Service Locator at Dependency Injection pattern

Nagkataon lang na pinagsama-sama ng mga programmer ang mga matagumpay na solusyon sa anyo ng mga pattern ng disenyo. Mayroong maraming panitikan sa mga pattern. Ang aklat ng Gang of Four na "Design Patterns" nina Erich Gamma, Richard Helm, Ralph Johnson at John Vlissides" at, marahil, "Patterns of Enterprise Application Architecture" ni Martin Fowler ay tiyak na itinuturing na mga klasiko. Ang pinakamagandang bagay na nabasa ko na may mga halimbawa sa PHP - ito. Nagkataon lang na ang lahat ng literatura na ito ay medyo kumplikado para sa mga taong nagsisimula pa lamang sa pag-master ng OOP. Kaya nagkaroon ako ng ideya na ipakita ang ilan sa mga pattern na sa tingin ko ay pinaka-kapaki-pakinabang sa isang pinasimpleng anyo. Sa iba salita, ang artikulong ito ang una kong pagtatangka na bigyang-kahulugan ang mga pattern ng disenyo sa istilong KISS.
Ngayon ay pag-uusapan natin kung anong mga problema ang maaaring lumitaw sa pagsisimula ng object sa isang OOP application at kung paano mo magagamit ang ilang sikat na pattern ng disenyo upang malutas ang mga problemang ito.

Halimbawa

Gumagana ang modernong OOP application sa sampu, daan-daan, at kung minsan ay libu-libong mga bagay. Buweno, tingnan natin nang maigi kung paano sinisimulan ang mga bagay na ito sa aming mga application. Ang pagsisimula ng object ay ang tanging aspeto na interesado sa amin sa artikulong ito, kaya nagpasya akong alisin ang lahat ng "dagdag" na pagpapatupad.
Sabihin nating gumawa kami ng super-duper kapaki-pakinabang na klase na maaaring magpadala ng kahilingan sa GET sa isang partikular na URI at magbalik ng HTML mula sa tugon ng server. Upang ang aming klase ay hindi mukhang masyadong simple, hayaan itong suriin din ang resulta at magtapon ng isang pagbubukod kung ang server ay tumugon ng "mali".

Class Grabber ( public function get($url) (/** nagbabalik ng HTML code o naghagis ng exception */) )

Gumawa tayo ng isa pang klase na ang mga object ay magiging responsable para sa pag-filter ng natanggap na HTML. Kinukuha ng paraan ng filter ang HTML code at isang CSS selector bilang mga argumento, at ibinabalik nito ang hanay ng mga elementong natagpuan para sa ibinigay na selector.

Class HtmlExtractor ( public function filter ($html, $selector) (/** nagbabalik ng hanay ng mga na-filter na elemento */) )

Ngayon, isipin na kailangan nating makakuha ng mga resulta ng paghahanap sa Google para sa mga ibinigay na keyword. Para magawa ito, ipapakilala namin ang isa pang klase na gagamit ng klase ng Grabber para magpadala ng kahilingan, at ang klase ng HtmlExtractor para kunin ang kinakailangang nilalaman. Maglalaman din ito ng lohika para sa pagbuo ng URI, isang tagapili para sa pag-filter ng natanggap na HTML, at pagproseso ng mga resultang nakuha.

Class GoogleFinder ( private $grabber; private $filter; public function __construct() ($this->grabber = new Grabber(); $this->filter = new HtmlExtractor(); ) public function find($searchString) ( /* * nagbabalik ng hanay ng mga naitatag na resulta */) )

Napansin mo ba na ang pagsisimula ng mga bagay na Grabber at HtmlExtractor ay nasa constructor ng klase ng GoogleFinder? Isipin natin kung gaano kahusay ang desisyong ito.
Siyempre, hindi magandang ideya ang hardcoding sa paglikha ng mga bagay sa isang constructor. At dahil jan. Una, hindi namin madaling ma-override ang Grabber class sa environment ng pagsubok para maiwasan ang pagpapadala ng totoong kahilingan. Upang maging patas, ito ay nagkakahalaga ng pagsasabing magagawa ito gamit ang Reflection API. Yung. ang teknikal na posibilidad ay umiiral, ngunit ito ay malayo sa pinaka maginhawa at malinaw na paraan.
Pangalawa, ang parehong problema ay lilitaw kung gusto naming muling gamitin ang GoogleFinder logic sa iba pang mga pagpapatupad ng Grabber at HtmlExtractor. Ang paglikha ng mga dependency ay naka-hardcode sa tagabuo ng klase. At sa pinakamagandang kaso, magagawa nating mamanahin ang GoogleFinder at ma-override ang constructor nito. At kahit na, kung ang saklaw ng grabber at filter na mga katangian ay protektado o pampubliko.
Isang huling punto, sa bawat oras na gumawa kami ng bagong object ng GoogleFinder, isang bagong pares ng mga dependency na object ang gagawin sa memorya, bagama't madali naming magagamit ang isang Grabber object at isang HtmlExtractor object sa ilang GoogleFinder object.
Sa tingin ko naiintindihan mo na na ang pagsisimula ng dependency ay kailangang ilipat sa labas ng klase. Maaari naming hilingin na ang mga nakahanda nang dependency ay ipasa sa constructor ng klase ng GoogleFinder.

Class GoogleFinder ( private $grabber; private $filter; public function __construct(Grabber $grabber, HtmlExtractor $filter) ($this->grabber = $grabber; $this->filter = $filter; ) public function find($searchString) ( /** nagbabalik ng hanay ng mga naitatag na resulta */) )

Kung gusto naming bigyan ang ibang mga developer ng kakayahang magdagdag at gumamit ng sarili nilang mga pagpapatupad ng Grabber at HtmlExtractor, dapat naming isaalang-alang ang pagpapakilala ng mga interface para sa kanila. Sa kasong ito, ito ay hindi lamang kapaki-pakinabang, ngunit kinakailangan din. Naniniwala ako na kung gagamit lamang tayo ng isang pagpapatupad sa isang proyekto at hindi inaasahan na lumikha ng mga bago sa hinaharap, dapat nating tumanggi na lumikha ng isang interface. Mas mainam na kumilos ayon sa sitwasyon at gumawa ng simpleng refactoring kapag talagang kailangan ito.
Ngayon ay mayroon na tayong lahat ng kinakailangang klase at magagamit natin ang klase ng GoogleFinder sa controller.

Class Controller ( public function action() ( /* Some stuff */ $finder = new GoogleFinder(new Grabber(), new HtmlExtractor()); $results = $finder->

Isa-isahin natin ang mga intermediate na resulta. Nagsulat kami ng napakakaunting code, at sa unang tingin, wala kaming ginawang mali. Ngunit... paano kung kailangan nating gumamit ng bagay tulad ng GoogleFinder sa ibang lugar? Kailangan nating duplicate ang paglikha nito. Sa aming halimbawa, ito ay isang linya lamang at ang problema ay hindi gaanong kapansin-pansin. Sa pagsasagawa, ang pagsisimula ng mga bagay ay maaaring maging kumplikado at maaaring tumagal ng hanggang 10 linya, o higit pa. Lumilitaw din ang iba pang mga problemang tipikal ng pagdoble ng code. Kung sa panahon ng proseso ng refactoring kailangan mong baguhin ang pangalan ng klase na ginamit o ang object initialization logic, kakailanganin mong manu-manong baguhin ang lahat ng mga lugar. Sa tingin ko alam mo kung paano ito nangyayari :)
Karaniwan, ang hardcode ay tinatalakay nang simple. Ang mga dobleng halaga ay karaniwang kasama sa pagsasaayos. Pinapayagan ka nitong baguhin ang mga halaga sa gitna ng lahat ng lugar kung saan ginagamit ang mga ito.

Template ng rehistro.

Kaya, nagpasya kaming ilipat ang paglikha ng mga bagay sa pagsasaayos. Gawin natin yan.

$registry = bagong ArrayObject(); $registry["grabber"] = bagong Grabber(); $registry["filter"] = bagong HtmlExtractor(); $registry["google_finder"] = bagong GoogleFinder($registry["grabber"], $registry["filter"]);
Ang kailangan lang nating gawin ay ipasa ang ating ArrayObject sa controller at malulutas ang problema.

Class Controller ( private $registry; public function __construct(ArrayObject $registry) ($this->registry = $registry; ) public function action() ( /* Some stuff */ $results = $this->registry["google_finder" ]->find("search string"); /* Gumawa ng isang bagay na may mga resulta */ ) )

Mapapaunlad pa natin ang ideya ng Registry. Magmana ng ArrayObject, i-encapsulate ang paglikha ng mga bagay sa loob ng isang bagong klase, ipagbawal ang pagdaragdag ng mga bagong bagay pagkatapos ng pagsisimula, atbp. Ngunit sa aking opinyon, ang ibinigay na code ay ganap na nilinaw kung ano ang template ng Registry. Ang pattern na ito ay hindi generative, ngunit napupunta ito sa ilang paraan upang malutas ang ating mga problema. Ang Registry ay isang lalagyan lamang kung saan maaari tayong mag-imbak ng mga bagay at ipasa ang mga ito sa loob ng application. Upang maging available ang mga bagay, kailangan muna nating gawin ang mga ito at irehistro ang mga ito sa lalagyang ito. Tingnan natin ang mga pakinabang at disadvantages ng diskarteng ito.
Sa unang tingin, nakamit na natin ang ating layunin. Itinigil namin ang hardcoding ng mga pangalan ng klase at lumikha ng mga bagay sa isang lugar. Lumilikha kami ng mga bagay sa isang kopya, na ginagarantiyahan ang kanilang muling paggamit. Kung nagbabago ang lohika para sa paglikha ng mga bagay, isang lugar lamang sa application ang kailangang i-edit. Bilang isang bonus, natanggap namin ang kakayahang sentral na pamahalaan ang mga bagay sa Registry. Madali kaming makakakuha ng isang listahan ng lahat ng magagamit na mga bagay at magsagawa ng ilang mga manipulasyon sa kanila. Tingnan natin ngayon kung ano ang maaaring hindi natin magustuhan sa template na ito.
Una, dapat nating likhain ang bagay bago ito irehistro sa Registry. Alinsunod dito, may mataas na posibilidad na lumikha ng "mga hindi kinakailangang bagay", i.e. yaong mga malilikha sa memorya, ngunit hindi gagamitin sa application. Oo, maaari kaming magdagdag ng mga bagay sa Registry nang pabago-bago, i.e. lumikha lamang ng mga bagay na kailangan upang iproseso ang isang partikular na kahilingan. Sa isang paraan o iba pa, kakailanganin nating kontrolin ito nang manu-mano. Alinsunod dito, sa paglipas ng panahon ito ay magiging napakahirap na mapanatili.
Pangalawa, mayroon kaming bagong dependency ng controller. Oo, makakatanggap kami ng mga bagay sa pamamagitan ng isang static na pamamaraan sa Registry para hindi na namin kailangang ipasa ang Registry sa constructor. Ngunit sa aking opinyon, hindi mo dapat gawin ito. Ang mga static na pamamaraan ay isang mas mahigpit na koneksyon kaysa sa paglikha ng mga dependency sa loob ng isang bagay, at mga kahirapan sa pagsubok (sa paksang ito).
Pangatlo, walang sinasabi sa amin ang controller interface tungkol sa kung anong mga bagay ang ginagamit nito. Maaari kaming makakuha ng anumang bagay na magagamit sa Registry sa controller. Mahihirapan kaming sabihin kung aling mga bagay ang ginagamit ng controller hanggang sa suriin namin ang lahat ng source code nito.

Paraan ng Pabrika

Ang aming pinakamalaking hinaing sa Registry ay ang isang bagay ay dapat masimulan bago ito ma-access. Sa halip na simulan ang isang bagay sa pagsasaayos, maaari nating paghiwalayin ang lohika para sa paglikha ng mga bagay sa isa pang klase, na maaari nating "hilingin" na buuin ang bagay na kailangan natin. Ang mga klase na may pananagutan sa paglikha ng mga bagay ay tinatawag na mga pabrika. At ang pattern ng disenyo ay tinatawag na Factory Method. Tingnan natin ang isang halimbawa ng pabrika.

Class Factory ( public function getGoogleFinder() ( return new GoogleFinder($this->getGrabber(), $this->getHtmlExtractor()); ) private function getGrabber() ( return new Grabber(); ) private function getHtmlExtractor() ( ibalik ang bagong HtmlFiletr(); ) )

Bilang isang patakaran, ang mga pabrika ay ginawa na may pananagutan sa paglikha ng isang uri ng bagay. Minsan ang isang pabrika ay maaaring lumikha ng isang pangkat ng mga kaugnay na bagay. Maaari naming gamitin ang pag-cache sa isang property upang maiwasan ang muling paglikha ng mga bagay.

Class Factory ( private $finder; public function getGoogleFinder() ( if (null === $this->finder) ($this->finder = new GoogleFinder($this->getGrabber(), $this->getHtmlExtractor() ); ) ibalik ang $this->finder; ) )

Maaari naming i-parameter ang isang factory method at italaga ang initialization sa ibang mga pabrika depende sa papasok na parameter. Ito ay magiging isang Abstract Factory na template.
Kung kailangan nating gawing modularize ang application, maaari nating hilingin sa bawat module na magbigay ng sarili nitong mga pabrika. Maaari pa nating paunlarin ang tema ng mga pabrika, ngunit sa palagay ko ay malinaw ang kakanyahan ng template na ito. Tingnan natin kung paano natin gagamitin ang pabrika sa controller.

Class Controller ( private $factory; public function __construct(Factory $factory) ( $this->factory = $factory; ) public function action() ( /* Some stuff */ $results = $this->factory->getGoogleFinder( )->find("search string"); /* Gumawa ng isang bagay na may mga resulta */ ) )

Kasama sa mga bentahe ng diskarteng ito ang pagiging simple nito. Ang aming mga bagay ay tahasang nilikha, at ang iyong IDE ay madaling magdadala sa iyo sa lugar kung saan ito nangyayari. Nalutas din namin ang problema sa Registry upang ang mga bagay sa memorya ay malilikha lamang kapag "tinanong" namin ang pabrika na gawin ito. Ngunit hindi pa namin napagpasyahan kung paano ibibigay ang mga kinakailangang pabrika sa mga controllers. Mayroong ilang mga pagpipilian dito. Maaari kang gumamit ng mga static na pamamaraan. Maaari naming hayaan ang mga controllers na sila mismo ang gumawa ng mga kinakailangang pabrika at mapawalang-bisa ang lahat ng aming mga pagtatangka na alisin ang copy-paste. Maaari kang lumikha ng isang pabrika ng mga pabrika at ipasa lamang iyon sa controller. Ngunit ang pagkuha ng mga bagay sa controller ay magiging mas kumplikado, at kakailanganin mong pamahalaan ang mga dependency sa pagitan ng mga pabrika. Bilang karagdagan, hindi lubos na malinaw kung ano ang gagawin kung gusto naming gumamit ng mga module sa aming aplikasyon, kung paano magrehistro ng mga pabrika ng module, kung paano pamahalaan ang mga koneksyon sa pagitan ng mga pabrika mula sa iba't ibang mga module. Sa pangkalahatan, nawala ang pangunahing bentahe ng pabrika - ang tahasang paglikha ng mga bagay. At hindi pa rin namin nalutas ang problema ng "implicit" na interface ng controller.

Tagahanap ng Serbisyo

Ang template ng Service Locator ay nagbibigay-daan sa iyo upang malutas ang kakulangan ng fragmentation ng mga pabrika at pamahalaan ang paglikha ng mga bagay nang awtomatiko at sa gitna. Kung iisipin natin ito, maaari tayong magpakilala ng karagdagang abstraction layer na magiging responsable sa paglikha ng mga bagay sa ating aplikasyon at pamamahala sa mga ugnayan sa pagitan ng mga bagay na ito. Upang ang layer na ito ay makagawa ng mga bagay para sa atin, kakailanganin nating bigyan ito ng kaalaman kung paano ito gagawin.
Mga tuntunin ng pattern ng Tagahanap ng Serbisyo:
  • Ang serbisyo ay isang handa na bagay na maaaring makuha mula sa isang lalagyan.
  • Depinisyon ng Serbisyo – lohika ng pagsisimula ng serbisyo.
  • Ang lalagyan (Service Container) ay isang pangunahing bagay na nag-iimbak ng lahat ng paglalarawan at maaaring lumikha ng mga serbisyo batay sa mga ito.
Maaaring irehistro ng anumang module ang mga paglalarawan ng serbisyo nito. Upang makakuha ng ilang serbisyo mula sa lalagyan, kakailanganin naming hilingin ito sa pamamagitan ng susi. Mayroong maraming mga opsyon para sa pagpapatupad ng Service Locator; sa pinakasimpleng bersyon, maaari naming gamitin ang ArrayObject bilang isang lalagyan at isang pagsasara bilang isang paglalarawan ng mga serbisyo.

Pinapalawak ng Class ServiceContainer ang ArrayObject ( public function get($key) ( if (is_callable ($this[$key])) ( return call_user_func ($this[$key]); ) throw new \RuntimeException("Hindi mahanap ang kahulugan ng serbisyo sa ilalim ang susi [ $key ]"); ))

Pagkatapos ang pagpaparehistro ng Mga Kahulugan ay magiging ganito:

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

At ang paggamit sa controller ay ganito:

Class Controller ( private $container; public function __construct(ServiceContainer $container) ( $this->container = $container; ) public function action() ( /* Some stuff */ $results = $this->container->get( "google_finder")->find("search string"); /* Gumawa ng isang bagay na may mga resulta */ ) )

Ang isang Service Container ay maaaring napakasimple, o maaari itong maging napakakumplikado. Halimbawa, ang Symfony Service Container ay nagbibigay ng maraming feature: mga parameter, saklaw ng mga serbisyo, paghahanap ng mga serbisyo sa pamamagitan ng mga tag, alias, pribadong serbisyo, ang kakayahang gumawa ng mga pagbabago sa container pagkatapos idagdag ang lahat ng serbisyo (compiller pass) at marami pa. Pinalawak pa ng DIExtraBundle ang mga kakayahan ng karaniwang pagpapatupad.
Ngunit bumalik tayo sa ating halimbawa. Gaya ng nakikita mo, hindi lamang nireresolba ng Service Locator ang lahat ng parehong problema gaya ng mga nakaraang template, ngunit pinapadali rin nitong gamitin ang mga module na may sariling mga kahulugan ng serbisyo.
Bilang karagdagan, sa antas ng balangkas nakatanggap kami ng karagdagang antas ng abstraction. Lalo na, sa pamamagitan ng pagpapalit ng ServiceContainer::get method, maaari nating, halimbawa, palitan ang object ng isang proxy. At ang saklaw ng aplikasyon ng mga proxy na bagay ay limitado lamang sa imahinasyon ng developer. Dito maaari mong ipatupad ang paradigm ng AOP, LazyLoading, atbp.
Ngunit itinuturing pa rin ng karamihan sa mga developer ang Service Locator bilang isang anti-pattern. Dahil, sa teorya, maaari tayong magkaroon ng maraming tinatawag Mga klase sa Container Aware (ibig sabihin, mga klase na naglalaman ng reference sa container). Halimbawa, ang aming Controller, kung saan maaari kaming makakuha ng anumang serbisyo.
Tingnan natin kung bakit ito masama.
Una, pagsubok muli. Sa halip na gumawa ng mga pangungutya para lamang sa mga klase na ginagamit sa mga pagsusulit, kakailanganin mong kutyain ang buong lalagyan o gumamit ng isang tunay na lalagyan. Ang unang pagpipilian ay hindi angkop sa iyo, dahil... kailangan mong magsulat ng maraming hindi kinakailangang code sa mga pagsubok, pangalawa, dahil ito ay sumasalungat sa mga prinsipyo ng unit testing, at maaaring humantong sa mga karagdagang gastos para sa pagpapanatili ng mga pagsubok.
Pangalawa, mahihirapan tayong mag-refactor. Sa pamamagitan ng pagpapalit ng anumang serbisyo (o ServiceDefinition) sa container, mapipilitan kaming suriin din ang lahat ng mga serbisyong umaasa. At ang problemang ito ay hindi malulutas sa tulong ng isang IDE. Ang paghahanap ng mga ganoong lugar sa buong application ay hindi magiging madali. Bilang karagdagan sa mga serbisyong umaasa, kakailanganin mo ring suriin ang lahat ng mga lugar kung saan nakuha ang refactored na serbisyo mula sa lalagyan.
Well, ang pangatlong dahilan ay ang hindi makontrol na paghila ng mga serbisyo mula sa lalagyan ay maaga o huli ay hahantong sa isang gulo sa code at hindi kinakailangang pagkalito. Mahirap itong ipaliwanag, kakailanganin mo lamang na gumugol ng mas maraming oras upang maunawaan kung paano gumagana ito o ang serbisyong iyon, sa madaling salita, lubos mong mauunawaan kung ano ang ginagawa nito o kung paano gumagana ang isang klase sa pamamagitan lamang ng pagbabasa ng buong source code nito.

Dependency Injection

Ano pa ang maaari mong gawin upang limitahan ang paggamit ng isang lalagyan sa isang application? Maaari mong ilipat ang kontrol sa paglikha ng lahat ng object ng user, kabilang ang mga controller, sa framework. Sa madaling salita, hindi dapat tawagan ng user code ang paraan ng pagkuha ng container. Sa aming halimbawa, maaari kaming magdagdag ng Depinisyon para sa controller sa lalagyan:

$container["google_finder"] = function() use ($container) ( ibalik ang bagong Controller(Grabber $grabber); );

At alisin ang lalagyan sa controller:

Class Controller ( private $finder; public function __construct(GoogleFinder $finder) ( $this->finder = $finder; ) public function action() ( /* Some stuff */ $results = $this->finder->find( "search string"); /* Gumawa ng isang bagay na may mga resulta */ ) )

Ang diskarte na ito (kapag ang access sa Service Container ay hindi ibinigay sa mga klase ng kliyente) ay tinatawag na Dependency Injection. Ngunit ang template na ito ay mayroon ding parehong mga pakinabang at disadvantages. Hangga't sumusunod kami sa prinsipyo ng solong responsibilidad, ang code ay mukhang napakaganda. Una sa lahat, inalis namin ang lalagyan sa mga klase ng kliyente, na ginagawang mas malinaw at mas simple ang kanilang code. Madali nating masusubok ang controller sa pamamagitan ng pagpapalit ng mga kinakailangang dependencies. Maaari naming gawin at subukan ang bawat klase nang hiwalay sa iba (kabilang ang mga klase ng controller) gamit ang TDD o BDD na diskarte. Kapag gumagawa ng mga pagsubok, maaari tayong mag-abstract mula sa lalagyan, at sa paglaon ay magdagdag ng Depinisyon kapag kailangan nating gumamit ng mga partikular na pagkakataon. Ang lahat ng ito ay gagawing mas simple at mas malinaw ang aming code, at mas transparent ang pagsubok.
Ngunit kinakailangang banggitin ang kabilang panig ng barya. Ang katotohanan ay ang mga controllers ay napaka tiyak na mga klase. Magsimula tayo sa katotohanan na ang controller, bilang panuntunan, ay naglalaman ng isang hanay ng mga aksyon, na nangangahulugang nilalabag nito ang prinsipyo ng solong responsibilidad. Bilang resulta, ang klase ng controller ay maaaring magkaroon ng mas maraming dependencies kaysa sa kinakailangan upang maisagawa ang isang partikular na aksyon. Ang paggamit ng tamad na pagsisimula (ang bagay ay na-instantiate sa oras ng unang paggamit nito, at bago iyon gumamit ng magaan na proxy) ay malulutas ang isyu sa pagganap sa ilang lawak. Ngunit mula sa isang punto ng arkitektura, ang paglikha ng maraming dependencies sa isang controller ay hindi rin ganap na tama. Bilang karagdagan, ang pagsubok sa mga controller ay karaniwang isang hindi kinakailangang operasyon. Ang lahat, siyempre, ay nakasalalay sa kung paano isinaayos ang pagsubok sa iyong aplikasyon at kung ano ang nararamdaman mo tungkol dito.
Mula sa nakaraang talata, napagtanto mo na ang paggamit ng Dependency Injection ay hindi ganap na nag-aalis ng mga problema sa arkitektura. Samakatuwid, isipin kung paano ito magiging mas maginhawa para sa iyo, kung mag-imbak ng isang link sa lalagyan sa mga controllers o hindi. Walang iisang tamang solusyon dito. Sa tingin ko ang parehong mga diskarte ay mabuti hangga't ang controller code ay nananatiling simple. Ngunit, tiyak, hindi ka dapat gumawa ng mga serbisyo ng Conatiner Aware bilang karagdagan sa mga controller.

mga konklusyon

Buweno, dumating na ang oras upang buod ang lahat ng sinabi. At ang daming nasabi... :)
Kaya, upang ayusin ang gawain ng paglikha ng mga bagay, maaari naming gamitin ang mga sumusunod na pattern:
  • Pagpapatala: Ang template ay may malinaw na mga disadvantages, ang pinakapangunahing nito ay ang pangangailangan na lumikha ng mga bagay bago ilagay ang mga ito sa isang karaniwang lalagyan. Malinaw, mas maraming problema ang makukuha natin kaysa sa mga benepisyo mula sa paggamit nito. Malinaw na hindi ito ang pinakamahusay na paggamit ng template.
  • Paraan ng Pabrika: Ang pangunahing bentahe ng pattern: ang mga bagay ay tahasang nilikha. Ang pangunahing kawalan: ang mga controller ay kailangang mag-alala tungkol sa paggawa ng mga pabrika mismo, na hindi ganap na malulutas ang problema ng mga pangalan ng klase na na-hardcode, o ang balangkas ay dapat na responsable para sa pagbibigay sa mga controller ng lahat ng kinakailangang mga pabrika, na hindi masyadong halata. Walang posibilidad na sentral na pamahalaan ang proseso ng paglikha ng mga bagay.
  • Tagahanap ng Serbisyo: Isang mas advanced na paraan upang kontrolin ang paglikha ng mga bagay. Ang isang karagdagang antas ng abstraction ay maaaring magamit upang i-automate ang mga karaniwang gawain na nakatagpo kapag lumilikha ng mga bagay. Halimbawa:
    class ServiceContainer extends ArrayObject ( public function get ($ key) ( if (is_callable ($this[$key])) ( $obj = call_user_func ($this[$key]); if ($obj instanceof RequestAwareInterface) ($obj- >setRequest($this->get("request")); ) return $obj; ) throw new \RuntimeException("Hindi mahanap ang kahulugan ng serbisyo sa ilalim ng key [ $key ]"); ) )
    Ang disadvantage ng Service Locator ay ang pampublikong API ng mga klase ay hindi na nagiging impormasyon. Kinakailangang basahin ang buong code ng klase upang maunawaan kung anong mga serbisyo ang ginagamit dito. Ang isang klase na naglalaman ng isang reference sa isang lalagyan ay mas mahirap subukan.
  • Dependency Injection: Mahalagang magagamit namin ang parehong Lalagyan ng Serbisyo tulad ng para sa nakaraang pattern. Ang pagkakaiba ay kung paano ginagamit ang lalagyang ito. Kung iiwasan nating gawing nakadepende ang mga klase sa container, makakakuha tayo ng malinaw at tahasang class API.
Hindi lang ito ang nais kong sabihin sa iyo tungkol sa problema ng paglikha ng mga bagay sa mga aplikasyon ng PHP. Mayroon ding pattern ng Prototype, hindi namin isinasaalang-alang ang paggamit ng Reflection API, iniwan namin ang problema ng tamad na pag-load ng mga serbisyo at maraming iba pang mga nuances. Ang artikulo ay naging medyo mahaba, kaya't tatapusin ko ito :)
Nais kong ipakita na ang Dependency Injection at iba pang mga pattern ay hindi kasing kumplikado gaya ng karaniwang pinaniniwalaan.
Kung pinag-uusapan natin ang Dependency Injection, may mga KISS na pagpapatupad ng pattern na ito, halimbawa

Pagpindot sa istraktura ng hinaharap na database. Nagawa na ang pagsisimula, at hindi na tayo maaaring umatras, at hindi ko na iniisip ito.

Babalik kami sa database mamaya, ngunit sa ngayon ay magsisimula kaming isulat ang code para sa aming makina. Ngunit una, kaunting hardware. Magsimula.

Ang simula ng panahon

Sa ngayon, mayroon lang tayong ilang ideya at pang-unawa sa pagpapatakbo ng sistema na nais nating ipatupad, ngunit wala pang mismong pagpapatupad. Wala kaming magagawa: wala kaming anumang pag-andar - at, tulad ng naaalala mo, hinati namin ito sa 2 bahagi: panloob at panlabas. Ang alpabeto ay nangangailangan ng mga titik, ngunit ang panlabas na pagpapagana ay nangangailangan ng panloob na pagpapagana—diyan tayo magsisimula.

Ngunit hindi ganoon kabilis. Para gumana ito kailangan mong lumalim nang kaunti. Ang aming system ay kumakatawan sa isang hierarchy, at anumang hierarchical system ay may simula: isang mount point sa Linux, isang lokal na disk sa Windows, isang sistema ng isang estado, isang kumpanya, isang institusyong pang-edukasyon, atbp. Ang bawat elemento ng naturang sistema ay nasasakupan ng isang tao at maaaring magkaroon ng ilang mga subordinates, at upang matugunan ang mga kapitbahay nito at ang kanilang mga subordinates ay gumagamit ito ng mga nakatataas o ang simula mismo. Ang isang magandang halimbawa ng isang hierarchical system ay isang family tree: isang panimulang punto ang pinili - ilang ninuno - at umalis tayo. Sa aming system, kailangan din namin ng panimulang punto kung saan kami magpapalago ng mga sanga - mga module, plugin, atbp. Kailangan namin ng ilang uri ng interface kung saan ang lahat ng aming mga module ay "makipag-usap". Para sa karagdagang trabaho, kailangan nating pamilyar sa konsepto " pattern ng disenyo" at ilan sa kanilang mga pagpapatupad.

Mga Pattern ng Disenyo

Mayroong napakaraming artikulo tungkol sa kung ano ito at kung anong mga uri ang mayroon; ang paksa ay medyo hackneyed at hindi ko sasabihin sa iyo ang anumang bago. Sa aking paboritong Wiki mayroong impormasyon sa paksang ito: isang karwahe na may slide at kaunti pa.

Ang mga pattern ng disenyo ay madalas ding tinatawag na mga pattern ng disenyo o simpleng mga pattern (mula sa salitang Ingles na pattern, isinalin na nangangahulugang "pattern"). Dagdag pa sa mga artikulo, kapag pinag-uusapan ko ang mga pattern, ang ibig kong sabihin ay mga pattern ng disenyo.

Mula sa malaking listahan ng lahat ng uri ng nakakatakot (at hindi masyadong nakakatakot) na mga pangalan ng pattern, dalawa lang ang interesado kami sa ngayon: registry at singleton.

Pagpapatala (o magparehistro) ay isang pattern na gumagana sa isang partikular na array kung saan maaari kang magdagdag at mag-alis ng isang tiyak na hanay ng mga bagay at makakuha ng access sa alinman sa mga ito at sa mga kakayahan nito.

Loner (o singleton) ay isang pattern na nagsisiguro na isang instance lamang ng isang klase ang maaaring umiral. Hindi ito maaaring kopyahin, patulugin o gisingin (pag-uusapan tungkol sa mahika ng PHP: __clone(), __sleep(), __wakeup()). Ang Singleton ay may pandaigdigang access point.

Ang mga kahulugan ay hindi kumpleto o pangkalahatan, ngunit ito ay sapat na para sa pag-unawa. Hindi namin kailangan ang mga ito nang hiwalay pa rin. Interesado kami sa mga kakayahan ng bawat isa sa mga pattern na ito, ngunit sa isang klase: ang gayong pattern ay tinatawag singleton registry o Singleton Registry.

Ano ang ibibigay nito sa atin?
  • Kami ay garantisadong magkakaroon ng isang halimbawa ng registry, kung saan maaari kaming magdagdag ng mga bagay anumang oras at gamitin ang mga ito mula sa kahit saan sa code;
  • magiging imposibleng kopyahin ito at gumamit ng iba pang hindi gustong (sa kasong ito) magic ng PHP language.

Sa yugtong ito, sapat na upang maunawaan na ang isang solong pagpapatala ay magbibigay-daan sa amin upang ipatupad ang isang modular na istraktura ng system, na kung saan ay kung ano ang gusto namin kapag tinatalakay ang mga layunin sa , at mauunawaan mo ang natitira habang umuunlad ang pag-unlad.

Well, sapat na mga salita, gumawa tayo!

Mga unang linya

Dahil ang klase na ito ay may kaugnayan sa functionality ng kernel, magsisimula tayo sa pamamagitan ng paglikha ng isang folder sa ugat ng ating proyekto na tinatawag na core kung saan ilalagay natin ang lahat ng klase ng kernel modules. Nagsisimula tayo sa registry, kaya tawagan natin ang file na registry.php

Hindi kami interesado sa posibilidad na ang isang mausisa na gumagamit ay magpasok ng isang direktang address sa aming file sa linya ng browser, kaya kailangan naming protektahan ang aming sarili mula dito. Upang makamit ang layuning ito, kailangan lang nating tukuyin ang isang tiyak na pare-pareho sa pangunahing maipapatupad na file, na susuriin natin. Ang ideya ay hindi bago; sa pagkakatanda ko, ginamit ito sa Joomla. Ito ay isang simple at gumaganang paraan, kaya maaari naming gawin nang walang mga bisikleta dito.

Dahil pinoprotektahan namin ang isang bagay na konektado, tatawagin namin ang constant na _PLUGSECURE_ :

If (!defined("_PLUGSECURE_")) ( die("Direct module call is prohibited!"); )

Ngayon, kung susubukan mong direktang i-access ang file na ito, walang lalabas na kapaki-pakinabang, na nangangahulugan na ang layunin ay nakamit.

Susunod, iminumungkahi kong magtakda ng isang tiyak na pamantayan para sa lahat ng aming mga module. Nais kong bigyan ang bawat module ng isang function na magbabalik ng ilang impormasyon tungkol dito, tulad ng pangalan ng module, at ang function na ito ay dapat na kinakailangan sa klase. Upang makamit ang layuning ito, isinusulat namin ang sumusunod:

Interface StorableObject ( public static function getClassName(); )

Ganito. Ngayon, kung ikinonekta namin ang anumang klase nang walang function getClassName() makakakita tayo ng mensahe ng error. Hindi ako magtutuon dito sa ngayon, magiging kapaki-pakinabang ito sa amin mamaya, para sa pagsubok at pag-debug man lang.

Oras na para sa klase ng ating singles registry mismo. Magsisimula tayo sa pagdedeklara ng klase at ilan sa mga variable nito:

Ipinapatupad ng Class Registry ang StorableObject ( // pangalan ng module, nababasa pribadong static $className = "Registry"; //registry instance pribadong static $instance; //array ng mga object pribadong static $objects = array();

Sa ngayon ang lahat ay lohikal at naiintindihan. Ngayon, tulad ng naaalala mo, mayroon kaming isang pagpapatala na may mga pag-aari ng singleton, kaya't agad tayong magsulat ng isang function na magpapahintulot sa amin na magtrabaho kasama ang pagpapatala sa ganitong paraan:

Pampublikong static na function singleton() ( if(!isset(self::$instance)) ( $obj = __CLASS__; self::$instance = new $obj; ) return self::$instance; )

Literal: sinusuri ng function kung mayroong isang instance ng aming registry: kung hindi, gagawa ito at ibinabalik ito; kung mayroon na, ibinabalik lang nito. Sa kasong ito, hindi namin kailangan ng magic, kaya para sa proteksyon, idedeklara namin itong pribado:

Pribadong function __construct()() pribadong function __clone()() pribadong function __wakeup()() pribadong function __sleep() ()

Ngayon kailangan namin ng isang function upang magdagdag ng isang bagay sa aming registry - ang function na ito ay tinatawag na isang setter at nagpasya akong ipatupad ito sa dalawang paraan upang ipakita kung paano namin magagamit ang magic at magbigay ng isang alternatibong paraan upang magdagdag ng isang bagay. Ang unang paraan ay isang karaniwang function, ang pangalawa ay nagpapatupad ng una sa pamamagitan ng magic ng __set().

//$object - path sa konektadong object //$key - access key sa object sa register public function addObject($key, $object) (require_once($object); //gumawa ng object sa hanay ng mga object self::$objects[ $key] = bagong $key(self::$instance); ) //isang alternatibong paraan sa pamamagitan ng magic public function __set($key, $object) ($this->addObject($key, $ bagay);)

Ngayon, para magdagdag ng object sa aming registry, maaari kaming gumamit ng dalawang uri ng mga entry (sabihin nating nakagawa na kami ng registry instance $registry at gusto naming idagdag ang config.php file):

$registry->addObject("config", "/core/config.php"); //regular na paraan $registry->config = "/core/config.php"; //sa pamamagitan ng PHP magic function __set()

Ang parehong mga entry ay gagawa ng parehong function - ikokonekta nila ang file, lumikha ng isang halimbawa ng klase at ilagay ito sa rehistro na may susi. Mayroong isang mahalagang punto dito, hindi natin dapat kalimutan ang tungkol dito sa hinaharap: ang object key sa rehistro ay dapat tumugma sa pangalan ng klase sa konektadong bagay. Kung titingnan mo muli ang code, mauunawaan mo kung bakit.

Aling recording ang gagamitin ay nasa iyo. Mas gusto ko ang pag-record sa pamamagitan ng magic method - ito ay "mas maganda" at mas maikli.

Kaya, inayos namin ang pagdaragdag ng isang bagay, ngayon kailangan namin ng isang function para sa pag-access ng isang konektadong bagay sa pamamagitan ng key - isang getter. Ipinatupad ko rin ito sa dalawang pag-andar, katulad ng setter:

//kunin ang object mula sa rehistro //$key - ang susi sa array public function getObject($key) ( //suriin kung ang variable ay isang object kung (is_object(self::$objects[$key]))) ( //kung gayon, ibabalik natin ang bagay na ito sa sarili::$objects[$key]; ) ) //isang katulad na paraan sa pamamagitan ng magic public function __get($key) ( if (is_object(self::$objects[$ key])) ( ibalik ang sarili: :$objects[$key]; ) )

Tulad ng setter, upang makakuha ng access sa object magkakaroon kami ng 2 katumbas na mga entry:

$registry->getObject("config"); //regular na paraan $registry->config; //sa pamamagitan ng PHP magic function na __get()

Ang matulungin na mambabasa ay magtatanong kaagad ng tanong: bakit sa __set() magic function na lang ako tumatawag ng regular (non-magic) object adding function, ngunit sa __get() getter kinopya ko ang getObject() function code sa halip na parehong tawag? Sa totoo lang, hindi ko masagot nang tumpak ang tanong na ito, sasabihin ko lang na nagkaroon ako ng mga problema kapag nagtatrabaho sa __get() magic sa ibang mga module, ngunit kapag muling isinulat ang code na "head-on" ay walang ganoong mga problema.

Marahil iyon ang dahilan kung bakit madalas kong nakikita sa mga artikulo ang mga paninisi sa mga pamamaraan ng mahika ng PHP at payo upang maiwasan ang paggamit ng mga ito.

"Lahat ng magic ay may kapalit." © Rumplestiltskin

Sa yugtong ito, handa na ang pangunahing pag-andar ng aming pagpapatala: maaari kaming lumikha ng isang halimbawa ng pagpapatala, magdagdag ng mga bagay at ma-access ang mga ito kapwa gamit ang mga kumbensyonal na pamamaraan at sa pamamagitan ng mga mahiwagang pamamaraan ng wikang PHP. "Paano ang pagtanggal?"— hindi namin kakailanganin ang function na ito sa ngayon, at hindi ako sigurado na may magbabago sa hinaharap. Sa huli, maaari naming palaging magdagdag ng kinakailangang pag-andar. Ngunit kung susubukan naming lumikha ng isang halimbawa ng aming pagpapatala,

$registry = Registry::singleton();

magkakaroon tayo ng error:

Malalang pagkakamali: Ang Class Registry ay naglalaman ng 1 abstract na pamamaraan at dapat samakatuwid ay ideklarang abstract o ipatupad ang natitirang mga pamamaraan (StorableObject::getClassName) sa ...

Lahat dahil nakalimutan naming magsulat ng kinakailangang function. Tandaan sa umpisa pa lang ay nagsalita ako tungkol sa isang function na nagbabalik ng pangalan ng module? Ito ang nananatiling idaragdag para sa buong pag-andar. Ito ay simple:

Pampublikong static na function getClassName() ( return self::$className; )

Ngayon ay hindi dapat magkaroon ng mga pagkakamali. Iminumungkahi kong magdagdag ng isa pang function, hindi ito kinakailangan, ngunit sa malao't madali ay maaaring magamit ito; gagamitin namin ito sa hinaharap para sa pagsusuri at pag-debug. Ibabalik ng function ang mga pangalan ng lahat ng object (modules) na idinagdag sa aming registry:

Public function getObjectsList() ( //the array na ibabalik namin $names = array(); //kunin ang pangalan ng bawat object mula sa array ng mga object foreach(self::$objects as $obj) ($name = $ obj->getClassName() ; ) //idagdag ang pangalan ng register module sa array array_push($names, self::getClassName()); //at ibalik ang $names; )

Iyon lang. Kinukumpleto nito ang rehistro. Tingnan natin ang kanyang trabaho? Kapag nagsusuri, kakailanganin naming ikonekta ang isang bagay - hayaang magkaroon ng configuration file. Gumawa ng bagong core/config.php file at idagdag ang pinakamababang nilalaman na kailangan ng aming registry:

//huwag kalimutang suriin ang constant if (!defined("_PLUGSECURE_")) ( die("Direct module call is prohibited!"); ) class Config ( //module name, readable private static $className = "Config "; public static function getClassName() ( return self::$className; ) )

May ganyan. Ngayon ay magpatuloy tayo sa mismong pag-verify. Sa ugat ng aming proyekto, lumikha ng isang file na index.php at isulat ang sumusunod na code dito:

Define("_PLUGSECURE_", true); //defined a constant to protect against direct access to objects require_once "/core/registry.php"; //kinonekta ang rehistro $registry = Registry::singleton(); //lumikha ng isang register singleton instance $registry->config = "/core/config.php"; //ikonekta ang aming, sa ngayon ay walang silbi, config //ipakita ang mga pangalan ng mga konektadong module echo " Nakakonekta"; foreach ($registry->

  • " . $pangalan ."
  • "; }

    O, kung iiwasan mo pa rin ang magic, ang ika-5 linya ay maaaring mapalitan ng alternatibong paraan:

    Define("_PLUGSECURE_", true); //defined a constant to protect against direct access to objects require_once "/core/registry.php"; //kinonekta ang rehistro $registry = Registry::singleton(); //created a register singleton instance $registry->addObject("config", "/core/config.php"); //ikonekta ang aming, sa ngayon ay walang silbi, config //ipakita ang mga pangalan ng mga konektadong module echo " Nakakonekta"; foreach ($registry->getObjectsList() bilang $names) ( echo "

  • " . $pangalan ."
  • "; }

    Ngayon buksan ang browser at isulat ang http://localhost/index.php o simpleng http://localhost/ sa address bar (may kaugnayan kung gumagamit ka ng karaniwang Open Server o katulad na mga setting ng web server)

    Bilang isang resulta, dapat nating makita ang isang bagay tulad nito:

    Tulad ng nakikita mo, walang mga pagkakamali, na nangangahulugang gumagana ang lahat, kung saan binabati kita :)

    Ngayon ay titigil tayo dito. Sa susunod na artikulo, babalik kami sa database at magsulat ng isang klase para sa pagtatrabaho sa MySQL SUDB, ikonekta ito sa pagpapatala at subukan ang gawain sa pagsasanay. See you!

    Ang pattern na ito, tulad ng Singleton, ay bihirang nagdudulot ng positibong reaksyon mula sa mga developer, dahil ito ay nagbibigay ng parehong mga problema kapag sinusubukan ang mga application. Gayunpaman, pinapagalitan nila, ngunit aktibong ginagamit. Tulad ng Singleton, ang pattern ng Registry ay matatagpuan sa maraming mga application at, sa isang paraan o iba pa, ay lubos na pinapasimple ang paglutas ng ilang mga problema.

    Isaalang-alang natin ang parehong mga pagpipilian sa pagkakasunud-sunod.

    Ang tinatawag na "pure registry" o simpleng Registry ay isang pagpapatupad ng isang klase na may static na interface. Ang pangunahing pagkakaiba sa pattern ng Singleton ay hinaharangan nito ang kakayahang lumikha ng kahit isang instance ng isang klase. Dahil dito, walang saysay na itago ang mga magic method na __clone() at __wakeup() sa likod ng pribado o protektadong modifier.

    Klase sa pagpaparehistro dapat mayroong dalawang static na pamamaraan - isang getter at isang setter. Inilalagay ng setter ang naipasa na bagay sa imbakan na may binding sa ibinigay na key. Ang getter, nang naaayon, ay nagbabalik ng isang bagay mula sa tindahan. Ang isang tindahan ay hindi hihigit sa isang nag-uugnay na key-value array.

    Para sa kumpletong kontrol sa pagpapatala, isa pang elemento ng interface ang ipinakilala - isang paraan na nagpapahintulot sa iyo na tanggalin ang isang bagay mula sa imbakan.

    Bilang karagdagan sa mga problemang kapareho ng pattern ng Singleton, may dalawa pa:

    • pagpapakilala ng isa pang uri ng dependency - sa mga registry key;
    • dalawang magkaibang registry key ay maaaring magkaroon ng reference sa parehong bagay

    Sa unang kaso, imposibleng maiwasan ang karagdagang pag-asa. Sa ilang mga lawak, tayo ay nakakabit sa mga pangunahing pangalan.

    Ang pangalawang problema ay nalutas sa pamamagitan ng pagpapakilala ng isang tseke sa Registry::set() na pamamaraan:

    Public static function set ($key, $item) ( if (!array_key_exists ($key, self::$_registry)) ( foreach (self::$_registry as $val) ( if ($val === $item) ( throw new Exception("Item already exists"); ) ) self::$_registry[$key] = $item; ) )

    « Malinis na pattern ng Registry"nagbibigay ng isa pang problema - ang pagtaas ng pag-asa dahil sa pangangailangan na ma-access ang setter at getter sa pamamagitan ng pangalan ng klase. Hindi ka maaaring lumikha ng isang sanggunian sa isang bagay at magtrabaho kasama nito, tulad ng nangyari sa pattern ng Singleton, kapag ang diskarte na ito ay magagamit:

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

    Dito mayroon tayong pagkakataong mag-save ng reference sa isang halimbawa ng Singleton, halimbawa, sa isang property ng kasalukuyang klase, at magtrabaho kasama nito ayon sa hinihingi ng ideolohiyang OOP: ipasa ito bilang parameter sa pinagsama-samang mga bagay o gamitin ito sa mga inapo.

    Upang malutas ang isyung ito mayroong Pagpapatupad ng Singleton Registry, na hindi gusto ng maraming tao dahil ito ay parang redundant code. Sa tingin ko ang dahilan ng saloobing ito ay ilang hindi pagkakaunawaan sa mga prinsipyo ng OOP o isang sadyang pagwawalang-bahala sa mga ito.

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

    Upang makatipid ng pera, sinadya kong tinanggal ang mga bloke ng komento para sa mga pamamaraan at katangian. Sa tingin ko hindi naman sila kailangan.

    Tulad ng sinabi ko na, ang pangunahing pagkakaiba ay na ngayon ay posible na mag-save ng isang sanggunian sa dami ng pagpapatala at hindi gumamit ng masalimuot na mga tawag sa mga static na pamamaraan sa bawat oras. Ang pagpipiliang ito ay tila mas tama sa akin. Ang pagsang-ayon o hindi pagsang-ayon sa aking opinyon ay hindi mahalaga, tulad ng aking opinyon mismo. Walang mga subtlety ng pagpapatupad ang maaaring mag-alis ng pattern mula sa isang bilang ng mga nabanggit na disadvantages.

    Nagpasya akong magsulat ng maikli tungkol sa mga pattern na madalas na ginagamit sa ating buhay, mas maraming halimbawa, mas kaunting tubig, tayo.

    Singleton

    Ang pangunahing punto ng "single" ay kapag sinabi mong "Kailangan ko ng palitan ng telepono," sasabihin nila sa iyo na "Nagawa na ito doon," at hindi "Buuin natin itong muli." Ang isang "nag-iisa" ay palaging nag-iisa.

    Class Singleton ( private static $instance = null; private function __construct())( /* ... @return Singleton */ ) // Protektahan laban sa paglikha sa pamamagitan ng bagong Singleton private function __clone() ( /* ... @return Singleton * / ) // Protektahan laban sa paglikha sa pamamagitan ng pag-clone ng pribadong function __wakeup() ( /* ... @return Singleton */ ) // Protektahan laban sa paglikha sa pamamagitan ng unserialize public static function getInstance() ( if (is_null(self::$instance ) ) ( self::$instance = new self; ) return self::$instance; ) )

    Registry (registry, journal ng mga entry)

    Gaya ng ipinahihiwatig ng pangalan, ang pattern na ito ay idinisenyo upang mag-imbak ng mga talaan na nakalagay dito at, nang naaayon, ibalik ang mga talang ito (sa pangalan) kung kinakailangan ang mga ito. Sa halimbawa ng isang palitan ng telepono, ito ay isang rehistro na may kaugnayan sa mga numero ng telepono ng mga residente.

    Class Registry ( private $registry = array(); public function set($key, $object) ($this->registry[$key] = $object; ) public function get($key) ( return $this->registry [$key]; ))

    Singleton Registry- huwag malito sa)

    Ang "registry" ay madalas na isang "nag-iisa," ngunit hindi ito palaging kailangang ganoon. Halimbawa, maaari kaming lumikha ng maraming mga journal sa departamento ng accounting, sa isa ay may mga empleyado mula sa "A" hanggang "M", sa isa pa mula sa "N" hanggang "Z". Ang bawat naturang journal ay magiging isang "registry", ngunit hindi isang "single", dahil mayroon nang 2 journal.

    Class SingletonRegistry ( private static $instance = null; private $registry = array(); private function __construct() ( /* ... @return Singleton */ ) // Protektahan mula sa paglikha sa pamamagitan ng bagong Singleton private function __clone() ( / * ... @return Singleton */ ) // Protektahan laban sa paglikha sa pamamagitan ng pag-clone ng pribadong function __wakeup() ( /* ... @return Singleton */ ) // Protektahan laban sa paglikha sa pamamagitan ng unserialize public static function getInstance() ( if ( is_null(self::$instance)) ( self::$instance = new self; ) return self::$instance; ) public function set($key, $object) ($this->registry[$key] = $ object; ) public function get($key) ( return $this->registry[$key]; ) )

    Multiton (pool ng "mga single") o sa madaling salitaRegistry Singleton ) - huwag malito sa Singleton Registry

    Kadalasan ang "rehistro" ay partikular na ginagamit upang mag-imbak ng "mga solo". Pero, kasi ang pattern ng "registry" ay hindi isang "generative pattern", ngunit nais kong isaalang-alang ang "register" na may kaugnayan sa "singleton".Iyon ang dahilan kung bakit nakabuo kami ng isang pattern Multiton, na ayon saSa kaibuturan nito, ito ay isang "registry" na naglalaman ng ilang "single", bawat isa ay may sariling "pangalan" kung saan maaari itong ma-access.

    Maikli: nagpapahintulot sa iyo na lumikha ng mga bagay ng klase na ito, ngunit kung pangalanan mo lamang ang bagay. Walang halimbawa sa totoong buhay, ngunit nakita ko ang sumusunod na halimbawa sa Internet:

    Class Database ( private static $instances = array(); private function __construct() ( ) private function __clone() ( ) public static function getInstance($key) ( if(!array_key_exists ($key, self::$instances))) ( self::$instances[$key] = new self(); ) return self::$instances[$key]; ) ) $master = Database::getInstance("master"); var_dump($master); // object(Database)#1 (0) ( ) $logger = Database::getInstance("logger"); var_dump($logger); // object(Database)#2 (0) ( ) $masterDupe = Database::getInstance("master"); var_dump($masterDupe); // object(Database)#1 (0) ( ) // Fatal error: Tumawag sa pribadong Database::__construct() from invalid context $dbFatalError = new Database(); // PHP Fatal error: Tumawag sa pribadong Database::__clone() $dbCloneError = clone $masterDupe;

    Object pool

    Mahalaga ang pattern na ito ay isang "registry" na nag-iimbak lamang ng mga bagay, walang mga string, array, atbp. uri ng data.

    Pabrika

    Ang kakanyahan ng pattern ay halos ganap na inilarawan sa pamamagitan ng pangalan nito. Kapag kailangan mong kumuha ng ilang bagay, tulad ng mga kahon ng juice, hindi mo kailangang malaman kung paano ginawa ang mga ito sa isang pabrika. Sasabihin mo lang, "bigyan mo ako ng isang karton ng orange juice," at ibabalik ng "pabrika" ang kinakailangang pakete sa iyo. Paano? Ang lahat ng ito ay napagpasyahan ng pabrika mismo, halimbawa, ito ay "kumopya" ng isang umiiral nang pamantayan. Ang pangunahing layunin ng "pabrika" ay gawing posible, kung kinakailangan, na baguhin ang proseso ng "hitsura" ng isang pakete ng juice, at ang mamimili mismo ay hindi kailangang sabihin tungkol dito, upang maaari niyang hilingin ito. gaya ng dati. Bilang isang patakaran, ang isang pabrika ay nakikibahagi sa "produksyon" ng isang uri lamang ng "produkto". Hindi inirerekumenda na lumikha ng isang "pabrika ng juice" na isinasaalang-alang ang paggawa ng mga gulong ng kotse. Tulad ng sa buhay, ang pattern ng pabrika ay madalas na nilikha ng isang solong tao.

    Abstract class AnimalAbstract ( protected $species; public function getSpecies() ( return $this->species; ) ) class Cat extends AnimalAbstract ( protected $species = "cat"; ) class Dog extends AnimalAbstract ( protected $species = "aso"; ) class AnimalFactory ( public static function factory ($ animal) ( switch ($ animal) ( case "cat": $obj = new Cat(); break; case "aso": $obj = new Dog(); break; default : throw new Exception("Animal factory cannot create animal of species "" . $animal . """, 1000); ) return $obj; ) ) $cat = AnimalFactory::factory("cat"); // object(Cat)#1 echo $cat->getSpecies(); // pusa $dog = AnimalFactory::factory("aso"); // object(Dog)#1 echo $dog->getSpecies(); // aso $hippo = AnimalFactory::factory("hippopotamus"); // Maghahagis ito ng Exception

    Nais kong iguhit ang iyong pansin sa katotohanan na ang pamamaraan ng pabrika ay isang pattern din; ito ay tinatawag na pamamaraan ng Pabrika.

    Tagabuo (tagabuo)

    Kaya, naintindihan na namin na ang "Factory" ay isang drinks vending machine, nakahanda na ang lahat, at sasabihin mo lang kung ano ang kailangan mo. Ang "Builder" ay isang planta na gumagawa ng mga inuming ito at naglalaman ng lahat ng kumplikadong operasyon at maaaring mag-assemble ng mga kumplikadong bagay mula sa mas simple (packaging, label, tubig, lasa, atbp.) depende sa kahilingan.

    Class Bottle ( public $name; public $liters; ) /** * all builders must */ interface BottleBuilderInterface ( public function setName(); public function setLiters(); public function getResult(); ) class CocaColaBuilder implements BottleBuilderInterface ( private $ bote; pampublikong function __construct() ($this->bottle = new Bottle(); ) public function setName($value) ($ ​​this->bottle->name = $value; ) public function setLiters($value) ($ this->bote->liters = $value; ) public function getResult() ( return $this->bote; ) ) $juice = new CocaColaBuilder(); $juice->setName("Coca-Cola Light"); $juice->setLiters(2); $juice->getResult();

    Prototype

    Na kahawig ng isang pabrika, nagsisilbi rin itong lumikha ng mga bagay, ngunit may bahagyang naiibang diskarte. Isipin ang iyong sarili sa isang bar, umiinom ka ng serbesa at nauubusan ka na nito, sinabi mo sa bartender - gawin mo akong isa pang kaparehong uri. Ang bartender naman ay tumitingin sa beer na iniinom mo at gumagawa ng kopya gaya ng hiniling mo. Ang PHP ay mayroon nang pagpapatupad ng pattern na ito, ito ay tinatawag na .

    $newJuice = clone $juice;

    Tamad na pagsisimula

    Halimbawa, ang isang boss ay nakakakita ng isang listahan ng mga ulat para sa iba't ibang uri ng mga aktibidad at iniisip na ang mga ulat na ito ay umiiral na, ngunit sa katunayan ang mga pangalan lamang ng mga ulat ay ipinapakita, at ang mga ulat mismo ay hindi pa nabubuo, at bubuo lamang. kapag nag-order (halimbawa, sa pamamagitan ng pag-click sa button na Tingnan ang ulat). Ang isang espesyal na kaso ng tamad na pagsisimula ay ang paglikha ng isang bagay sa oras na ito ay na-access. Makakahanap ka ng isang kawili-wili sa Wikipedia, ngunit... ayon sa teorya, ang tamang halimbawa sa php ay, halimbawa, isang function

    Adapter o Wrapper (adapter, wrapper)

    Ang pattern na ito ay ganap na tumutugma sa pangalan nito. Upang gumana ang plug ng "Soviet" sa pamamagitan ng Euro socket, kinakailangan ang isang adaptor. Ito ay eksakto kung ano ang ginagawa ng isang "adapter" - ito ay nagsisilbing isang intermediate na bagay sa pagitan ng dalawang iba pa na hindi maaaring gumana nang direkta sa isa't isa. Sa kabila ng kahulugan, sa pagsasanay nakikita ko pa rin ang pagkakaiba sa pagitan ng Adapter at Wrapper.

    Class MyClass ( public function methodA() () ) class MyClassWrapper ( public function __construct())( $this->myClass = new MyClass(); ) public function __call($name, $arguments)( Log::info(" Tatawagin mo na ang $name method."); return call_user_func_array(array($this->myClass, $name), $arguments); ) ) $obj = new MyClassWrapper(); $obj->methodA();

    Dependency injection

    Ang dependency injection ay nagpapahintulot sa iyo na ilipat ang bahagi ng responsibilidad para sa ilang pag-andar sa iba pang mga bagay. Halimbawa, kung kailangan nating kumuha ng mga bagong tauhan, hindi tayo makakagawa ng sarili nating departamento ng HR, ngunit ipakilala ang pag-asa sa isang kumpanya ng recruitment, na, naman, sa una nating kahilingan na "kailangan natin ng isang tao," ay gagana bilang isang Ang departamento ng HR mismo, o maghahanap ng ibang kumpanya (gamit ang "tagahanap ng serbisyo") na magbibigay ng mga serbisyong ito.
    Binibigyang-daan ka ng "Dependency injection" na ilipat at palitan ang mga indibidwal na bahagi ng kumpanya nang hindi nawawala ang pangkalahatang paggana.

    Class AppleJuice () // ang paraang ito ay isang primitive na pagpapatupad ng Dependency injection pattern at higit pa, makikita mo ang function na ito getBottleJuice())( $obj = new AppleJuice AppleJuice)( return $obj; ) ) $bottleJuice = getBottleJuice();

    Ngayon isipin na hindi na namin gusto ang apple juice, gusto namin ang orange juice.

    Class AppleJuice() Class OrangeJuice() // ang pamamaraang ito ay nagpapatupad ng Dependency injection function getBottleJuice())( $obj = bago OrangeJuice; // suriin ang bagay, kung sakaling madulas tayo ng beer (beer ay hindi juice) if($obj instanceof OrangeJuice)( ibalik ang $obj; ) )

    Tulad ng nakikita mo, kailangan naming baguhin hindi lamang ang uri ng juice, kundi pati na rin ang tseke para sa uri ng juice, na hindi masyadong maginhawa. Mas tama na gamitin ang prinsipyo ng Dependency inversion:

    Interface Juice () Class AppleJuice implements Juice () Class OrangeJuice implements Juice () function getBottleJuice())( $obj = new OrangeJuice; // suriin ang bagay, kung sakaling madulas tayo ng beer (beer ay hindi juice) if($obj halimbawa ng Juice)( ibalik ang $obj; ) )

    Ang dependency inversion ay minsan nalilito sa Dependency injection, ngunit hindi na kailangang lituhin ang mga ito, dahil Ang dependency inversion ay isang prinsipyo, hindi isang pattern.

    Tagahanap ng Serbisyo

    Ang "Service Locator" ay isang paraan ng pagpapatupad ng "Dependency Injection". Nagbabalik ito ng iba't ibang uri ng mga bagay depende sa initialization code. Hayaan ang gawain ay ihatid ang aming pakete ng juice, na ginawa ng isang tagabuo, pabrika o iba pa, saanman gusto ng mamimili. Sinasabi namin sa tagahanap na "bigyan kami ng serbisyo sa paghahatid," at hilingin sa serbisyo na ihatid ang juice sa nais na address. Ngayon ay may isang serbisyo, at bukas ay maaaring may isa pa. Hindi mahalaga sa amin kung anong partikular na serbisyo ito, mahalagang malaman namin na ihahatid ng serbisyong ito ang sinasabi namin at kung saan namin ito sasabihin. Kaugnay nito, ipinapatupad ng mga serbisyo ang "Ihatid<предмет>sa<адрес>».

    Kung pag-uusapan natin ang tungkol sa totoong buhay, marahil ang isang magandang halimbawa ng isang Service Locator ay ang extension ng PDO PHP, dahil Ngayon nagtatrabaho kami sa isang database ng MySQL, at bukas ay maaari kaming magtrabaho sa PostgreSQL. Gaya ng naintindihan mo na, hindi mahalaga sa aming klase kung saang database ito magpapadala ng data nito, mahalaga na magagawa nito.

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

    Ang pagkakaiba sa pagitan ng Dependency injection at Service Locator

    Kung hindi mo pa napapansin, gusto kong ipaliwanag. Dependency injection Bilang resulta, hindi ito nagbabalik ng isang serbisyo (na maaaring maghatid ng isang bagay sa isang lugar) ngunit isang bagay na ang data ay ginagamit nito.

    Susubukan kong sabihin sa iyo ang tungkol sa aking pagpapatupad ng pattern ng Registry sa PHP. Ang Registry ay isang OOP na kapalit para sa mga pandaigdigang variable, na idinisenyo upang mag-imbak ng data at ilipat ito sa pagitan ng mga module ng system. Alinsunod dito, ito ay pinagkalooban ng mga karaniwang katangian - magsulat, magbasa, magtanggal. Narito ang isang tipikal na pagpapatupad.

    Well, sa ganitong paraan nakakakuha tayo ng isang hangal na kapalit ng mga pamamaraan $key = $value - Registry::set($key, $value) $key - Registry::get($key) unset($key) - alisin Registry::remove ($key) Nagiging hindi malinaw - kung bakit ang dagdag na code na ito. Kaya, turuan natin ang ating klase na gawin ang hindi kayang gawin ng mga global variable. Lagyan natin ito ng paminta.

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

    Sa mga karaniwang gawain ng pattern, idinagdag ko ang kakayahang harangan ang isang variable mula sa mga pagbabago, ito ay napaka-maginhawa sa malalaking proyekto, hindi mo sinasadyang magpasok ng anuman. Halimbawa, maginhawa para sa pagtatrabaho sa mga database
    define('DB_DNS', 'mysql:host=localhost;dbname= ’);
    tukuyin('DB_USER', ' ’);
    tukuyin('DB_PASSWORD', ' ’);
    define('DB_HANDLE');

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

    Ngayon para sa isang paliwanag ng code, upang mag-imbak ng data, ginagamit namin ang static na variable na $data, ang $lock variable ay nag-iimbak ng data tungkol sa mga key na naka-lock para sa pagbabago. Sa network, sinusuri namin kung ang variable ay naka-lock at binago o idagdag ito sa rehistro. Kapag nagde-delete, sinusuri din namin ang lock; nananatiling hindi nagbabago ang getter, maliban sa default na opsyonal na parameter. Well, ito ay nagkakahalaga ng pagbibigay pansin sa exception handling, na para sa ilang kadahilanan ay bihirang gamitin. Siyanga pala, mayroon na akong draft sa mga exception, hintayin ang artikulo. Nasa ibaba ang isang draft code para sa pagsubok, narito ang isang artikulo tungkol sa pagsubok, hindi rin masasaktan na isulat ito, kahit na hindi ako fan ng TDD.

    Sa susunod na artikulo ay palawakin pa namin ang pag-andar sa pamamagitan ng pagdaragdag ng data initialization at pagpapatupad ng "katamaran".