PHP da OOP ilovalarida obyektni ishga tushirish muammosi. Ro'yxatdan o'tish kitobi, zavod usuli, xizmat ko'rsatuvchisi va qaramlik kiritish naqshlari yordamida yechim topish. Misollar va tavsiflar bilan OOP naqshlari Vakolatli registro php

PHP da OOP ilovalarida obyektni ishga tushirish muammosi. Ro'yxatdan o'tish kitobi, zavod usuli, xizmat ko'rsatuvchisi va qaramlik in'ektsiya naqshlari yordamida yechim topish

Shunday bo'ladiki, dasturchilar muvaffaqiyatli echimlarni dizayn naqshlari shaklida birlashtiradi. Naqshlar bo'yicha ko'plab adabiyotlar mavjud. To'rtlik to'dasining Erich Gamma, Richard Helm, Ralf Jonson va Jon Vlissidesning "Dizayn naqshlari" kitobi va, ehtimol, Martin Faulerning "Korxona ilovalari arxitekturasining namunalari" kitobi, albatta, klassik deb hisoblanadi. Men misollar bilan o'qigan eng yaxshi narsam. PHP-da - bu. Shunday bo'ladiki, bu adabiyotlarning barchasi OOPni endigina o'zlashtira boshlaganlar uchun juda murakkabdir. Shuning uchun men o'zim uchun eng foydali deb topadigan ba'zi naqshlarni juda soddalashtirilgan shaklda taqdim etish fikri tug'ildi. so'z bilan aytganda, bu maqola mening KISS uslubidagi dizayn naqshlarini talqin qilishga birinchi urinishim.
Bugun biz OOP ilovasida ob'ektni ishga tushirishda qanday muammolar paydo bo'lishi mumkinligi va ushbu muammolarni hal qilish uchun ba'zi mashhur dizayn naqshlaridan qanday foydalanish haqida gaplashamiz.

Misol

Zamonaviy OOP ilovasi o'nlab, yuzlab va ba'zan minglab ob'ektlar bilan ishlaydi. Keling, ushbu ob'ektlar bizning ilovalarimizda qanday ishga tushirilganligini batafsil ko'rib chiqaylik. Ob'ektni ishga tushirish - bu maqolada bizni qiziqtiradigan yagona jihat, shuning uchun men barcha "qo'shimcha" amalga oshirishni o'tkazib yuborishga qaror qildim.
Aytaylik, biz ma'lum bir URI ga GET so'rovini yuborishi va server javobidan HTMLni qaytarishi mumkin bo'lgan super-juda foydali sinf yaratdik. Bizning sinfimiz juda oddiy ko'rinmasligi uchun, u ham natijani tekshirsin va agar server "noto'g'ri" javob bersa, istisno qilsin.

Class Grabber ( umumiy funksiya get($url) (/** HTML kodini qaytaradi yoki istisno qiladi */))

Keling, ob'ektlari qabul qilingan HTMLni filtrlash uchun javobgar bo'lgan boshqa sinf yarataylik. Filtrlash usuli argument sifatida HTML kodini va CSS selektorini oladi va berilgan selektor uchun topilgan elementlar qatorini qaytaradi.

HtmlExtractor klassi ( umumiy funktsiya filtri($html, $selector) (/** filtrlangan elementlar qatorini qaytaradi */) )

Endi tasavvur qiling-a, biz berilgan kalit so'zlar uchun Google-da qidiruv natijalarini olishimiz kerak. Buning uchun so'rov yuborish uchun Grabber sinfidan va kerakli tarkibni chiqarish uchun HtmlExtractor sinfidan foydalanadigan boshqa sinfni taqdim etamiz. Shuningdek, u URI-ni yaratish mantiqini, qabul qilingan HTML-ni filtrlash va olingan natijalarni qayta ishlash uchun selektorni o'z ichiga oladi.

GoogleFinder klassi ( private $grabber; private $filter; public function __construct() ( $this->grabber = new Grabber(); $this->filter = new HtmlExtractor(); ) public function find($searchString) ( /* * asoslangan natijalar qatorini qaytaradi */))

Grabber va HtmlExtractor obyektlarining ishga tushirilishi GoogleFinder sinfining konstruktorida ekanligini payqadingizmi? Keling, bu qaror qanchalik yaxshi ekanini o'ylab ko'raylik.
Albatta, konstruktorda ob'ektlarni yaratishni qattiq kodlash yaxshi fikr emas. Va shuning uchun ham. Birinchidan, haqiqiy so'rov yubormaslik uchun Grabber sinfini sinov muhitida osongina bekor qila olmaymiz. Rostini aytsam, buni Reflection API yordamida amalga oshirish mumkinligini aytish kerak. Bular. texnik imkoniyat mavjud, ammo bu eng qulay va aniq yo'ldan uzoqdir.
Ikkinchidan, agar biz GoogleFinder mantig'ini boshqa Grabber va HtmlExtractor ilovalari bilan qayta ishlatmoqchi bo'lsak, xuddi shunday muammo paydo bo'ladi. Bog'liqlarni yaratish sinf konstruktorida qattiq kodlangan. Va eng yaxshi holatda, biz GoogleFinder-ni meros qilib olishimiz va uning konstruktorini bekor qilishimiz mumkin. Va shunga qaramay, faqat grabber va filtr xususiyatlarining doirasi himoyalangan yoki ommaviy bo'lsa.
Oxirgi nuqta, har safar yangi GoogleFinder ob'ektini yaratganimizda, xotirada yangi bog'liqlik ob'ektlari juftligi yaratiladi, ammo biz bir nechta GoogleFinder ob'ektlarida bitta Grabber ob'ekti va bitta HtmlExtractor ob'ektidan juda oson foydalanishimiz mumkin.
O'ylaymanki, siz allaqachon qaramlikni ishga tushirish sinfdan tashqariga ko'chirilishi kerakligini tushunasiz. Biz allaqachon tayyorlangan bog'liqliklarni GoogleFinder sinfining konstruktoriga o'tkazishni talab qilishimiz mumkin.

GoogleFinder klassi ( private $grabber; private $filter; umumiy funktsiya __construct(Grabber $grabber, HtmlExtractor $filter) ( $this->grabber = $grabber; $this->filtr = $filter; ) public function find($searchString) ( /** asosli natijalar qatorini qaytaradi */))

Agar biz boshqa ishlab chiquvchilarga o'zlarining Grabber va HtmlExtractor ilovalarini qo'shish va ulardan foydalanish imkoniyatini berishni istasak, ular uchun interfeyslarni joriy etish haqida o'ylashimiz kerak. Bunday holda, bu nafaqat foydali, balki zarurdir. Menimcha, agar biz loyihada faqat bitta dasturdan foydalansak va kelajakda yangilarini yaratishni kutmasak, u holda interfeys yaratishdan bosh tortishimiz kerak. Vaziyatga qarab harakat qilish va unga haqiqiy ehtiyoj paydo bo'lganda oddiy refaktoringni amalga oshirish yaxshiroqdir.
Endi bizda barcha kerakli sinflar mavjud va biz boshqaruvchida GoogleFinder sinfidan foydalanishimiz mumkin.

Class Controller (ommaviy funktsiya action() ( /* Ba'zi narsalar */ $finder = yangi GoogleFinder (yangi Grabber(), yangi HtmlExtractor()); $results = $finder->

Keling, oraliq natijalarni sarhisob qilaylik. Biz juda oz kod yozdik va birinchi qarashda biz noto'g'ri ish qilmadik. Lekin... GoogleFinder kabi ob'ektni boshqa joyda ishlatishimiz kerak bo'lsa-chi? Biz uning yaratilishini takrorlashimiz kerak bo'ladi. Bizning misolimizda bu faqat bitta chiziq va muammo unchalik sezilmaydi. Amalda, obyektlarni ishga tushirish ancha murakkab bo'lishi mumkin va 10 qatorgacha yoki undan ham ko'proq vaqtni olishi mumkin. Kodning takrorlanishiga xos bo'lgan boshqa muammolar ham paydo bo'ladi. Agar refaktoring jarayonida foydalanilgan sinf nomini yoki ob'ektni ishga tushirish mantiqini o'zgartirish kerak bo'lsa, barcha joylarni qo'lda o'zgartirishingiz kerak bo'ladi. Bu qanday sodir bo'lishini bilasiz deb o'ylayman :)
Odatda, qattiq kod oddiygina hal qilinadi. Ikki nusxadagi qiymatlar odatda konfiguratsiyaga kiritiladi. Bu qiymatlarni ular ishlatiladigan barcha joylarda markaziy ravishda o'zgartirishga imkon beradi.

Ro'yxatga olish shabloni.

Shunday qilib, biz ob'ektlarni yaratishni konfiguratsiyaga o'tkazishga qaror qildik. Keling, shunday qilaylik.

$registry = new ArrayObject(); $registry["grabber"] = new Grabber(); $registry["filtr"] = new HtmlExtractor(); $registry["google_finder"] = new GoogleFinder($registry["grabber"], $registry["filtr"]);
Biz qilishimiz kerak bo'lgan narsa bizning ArrayObject-ni kontrollerga o'tkazish va muammo hal qilinadi.

Class Controller ( private $registry; public function __construct(ArrayObject $registry) ( $this->registry = $registry; ) public function action() ( /* Baʼzi narsalar */ $results = $this->registry["google_finder" ]->find ("qidiruv qatori"); /* Natijalar bilan biror narsa qiling */ ) )

Biz Registr g'oyasini yanada rivojlantirishimiz mumkin. ArrayObject-ni meros qilib olish, yangi sinf ichida ob'ektlarni yaratishni inkapsulyatsiya qilish, ishga tushirilgandan so'ng yangi ob'ektlarni qo'shishni taqiqlash va hokazo. Lekin mening fikrimcha, berilgan kod Ro'yxatga olish shablonining nima ekanligini to'liq aniqlab beradi. Bu naqsh generativ emas, lekin u bizning muammolarimizni hal qilish uchun bir yo'l bo'ladi. Ro'yxatga olish kitobi shunchaki konteyner bo'lib, unda biz ob'ektlarni saqlashimiz va ularni dastur ichida o'tkazishimiz mumkin. Ob'ektlar mavjud bo'lishi uchun biz avval ularni yaratishimiz va ularni ushbu konteynerda ro'yxatdan o'tkazishimiz kerak. Keling, ushbu yondashuvning afzalliklari va kamchiliklarini ko'rib chiqaylik.
Bir qarashda maqsadimizga erishdik. Biz sinf nomlarini qattiq kodlashni to'xtatdik va ob'ektlarni bir joyda yaratdik. Biz ob'ektlarni bitta nusxada yaratamiz, bu ularning qayta ishlatilishini kafolatlaydi. Agar ob'ektlarni yaratish mantig'i o'zgarsa, ilovaning faqat bitta joyini tahrirlash kerak bo'ladi. Bonus sifatida biz Registrdagi ob'ektlarni markazlashtirilgan tarzda boshqarish imkoniyatini oldik. Biz barcha mavjud ob'ektlar ro'yxatini osongina olishimiz va ular bilan ba'zi manipulyatsiyalarni bajarishimiz mumkin. Keling, ushbu shablonda bizga nima yoqmasligi mumkinligini ko'rib chiqaylik.
Birinchidan, ob'ektni ro'yxatga olish kitobida ro'yxatdan o'tkazishdan oldin uni yaratishimiz kerak. Shunga ko'ra, "keraksiz ob'ektlar" ni yaratish ehtimoli yuqori, ya'ni. xotirada yaratiladigan, lekin ilovada ishlatilmaydiganlar. Ha, biz ob'ektlarni ro'yxatga olish kitobiga dinamik ravishda qo'shishimiz mumkin, ya'ni. faqat ma'lum bir so'rovni qayta ishlash uchun zarur bo'lgan ob'ektlarni yarating. Qanday bo'lmasin, biz buni qo'lda boshqarishimiz kerak. Shunga ko'ra, vaqt o'tishi bilan uni saqlash juda qiyin bo'ladi.
Ikkinchidan, bizda yangi nazoratchiga bog'liqlik mavjud. Ha, biz registrni konstruktorga o'tkazmasligimiz uchun biz registrdagi statik usul orqali ob'ektlarni qabul qilishimiz mumkin. Lekin mening fikrimcha, buni qilmaslik kerak. Statik usullar ob'ekt ichida bog'liqliklarni yaratishdan va sinovdan o'tkazishdagi qiyinchiliklardan (ushbu mavzu bo'yicha) yanada qattiqroq bog'lanishdir.
Uchinchidan, boshqaruvchi interfeysi bizga qanday ob'ektlardan foydalanishi haqida hech narsa aytmaydi. Registrda mavjud bo'lgan har qanday ob'ektni tekshirgichda olishimiz mumkin. Biz uning barcha manba kodini tekshirmagunimizcha, boshqaruvchi qaysi ob'ektlardan foydalanishini aytish qiyin bo'ladi.

Zavod usuli

Ro'yxatga olish kitobi bilan bog'liq eng katta tashvishimiz shundaki, ob'ektga kirishdan oldin uni ishga tushirish kerak. Konfiguratsiyadagi ob'ektni ishga tushirish o'rniga, biz ob'ektlarni yaratish mantiqini boshqa sinfga ajratishimiz mumkin, biz undan kerakli ob'ektni qurishni "so'rashimiz" mumkin. Ob'ektlarni yaratish uchun mas'ul bo'lgan sinflar fabrikalar deb ataladi. Dizayn namunasi esa zavod usuli deb ataladi. Keling, zavod misolini ko'rib chiqaylik.

Class Factory ( getGoogleFinder() umumiy funksiyasi ( yangi GoogleFinder($this->getGrabber(), $this->getHtmlExtractor()); ) xususiy funksiya getGrabber() ( new Grabber(); ) xususiy funksiya getHtmlExtractor() ( yangi HtmlFiletr(); )) qaytaring

Qoida tariqasida, bitta turdagi ob'ektni yaratish uchun mas'ul bo'lgan zavodlar ishlab chiqariladi. Ba'zan zavod tegishli ob'ektlar guruhini yaratishi mumkin. Ob'ektlarni qayta yaratmaslik uchun xususiyatga keshlashdan foydalanishimiz mumkin.

Class Factory ( xususiy $finder; umumiy funksiya getGoogleFinder() ( if (null === $this->finder) ( $this->finder = new GoogleFinder($this->getGrabber(), $this->getHtmlExtractor() ); ) $this->finderni qaytaring; ))

Biz zavod usulini parametrlashimiz va kiruvchi parametrga qarab ishga tushirishni boshqa zavodlarga topshirishimiz mumkin. Bu allaqachon Abstract Factory shabloniga aylanadi.
Agar biz dasturni modullashtirishimiz kerak bo'lsa, biz har bir moduldan o'z zavodlarini taqdim etishini talab qilishimiz mumkin. Biz fabrikalar mavzusini yanada rivojlantirishimiz mumkin, ammo menimcha, bu shablonning mohiyati aniq. Keling, zavodni kontrollerda qanday ishlatishimizni ko'rib chiqaylik.

Class Controller ( xususiy $factory; umumiy funktsiya __construct(Factory $factory) ( $this->factory = $factory; ) public function action() ( /* Baʼzi narsalar */ $results = $this->factory->getGoogleFinder( )->find ("qidiruv satri"); /* Natijalar bilan biror narsa qiling */ ) )

Ushbu yondashuvning afzalliklari uning soddaligini o'z ichiga oladi. Bizning ob'ektlarimiz aniq yaratilgan va sizning IDE sizni bu sodir bo'ladigan joyga osongina olib boradi. Biz registr muammosini ham hal qildik, shunda xotiradagi ob'ektlar faqat zavoddan buni "so'raganimizda" yaratiladi. Lekin biz hali nazoratchilarga kerakli zavodlarni qanday yetkazib berishni hal qilmaganmiz. Bu erda bir nechta variant mavjud. Statik usullardan foydalanishingiz mumkin. Biz nazoratchilarga kerakli zavodlarni o'zlari yaratishiga ruxsat berishimiz va nusxa ko'chirish-joylashtirishdan xalos bo'lishga bo'lgan barcha urinishlarimizni bekor qilishimiz mumkin. Siz zavodlar zavodini yaratishingiz va faqat buni boshqaruvchiga topshirishingiz mumkin. Ammo tekshirgichda ob'ektlarni olish biroz murakkablashadi va siz zavodlar o'rtasidagi bog'liqlikni boshqarishingiz kerak bo'ladi. Bundan tashqari, agar biz dasturimizda modullardan foydalanmoqchi bo'lsak, nima qilish kerakligi, modul zavodlarini qanday ro'yxatdan o'tkazish, turli modullardan zavodlar o'rtasidagi ulanishlarni qanday boshqarish kerakligi to'liq aniq emas. Umuman olganda, biz zavodning asosiy afzalligi - ob'ektlarning aniq yaratilishini yo'qotdik. Va biz hali ham "yashirin" boshqaruvchi interfeysi muammosini hal qilmadik.

Xizmatni aniqlash

Service Locator shabloni zavodlarning parchalanishining etishmasligini hal qilish va ob'ektlarni yaratishni avtomatik va markaziy tarzda boshqarish imkonini beradi. Agar biz bu haqda o'ylab ko'rsak, biz ilovamizdagi ob'ektlarni yaratish va ushbu ob'ektlar orasidagi munosabatlarni boshqarish uchun javob beradigan qo'shimcha abstraksiya qatlamini joriy qilishimiz mumkin. Ushbu qatlam biz uchun ob'ektlar yaratish imkoniyatiga ega bo'lishi uchun biz unga buni qanday qilish haqida bilim berishimiz kerak.
Xizmatni aniqlash namunasi shartlari:
  • Xizmat - bu konteynerdan olinadigan tayyor ob'ekt.
  • Xizmat ta'rifi - xizmatni ishga tushirish mantig'i.
  • Konteyner (Xizmat konteyneri) barcha tavsiflarni saqlaydigan va ular asosida xizmatlarni yaratishi mumkin bo'lgan markaziy ob'ektdir.
Har qanday modul o'zining xizmat tavsiflarini ro'yxatdan o'tkazishi mumkin. Konteynerdan biron bir xizmatni olish uchun biz uni kalit orqali so'rashimiz kerak. Service Locator-ni amalga oshirishning ko'plab variantlari mavjud; eng oddiy versiyada biz ArrayObject-dan konteyner sifatida foydalanishimiz mumkin va xizmatlar tavsifi sifatida yopish.

Class ServiceContainer ArrayObject ni kengaytiradi ( umumiy funksiya get($key) ( if (is_callable($this[$key])) ( return call_user_func($this[$key]); ) throw new \RuntimeException("Xizmat ta'rifini topib bo'lmadi" kalit [ $key ]"); ))

Keyin Ta'riflarni ro'yxatdan o'tkazish quyidagicha ko'rinadi:

$container = new ServiceContainer(); $container["grabber"] = function () ( new Grabber(); ni qaytaring); $container["html_filter"] = function () ( new HtmlExtractor(); ); $container["google_finder"] = function() use ($container) ( yangi GoogleFinderni qaytaring($container->get("grabber"), $container->get("html_filter")); );

Va boshqaruvchida foydalanish quyidagicha:

Class Controller ( private $container; public function __construct(ServiceContainer $container) ( $this->container = $container; ) public function action() ( /* Baʼzi narsalar */ $results = $this->container->get( "google_finder")->find("search string"); /* Natijalar bilan biror narsa qiling */ ) )

Xizmat ko'rsatish konteyneri juda oddiy yoki juda murakkab bo'lishi mumkin. Masalan, Symfony Service Container juda ko'p funksiyalarni taqdim etadi: parametrlar, xizmatlar ko'lami, teglar, taxalluslar bo'yicha xizmatlarni qidirish, shaxsiy xizmatlar, barcha xizmatlarni qo'shgandan so'ng konteynerga o'zgartirishlar kiritish imkoniyati (kompilyator o'tishlari) va boshqalar. DIExtraBundle standartni amalga oshirish imkoniyatlarini yanada kengaytiradi.
Ammo keling, misolimizga qaytaylik. Ko'rib turganingizdek, Service Locator nafaqat oldingi andozalar bilan bir xil muammolarni hal qiladi, balki o'z xizmat ta'riflari bilan modullardan foydalanishni osonlashtiradi.
Bundan tashqari, ramka darajasida biz mavhumlikning qo'shimcha darajasini oldik. Ya'ni, ServiceContainer::get usulini o'zgartirish orqali, masalan, ob'ektni proksi-server bilan almashtirishimiz mumkin. Va proksi-server ob'ektlarini qo'llash doirasi faqat ishlab chiquvchining tasavvuri bilan cheklangan. Bu erda siz AOP paradigmasini, LazyLoading va boshqalarni amalga oshirishingiz mumkin.
Ammo ko'pchilik ishlab chiquvchilar hali ham Service Locator-ni anti-naqsh deb bilishadi. Chunki, nazariy jihatdan, biz shunchalik ko'p so'zlarga ega bo'lishimiz mumkin Container Aware sinflari (ya'ni konteynerga havolani o'z ichiga olgan sinflar). Masalan, bizning Controller, uning ichida biz istalgan xizmatni olishimiz mumkin.
Keling, nima uchun bu yomon ekanligini ko'rib chiqaylik.
Birinchidan, yana sinov. Faqat testlarda ishlatiladigan sinflar uchun masxara yaratish o'rniga, siz butun konteynerni masxara qilishingiz yoki haqiqiy konteynerdan foydalanishingiz kerak bo'ladi. Birinchi variant sizga mos kelmaydi, chunki... testlarda juda ko'p keraksiz kodlarni yozishingiz kerak, ikkinchidan, chunki u birliklarni sinovdan o'tkazish tamoyillariga zid keladi va testlarni saqlash uchun qo'shimcha xarajatlarga olib kelishi mumkin.
Ikkinchidan, refaktor qilish biz uchun qiyin bo'ladi. Konteynerdagi har qanday xizmatni (yoki ServiceDefinition) o'zgartirish orqali biz barcha bog'liq xizmatlarni ham tekshirishga majbur bo'lamiz. Va bu muammoni IDE yordamida hal qilib bo'lmaydi. Ilova davomida bunday joylarni topish unchalik oson bo'lmaydi. Bog'liq xizmatlarga qo'shimcha ravishda, konteynerdan refaktorlangan xizmat olingan barcha joylarni ham tekshirishingiz kerak bo'ladi.
Uchinchi sabab shundaki, xizmatlarni konteynerdan nazoratsiz tortib olish ertami-kechmi koddagi tartibsizlikka va keraksiz chalkashlikka olib keladi. Buni tushuntirish qiyin, u yoki bu xizmat qanday ishlashini tushunish uchun ko'proq va ko'proq vaqt sarflashingiz kerak bo'ladi, boshqacha qilib aytganda, uning nima qilishini yoki sinf qanday ishlashini faqat uning butun manba kodini o'qib chiqish orqali to'liq tushunishingiz mumkin.

Bog'liqlik in'ektsiyasi

Ilovada konteynerdan foydalanishni cheklash uchun yana nima qila olasiz? Siz barcha foydalanuvchi ob'ektlarini, shu jumladan kontrollerlarni yaratish boshqaruvini ramkaga o'tkazishingiz mumkin. Boshqacha qilib aytganda, foydalanuvchi kodi konteynerning get usulini chaqirmasligi kerak. Bizning misolimizda biz konteynerga kontroller uchun ta'rif qo'shishimiz mumkin:

$container["google_finder"] = function() use ($container) (yangi Controllerni qaytaring(Grabber $grabber); );

Va kontrollerdagi konteynerdan xalos bo'ling:

Class Controller ( xususiy $finder; umumiy funktsiya __construct(GoogleFinder $finder) ( $this->finder = $finder; ) public function action() ( /* Baʼzi narsalar */ $results = $this->finder->find( "search string"); /* Natijalar bilan biror narsa qiling */ ) )

Ushbu yondashuv (mijoz sinflariga xizmat ko'rsatish konteyneriga kirish imkoni bo'lmaganda) Dependency Injection deb ataladi. Ammo bu shablonning ham afzalliklari, ham kamchiliklari bor. Yagona javobgarlik tamoyiliga amal qilsak, kod juda chiroyli ko'rinadi. Avvalo, biz mijoz sinflarida konteynerdan xalos bo'ldik, bu ularning kodini ancha aniq va sodda qildi. Biz kerakli bog'liqliklarni almashtirish orqali tekshirgichni osongina sinab ko'rishimiz mumkin. Biz har bir sinfni TDD yoki BDD yondashuvidan foydalangan holda boshqalardan (jumladan, nazorat qiluvchi sinflardan) mustaqil ravishda yaratishimiz va sinab ko'rishimiz mumkin. Sinovlarni yaratishda biz konteynerdan mavhum olib tashlashimiz va keyinroq aniq misollardan foydalanishimiz kerak bo'lganda Ta'rifni qo'shishimiz mumkin. Bularning barchasi bizning kodimizni sodda va tushunarli qiladi va sinovni yanada shaffof qiladi.
Lekin tanganing boshqa tomonini ham eslatib o'tish kerak. Haqiqat shundaki, kontrollerlar juda o'ziga xos sinflardir. Keling, nazoratchi, qoida tariqasida, harakatlar to'plamini o'z ichiga olganligidan boshlaylik, ya'ni u yagona javobgarlik tamoyilini buzadi. Natijada, boshqaruvchi sinfi muayyan harakatni bajarish uchun zarur bo'lgandan ko'ra ko'proq bog'liqliklarga ega bo'lishi mumkin. Dangasa ishga tushirishdan foydalanish (ob'ekt birinchi marta qo'llanilganda yaratilgan va undan oldin engil proksi-server ishlatiladi) ishlash muammosini ma'lum darajada hal qiladi. Ammo arxitektura nuqtai nazaridan, boshqaruvchiga ko'plab bog'liqliklar yaratish ham mutlaqo to'g'ri emas. Bundan tashqari, tekshirgichlarni sinovdan o'tkazish odatda keraksiz operatsiya hisoblanadi. Albatta, hamma narsa sizning arizangizda test qanday tashkil etilganiga va o'zingiz bunga qanday munosabatda bo'lishingizga bog'liq.
Oldingi paragrafdan siz Dependency Injection-dan foydalanish arxitektura muammolarini to'liq bartaraf etmasligini tushundingiz. Shuning uchun, konteynerga havolani kontrollerlarda saqlash yoki saqlamaslik siz uchun qanday qulayroq bo'lishini o'ylab ko'ring. Bu erda yagona to'g'ri yechim yo'q. Menimcha, nazoratchi kodi oddiy bo'lib qolsa, ikkala yondashuv ham yaxshi. Lekin, albatta, siz kontrollerlardan tashqari Conatiner Aware xizmatlarini yaratmasligingiz kerak.

xulosalar

Xo'sh, aytilganlarning hammasini umumlashtirish vaqti keldi. Va juda ko'p aytilgan ... :)
Shunday qilib, ob'ektlarni yaratish ishini tuzish uchun biz quyidagi naqshlardan foydalanishimiz mumkin:
  • Registr: Shablonning aniq kamchiliklari bor, ularning eng asosiysi ob'ektlarni umumiy konteynerga qo'yishdan oldin ularni yaratish zarurati. Shubhasiz, biz undan foydalanishning foydasidan ko'ra ko'proq muammolarga duch kelamiz. Bu shablondan eng yaxshi foydalanish emasligi aniq.
  • Zavod usuli: Naqshning asosiy afzalligi: ob'ektlar aniq yaratilgan. Asosiy kamchilik: boshqaruvchilar yoki zavodlarni o'zlari yaratish haqida tashvishlanishlari kerak, bu sinf nomlarini qattiq kodlash muammosini to'liq hal qilmaydi yoki ramka boshqaruvchilarni barcha zarur zavodlar bilan ta'minlash uchun javobgar bo'lishi kerak, bu unchalik aniq bo'lmaydi. Ob'ektlarni yaratish jarayonini markazlashtirilgan holda boshqarish imkoniyati yo'q.
  • Xizmatni aniqlash: Ob'ektlarni yaratishni boshqarishning yanada rivojlangan usuli. Ob'ektlarni yaratishda duch keladigan umumiy vazifalarni avtomatlashtirish uchun qo'shimcha mavhumlik darajasidan foydalanish mumkin. Masalan:
    class ServiceContainer ArrayObject ni kengaytiradi ( umumiy funktsiya 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("[ $key ] tugmasi ostida xizmat ta'rifini topib bo'lmadi"); ))
    Service Locator-ning kamchiligi shundaki, sinflarning umumiy APIsi informatsion bo'lishni to'xtatadi. Unda qanday xizmatlar ishlatilishini tushunish uchun sinfning butun kodini o'qib chiqish kerak. Konteynerga havolani o'z ichiga olgan sinfni sinab ko'rish qiyinroq.
  • Bog'liqlik in'ektsiyasi: Aslida biz avvalgi namunadagi kabi bir xil xizmat ko'rsatish konteyneridan foydalanishimiz mumkin. Farqi shundaki, bu idish qanday ishlatiladi. Agar biz sinflarni konteynerga bog'liq qilishdan qochsak, biz aniq va aniq sinf API ni olamiz.
PHP ilovalarida ob'ektlar yaratish muammosi haqida sizga aytmoqchi bo'lganlar hammasi bu emas. Prototip namunasi ham mavjud, biz Reflection API-dan foydalanishni ko'rib chiqmadik, xizmatlarni dangasa yuklash muammosini va boshqa ko'plab nuanslarni chetga surib qo'ydik. Maqola juda uzun bo'lib chiqdi, shuning uchun men uni yakunlayman :)
Men Dependency Injection va boshqa naqshlar odatda ishonilgandek murakkab emasligini ko'rsatmoqchi edim.
Agar biz Dependency Injection haqida gapiradigan bo'lsak, masalan, ushbu naqshning KISS ilovalari mavjud

Kelajakdagi ma'lumotlar bazasining tuzilishiga to'xtalib o'tish. Boshlanish boshlandi va biz orqaga chekinolmaymiz va men bu haqda o'ylamayman ham.

Biz ma'lumotlar bazasiga birozdan keyin qaytamiz, ammo hozircha biz dvigatelimiz uchun kod yozishni boshlaymiz. Lekin birinchi navbatda, bir oz apparat. Boshlanishi.

Vaqtning boshlanishi

Hozirda biz amalga oshirmoqchi bo'lgan tizimning ishlashi haqida faqat ba'zi g'oyalar va tushunchalarga egamiz, lekin hali amalga oshirishning o'zi yo'q. Bizda ishlash uchun hech narsa yo'q: bizda hech qanday funksionallik yo'q - va siz eslaganingizdek, biz uni 2 qismga ajratdik: ichki va tashqi. Alifbo harflarni talab qiladi, ammo tashqi funksiya ichki funksiyalarni talab qiladi - biz shu erdan boshlaymiz.

Lekin unchalik tez emas. Uning ishlashi uchun siz biroz chuqurroq borishingiz kerak. Bizning tizimimiz ierarxiyani ifodalaydi va har qanday ierarxik tizimning boshlanishi bor: Linuxda o'rnatish nuqtasi, Windowsda mahalliy disk, davlat tizimi, kompaniya, ta'lim muassasasi va boshqalar. Bunday tizimning har bir elementi kimgadir bo'ysunadi va bir nechta bo'ysunuvchilarga ega bo'lishi mumkin va o'z qo'shnilari va ularga bo'ysunuvchilarga murojaat qilish uchun u yuqori yoki boshlang'ichning o'zidan foydalanadi. Ierarxik tizimning yaxshi namunasi - oila daraxti: boshlang'ich nuqta tanlanadi - qandaydir ajdod - va biz ketamiz. Bizning tizimimizda, shuningdek, biz filiallarni - modullarni, plaginlarni va boshqalarni o'stiradigan boshlang'ich nuqtaga muhtojmiz. Bizga barcha modullarimiz "muloqot qiladigan" qandaydir interfeys kerak. Keyingi ish uchun biz kontseptsiya bilan tanishishimiz kerak " dizayn namunasi" va ularning bir nechta ilovalari.

Dizayn naqshlari

Bu nima va qanday navlar borligi haqida juda ko'p maqolalar mavjud; mavzu juda noaniq va men sizga yangi hech narsa aytmayman. Mening sevimli Wiki-da ushbu mavzu bo'yicha ma'lumot mavjud: slaydli vagon va yana bir oz.

Dizayn naqshlari ko'pincha dizayn naqshlari yoki oddiygina naqshlar deb ataladi (inglizcha naqsh so'zidan tarjima qilingan, "naqsh" degan ma'noni anglatadi). Keyinchalik maqolalarda naqshlar haqida gapirganda, men dizayn naqshlarini nazarda tutaman.

Har qanday qo'rqinchli (va unchalik qo'rqinchli bo'lmagan) naqsh nomlarining ulkan ro'yxatidan bizni hozirgacha faqat ikkitasi qiziqtiradi: registr va singleton.

Registr (yoki ro'yxatdan o'ting) ma'lum bir massivda ishlaydigan naqsh bo'lib, unga ma'lum ob'ektlar to'plamini qo'shishingiz va olib tashlashingiz va ularning har qandayiga va uning imkoniyatlariga kirishingiz mumkin.

Yolg'iz (yoki singleton) - bu sinfning faqat bitta nusxasi mavjud bo'lishini ta'minlaydigan naqsh. Uni nusxalash, uyquga qo'yish yoki uyg'otish mumkin emas (PHP sehri haqida gapiramiz: __clone(), __sleep(), __wakeup()). Singleton global kirish nuqtasiga ega.

Ta'riflar to'liq yoki umumlashtirilmagan, ammo bu tushunish uchun etarli. Bizga ular alohida kerak emas. Biz ushbu naqshlarning har birining imkoniyatlariga qiziqamiz, lekin bitta sinfda: bunday naqsh deyiladi Singleton registr yoki Singleton Registry.

Bu bizga nima beradi?
  • Bizga reestrning yagona nusxasi bo'lishi kafolatlanadi, biz istalgan vaqtda ob'ektlarni qo'shishimiz va ularni kodning istalgan joyidan ishlatishimiz mumkin;
  • uni nusxalash va PHP tilining boshqa kiruvchi (bu holda) sehridan foydalanish mumkin bo'lmaydi.

Ushbu bosqichda, yagona registr bizga tizimning modulli tuzilishini amalga oshirishga imkon berishini tushunish kifoya, bu biz maqsadlarni muhokama qilishda xohlagan narsadir va siz rivojlanishning rivojlanishi bilan qolganini tushunasiz.

Xo'sh, so'zlar etarli, keling yarataylik!

Birinchi qatorlar

Ushbu sinf yadroning funksionalligi bilan bog'liq bo'lganligi sababli, biz loyihamizning ildizida yadro modullarining barcha sinflarini joylashtirgan yadro deb nomlangan papka yaratishdan boshlaymiz. Biz registrdan boshlaymiz, shuning uchun faylni registry.php deb nomlaymiz

Biz qiziqqan foydalanuvchining brauzer qatoriga faylimizga to'g'ridan-to'g'ri manzilni kiritishi bilan qiziqmaymiz, shuning uchun biz o'zimizni bundan himoya qilishimiz kerak. Ushbu maqsadga erishish uchun biz asosiy bajariladigan faylda ma'lum bir konstantani aniqlashimiz kerak, biz uni tekshiramiz. Bu g'oya yangi emas; esimda, u Joomla'da ishlatilgan. Bu oddiy va ishlaydigan usul, shuning uchun biz bu erda velosipedsiz qila olamiz.

Biz ulangan biror narsani himoya qilganimiz uchun biz doimiy _PLUGSECURE_ deb nomlaymiz:

Agar (!defined("_PLUGSECURE_")) ( die("To'g'ridan-to'g'ri modul chaqiruvi taqiqlangan!"); )

Endi, agar siz ushbu faylga to'g'ridan-to'g'ri kirishga harakat qilsangiz, hech qanday foydali narsa chiqmaydi, ya'ni maqsadga erishildi.

Keyinchalik, men barcha modullarimiz uchun ma'lum bir standartni belgilashni taklif qilaman. Men har bir modulni u haqida ba'zi ma'lumotlarni qaytaradigan funksiya bilan ta'minlamoqchiman, masalan, modul nomi va bu funksiya sinfda talab qilinishi kerak. Ushbu maqsadga erishish uchun biz quyidagilarni yozamiz:

StorableObject interfeysi (ommaviy statik funksiya getClassName(); )

Mana bunday. Endi har qanday sinfni funksiyasiz ulashsak getClassName() xato xabarini ko'ramiz. Men hozircha bunga e'tibor bermayman, bu bizga keyinroq, hech bo'lmaganda sinab ko'rish va tuzatish uchun foydali bo'ladi.

Bizning bo'ydoqlar reestrining o'zi uchun vaqt keldi. Biz sinfni va uning ba'zi o'zgaruvchilarini e'lon qilishdan boshlaymiz:

Class Registry StorableObject ( //modul nomi o'qilishi mumkin bo'lgan xususiy statik $className = "Registry"; //ro'yxatga olish kitobi xususiy statik $instance; //ob'ektlar qatori xususiy statik $ob'ektlar = array();

Hozircha hamma narsa mantiqiy va tushunarli. Endi, siz eslaganingizdek, bizda singleton xususiyatlariga ega registr mavjud, shuning uchun darhol registr bilan shu tarzda ishlashga imkon beradigan funktsiyani yozaylik:

Umumiy statik funksiya singleton() ( if(!isset(self::$instance)) ( $obj = __CLASS__; self::$instance = new $obj; ) return self::$instance; )

So'zma-so'z: funktsiya bizning reestrimizning namunasi mavjudligini tekshiradi: agar bo'lmasa, uni yaratadi va qaytaradi; agar u allaqachon mavjud bo'lsa, uni qaytaradi. Bunday holda, bizga sehr kerak emas, shuning uchun himoya qilish uchun biz uni shaxsiy deb e'lon qilamiz:

Maxfiy funksiya __construct()() xususiy funksiya __clone()() xususiy funksiya __wakeup()() xususiy funksiya __sleep() ()

Endi bizga ob'ektni reestrimizga qo'shish funksiyasi kerak - bu funktsiya sozlagich deb ataladi va men sehrdan qanday foydalanishimiz va ob'ektni qo'shishning muqobil usulini taqdim etish uchun uni ikki usulda amalga oshirishga qaror qildim. Birinchi usul standart funktsiyadir, ikkinchisi birinchisini __set() sehri orqali bajaradi.

//$object - bog'langan ob'ektga yo'l //$key - registrdagi ob'ektga kirish kaliti umumiy funksiya addObject($key, $object) ( require_once($object); //ob'ektlar massivida ob'ekt yaratish self::$objects[ $key] = new $key(self::$instance); ) //sehrli umumiy funksiya orqali muqobil usul __set($key, $object) ( $this->addObject($key, $) ob'ekt);)

Endi, reestrimizga ob'ekt qo'shish uchun biz ikki turdagi yozuvlardan foydalanishimiz mumkin (aytaylik, biz allaqachon $registry registrini yaratdik va config.php faylini qo'shmoqchimiz):

$registry->addObject("config", "/core/config.php"); //muntazam usul $registry->config = "/core/config.php"; //PHP sehrli funksiyasi orqali __set()

Ikkala yozuv ham bir xil funktsiyani bajaradi - ular faylni bog'laydi, sinfning namunasini yaratadi va uni kalit bilan registrga joylashtiradi. Bu erda bir muhim jihat bor, kelajakda bu haqda unutmasligimiz kerak: registrdagi obyekt kaliti ulangan obyektdagi sinf nomiga mos kelishi kerak. Agar siz kodni qayta ko'rib chiqsangiz, nima uchun ekanligini tushunasiz.

Qaysi yozuvdan foydalanish sizga bog'liq. Men sehrli usul orqali yozishni afzal ko'raman - bu "chiroyli" va qisqaroq.

Shunday qilib, biz ob'ektni qo'shishni tartibladik, endi bizga kalit orqali ulangan ob'ektga kirish funktsiyasi kerak - oluvchi. Men uni sozlagichga o'xshash ikkita funktsiya bilan ham amalga oshirdim:

//obyektni registrdan olish //$key - massivdagi kalit umumiy funksiya getObject($key) ( //o‘zgaruvchining ob’ekt ekanligini tekshiring if (is_object(self::$objects[$key]))) ( //agar shunday bo'lsa, biz bu obyektni qaytaramiz self::$objects[$key]; ) ) //sehrli umumiy funksiya orqali shunga o'xshash usul __get($key) ( if (is_object(self::$objects[$) kalit])) ( o'zini qaytarish: :$objects[$key]; ) )

Sozlagichda bo'lgani kabi, ob'ektga kirish uchun bizda ikkita ekvivalent yozuvlar bo'ladi:

$registry->getObject("config"); // oddiy usul $registry->config; //PHP sehrli funksiyasi orqali __get()

Diqqatli o'quvchi darhol savol beradi: Nima uchun __set() sehrli funksiyasida men oddiy (sehrli bo'lmagan) ob'ekt qo'shish funktsiyasini chaqiraman, lekin __get() oluvchida bir xil qo'ng'iroq o'rniga getObject() funktsiya kodini ko'chiraman? Rostini aytsam, men bu savolga etarlicha aniq javob bera olmayman, men boshqa modullarda __get() sehri bilan ishlashda muammolarga duch kelganimni aytaman, lekin kodni "boshqa" qayta yozishda bunday muammolar yo'q.

Balki shuning uchun ham men maqolalarda PHP sehrli usullariga nisbatan tanbeh va ulardan foydalanmaslik bo'yicha maslahatlarni tez-tez ko'rganman.

"Barcha sehr o'z narxi bilan keladi." © Rumplestiltskin

Ushbu bosqichda bizning registrimizning asosiy funksionalligi allaqachon tayyor: biz registrning yagona nusxasini yaratishimiz, ob'ektlarni qo'shishimiz va ularga odatiy usullardan foydalangan holda ham, PHP tilining sehrli usullari orqali ham kirishimiz mumkin. "O'chirish haqida nima deyish mumkin?"— bizga hozircha bu funksiya kerak emas va kelajakda biror narsa o‘zgarishiga ishonchim komil emas. Oxir-oqibat, biz har doim kerakli funksionallikni qo'shishimiz mumkin. Ammo endi biz ro'yxatga olish kitobimizning namunasini yaratishga harakat qilsak,

$registry = Registry::singleton();

biz xatoga duch kelamiz:

Jiddiy xato: Sinf registrida 1 ta mavhum usul mavjud va shuning uchun mavhum deb e'lon qilinishi yoki qolgan usullarni (StorableObject::getClassName) ...

Buning sababi, biz kerakli funktsiyani yozishni unutganmiz. Esingizdami, boshida modul nomini qaytaradigan funksiya haqida gapirgan edim? Bu to'liq funksionallik uchun qo'shilishi kerak bo'lgan narsa. Bu oddiy:

Umumiy statik funksiya getClassName() ( self::$className; ni qaytaradi)

Endi hech qanday xato bo'lmasligi kerak. Men yana bitta funktsiyani qo'shishni taklif qilaman, bu shart emas, lekin ertami-kechmi u foydali bo'lishi mumkin, biz uni kelajakda tekshirish va tuzatish uchun ishlatamiz. Funktsiya reestrimizga qo'shilgan barcha ob'ektlar (modullar) nomlarini qaytaradi:

Ommaviy funksiya getObjectsList() ( //biz qaytaradigan massiv $names = array(); //foreach(self::$objects as $obj) ob'ektlar massividan har bir ob'ekt nomini oling) ( $names = $ obj->getClassName() ; ) //array_push($names, self::getClassName()); massivga registr moduli nomini qo‘shing; //va $names; ni qaytaring.

Ana xolos. Bu registrni yakunlaydi. Keling, uning ishini tekshiramizmi? Tekshirish paytida biz biror narsani ulashimiz kerak - konfiguratsiya fayli bo'lsin. Yangi core/config.php faylini yarating va reestrimiz talab qiladigan minimal tarkibni qo'shing:

//konstantani tekshirishni unutmang, agar (!defined("_PLUGSECURE_")) ( die("To'g'ridan-to'g'ri modul chaqiruvi taqiqlangan!"); ) class Config ( //modul nomi, o'qilishi mumkin bo'lgan shaxsiy statik $className = "Config" "; umumiy statik funksiya getClassName() ( qaytar self::$className; ) )

Shunga o'xshash narsa. Endi tekshirishning o'ziga o'taylik. Loyihamizning ildizida index.php faylini yarating va unga quyidagi kodni yozing:

Aniqlash("_PLUGSECURE_", rost); //constanta belgilangan ob'ektlarga to'g'ridan-to'g'ri kirishdan himoya qilish uchun require_once "/core/registry.php"; //registr ulandi $registry = Registry::singleton(); //registrning yagona nusxasi yaratildi $registry->config = "/core/config.php"; //bizning, hozirgacha foydasiz, konfiguratsiyani ulang //ulangan modullarning nomlarini aks ettiring echo " Ulangan"; foreach ($registry->

  • ". $ismlar."
  • "; }

    Yoki, agar siz hali ham sehrdan qochsangiz, 5-qatorni muqobil usul bilan almashtirish mumkin:

    Aniqlash("_PLUGSECURE_", rost); //constanta belgilangan ob'ektlarga to'g'ridan-to'g'ri kirishdan himoya qilish uchun require_once "/core/registry.php"; //registr ulandi $registry = Registry::singleton(); //registrning yagona nusxasi yaratildi $registry->addObject("config", "/core/config.php"); //bizning, hozirgacha foydasiz, konfiguratsiyani ulang //ulangan modullarning nomlarini aks ettiring echo " Ulangan"; foreach ($registry->getObjectsList() $names sifatida) ( echo "

  • ". $ismlar."
  • "; }

    Endi brauzerni oching va manzil satriga http://localhost/index.php yoki oddiygina http://localhost/ yozing. (standart Open Server yoki shunga o'xshash veb-server sozlamalaridan foydalansangiz tegishli)

    Natijada, biz shunga o'xshash narsani ko'rishimiz kerak:

    Ko'rib turganingizdek, hech qanday xatolik yo'q, ya'ni hamma narsa ishlaydi, buning uchun sizni tabriklayman :)

    Bugun biz bunga to'xtalamiz. Keyingi maqolada biz ma'lumotlar bazasiga qaytamiz va MySQL SUDB bilan ishlash uchun sinf yozamiz, uni registrga ulaymiz va ishni amalda sinab ko'ramiz. Ko'rishguncha!

    Singleton kabi bu naqsh kamdan-kam hollarda ishlab chiquvchilarning ijobiy reaktsiyasini keltirib chiqaradi, chunki u ilovalarni sinovdan o'tkazishda bir xil muammolarni keltirib chiqaradi. Shunga qaramay, ular so'kadilar, lekin faol foydalanadilar. Singleton singari, Ro'yxatga olish kitobi naqshlari ko'plab ilovalarda mavjud va u yoki bu tarzda muayyan muammolarni hal qilishni sezilarli darajada osonlashtiradi.

    Keling, ikkala variantni ham tartibda ko'rib chiqaylik.

    "Sof registr" yoki oddiygina Registr deb ataladigan narsa statik interfeysga ega sinfni amalga oshirishdir. Singleton naqshidan asosiy farq shundaki, u sinfning kamida bitta nusxasini yaratish qobiliyatini bloklaydi. Shu nuqtai nazardan, __clone() va __wakeup() sehrli usullarini shaxsiy yoki himoyalangan modifikator orqasida yashirishning ma'nosi yo'q.

    Ro'yxatga olish sinfi ikkita statik usulga ega bo'lishi kerak - getter va setter. Sozlagich o'tkazilgan ob'ektni berilgan kalitga bog'langan holda saqlashga joylashtiradi. Qabul qiluvchi mos ravishda do'kondan ob'ektni qaytaradi. Do'kon assotsiativ kalit-qiymat massividan boshqa narsa emas.

    Ro'yxatga olish kitobini to'liq nazorat qilish uchun yana bir interfeys elementi kiritiladi - bu ob'ektni saqlashdan o'chirish imkonini beruvchi usul.

    Singleton naqshiga o'xshash muammolarga qo'shimcha ravishda yana ikkitasi mavjud:

    • boshqa turdagi qaramlikni joriy qilish - ro'yxatga olish kitobi kalitlariga;
    • ikki xil ro'yxatga olish kitobi kalitlari bir xil ob'ektga havola bo'lishi mumkin

    Birinchi holda, qo'shimcha qaramlikdan qochish mumkin emas. Qaysidir ma'noda biz kalit nomlarga bog'lanib qolamiz.

    Ikkinchi muammo Registry::set() usuliga chekni kiritish orqali hal qilinadi:

    Umumiy statik funksiyalar toʻplami($kalit, $item) ( agar (!array_key_mavjud boʻlsa($kalit, self::$_registry)) ( foreach (self::$_registry as $val) ( if ($val === $element) (yangi Istisno oching("Element allaqachon mavjud"); ) ) self::$_registry[$key] = $element; ) )

    « Ro'yxatga olish kitobi namunasini tozalash"boshqa muammoni keltirib chiqaradi - sinf nomi orqali setter va getterga kirish zarurati tufayli qaramlikni oshiradi. Ushbu yondashuv mavjud bo'lganda, Singleton naqshida bo'lgani kabi ob'ektga havola yaratib, u bilan ishlay olmaysiz:

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

    Bu erda biz Singleton misoliga havolani, masalan, joriy sinf xususiyatida saqlash va OOP mafkurasi talab qilganidek, u bilan ishlash imkoniyatiga egamiz: uni birlashtirilgan ob'ektlarga parametr sifatida o'tkazish yoki avlodlarda foydalanish.

    Bu muammoni hal qilish uchun mavjud Singleton registrini joriy qilish, bu ko'pchilikka yoqmaydi, chunki bu ortiqcha kod kabi ko'rinadi. Menimcha, bunday munosabatning sababi OOP tamoyillarini noto'g'ri tushunish yoki ularni ataylab mensimaslikdir.

    _registry[$key] = $object; ) statik umumiy funksiya get($key) ( return self::getInstance()->_registry[$key]; ) xususiy funksiya __wakeup() ( ) xususiy funksiya __construct() ( ) xususiy funksiya __clone() ( ) ) ?>

    Pulni tejash uchun men usullar va xususiyatlar uchun izoh bloklarini ataylab o'tkazib yubordim. Menimcha, ular kerak emas.

    Yuqorida aytib o'tganimdek, asosiy farq shundaki, endi ro'yxatga olish kitobi hajmiga havolani saqlash va har safar statik usullarga noqulay qo'ng'iroqlarni ishlatmaslik mumkin. Bu variant menga biroz to'g'riroq ko'rinadi. Mening fikrimga qo'shilish yoki qo'shilmaslik juda muhim emas, xuddi mening fikrim kabi. Amalga oshirishning hech qanday nozikligi naqshni ko'rsatilgan bir qator kamchiliklardan bartaraf eta olmaydi.

    Hayotimizda tez-tez ishlatiladigan naqshlar, ko'proq misollar, kamroq suv haqida qisqacha yozishga qaror qildim.

    Singleton

    “Yagona”ning asosiy mazmuni shundaki, “Menga telefon stansiyasi kerak” desangiz, ular sizga “qayta quramiz” emas, “u yerda allaqachon qurilgan”, deyishadi. "Yolg'iz" har doim yolg'iz.

    Sinf Singleton ( private static $instance = null; private function __construct())( /* ... @return Singleton */ ) // Yangi Singleton xususiy funksiyasi __clone() ( /* ... @return Singleton) orqali yaratilishdan himoya qiling * / ) // Maxfiy funksiyani klonlash orqali yaratilishdan himoya qilish __wakeup() ( /* ... @return Singleton */ ) // GetInstance() (if (is_null(self::$instance)) seriyasiz umumiy statik funksiyasi orqali yaratishdan himoya qilish ) ) ( self::$instance = new self; ) return self::$instance; ) )

    Ro'yxatga olish kitobi (reestr, yozuvlar jurnali)

    Nomidan ko'rinib turibdiki, bu naqsh unda joylashtirilgan yozuvlarni saqlash uchun mo'ljallangan va shunga mos ravishda, agar kerak bo'lsa, ushbu yozuvlarni (nomi bo'yicha) qaytaradi. Telefon stansiyasi misolida bu aholining telefon raqamlariga nisbatan reestrdir.

    Sinf registri ( xususiy $registry = array(); umumiy funksiyalar toʻplami($kalit, $object) ( $this->registry[$key] = $object; ) umumiy funksiya get($key) ($this->registrni qaytaring [$key];))

    Singleton registr- bilan aralashtirmang)

    "Ro'yxatga olish kitobi" ko'pincha "yolg'iz", lekin har doim ham shunday bo'lishi shart emas. Misol uchun, biz buxgalteriya bo'limida bir nechta jurnallar yaratishimiz mumkin, birida "A" dan "M" gacha, ikkinchisida "N" dan "Z" gacha bo'lgan xodimlar mavjud. Har bir bunday jurnal "reestr" bo'ladi, lekin "yagona" emas, chunki allaqachon 2 ta jurnal mavjud.

    Sinf SingletonRegistry ( private static $instance = null; private $registry = array(); private function __construct() ( /* ... @return Singleton */ ) // Yangi Singleton xususiy funksiyasi __clone() (/) orqali yaratilishdan himoya qiling * ... @return Singleton */ ) // Xususiy funksiyani klonlash orqali yaratilishdan himoya qilish __wakeup() ( /* ... @return Singleton */ ) // GetInstance() umumiy statik funksiyasini seriyadan chiqarish orqali yaratishdan himoya qilish ( if ( is_null(self::$instance)) ( self::$instance = new self; ) return self::$instance; ) public function set($key, $object) ( $this->registry[$key] = $ obyekt; ) get($key) umumiy funksiyasi ($this->registr[$key]; ) qaytaring)

    Multiton ("yakkaliklar" hovuzi) yoki boshqacha qilib aytgandaRegistry Singleton ) - Singleton Registry bilan adashtirmang

    Ko'pincha "ro'yxatga olish" "singllarni" saqlash uchun maxsus ishlatiladi. Lekin, chunki "ro'yxatga olish kitobi" naqsh "generativ naqsh" emas, lekin men "registr" ni "singleton" bilan bog'liq holda ko'rib chiqmoqchiman.Shuning uchun biz naqsh o'ylab topdik Multiton, bunga ko'raAsosiysi, bu bir nechta "yakkaliklar" ni o'z ichiga olgan "ro'yxatga olish kitobi" bo'lib, ularning har biri o'z "nomiga" ega bo'lib, unga kirish mumkin.

    Qisqa: ushbu klass ob'ektlarini yaratishga imkon beradi, lekin faqat ob'ektga nom bersangiz. Haqiqiy misol yo'q, lekin men Internetda quyidagi misolni topdim:

    Sinf ma'lumotlar bazasi ( xususiy statik $instances = array(); xususiy funksiya __construct() ( ) xususiy funksiya __clone() ( ) umumiy statik funksiya 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); // obyekt(Ma'lumotlar bazasi)#1 (0) ( ) $logger = Ma'lumotlar bazasi::getInstance("logger"); var_dump($logger); // obyekt(Ma'lumotlar bazasi)#2 (0) ( ) $masterDupe = Ma'lumotlar bazasi::getInstance("master"); var_dump($masterDupe); // object(Ma'lumotlar bazasi)#1 (0) ( ) // Muhim xato: Shaxsiy ma'lumotlar bazasiga qo'ng'iroq::__construct() noto'g'ri kontekstdan $dbFatalError = new Database(); // PHP halokatli xatosi: Shaxsiy ma'lumotlar bazasiga qo'ng'iroq qilish::__clone() $dbCloneError = $masterDupe klonlash;

    Ob'ektlar hovuzi

    Asosan bu naqsh faqat ob'ektlarni, hech qanday satrlarni, massivlarni va hokazolarni saqlaydigan "ro'yxatga olish kitobi". ma'lumotlar turlari.

    Zavod

    Naqshning mohiyati uning nomi bilan deyarli to'liq tasvirlangan. Sharbat qutilari kabi ba'zi narsalarni olish kerak bo'lganda, ular zavodda qanday tayyorlanishini bilishingiz shart emas. Siz shunchaki: "Menga bir karton apelsin sharbati bering" deysiz va "zavod" sizga kerakli paketni qaytarib beradi. Qanaqasiga? Bularning barchasini zavodning o'zi hal qiladi, masalan, u allaqachon mavjud standartdan "nusxa oladi". "Zavod" ning asosiy maqsadi, agar kerak bo'lsa, sharbat paketining "tashqi ko'rinishi" jarayonini o'zgartirishga imkon berishdir va iste'molchiga bu haqda hech narsa aytilmasligi kerak, shunda u buni talab qilishi mumkin. avvalgidek. Qoidaga ko'ra, bitta zavod faqat bitta turdagi "mahsulot" ni "ishlab chiqarish" bilan shug'ullanadi. Avtomobil shinalarini ishlab chiqarishni hisobga olgan holda "sharbat zavodi" ni yaratish tavsiya etilmaydi. Hayotda bo'lgani kabi, zavod naqshini ko'pincha bitta odam yaratadi.

    Mavhum sinf AnimalAbstract ( protected $species; public function getSpecies() ( return $this->species; ) ) sinf Cat kengaytiriladi AnimalAbstract ( himoyalangan $turlar = "cat"; ) sinf It kengaytiriladi AnimalAbstract ( himoyalangan $turlar = "it"; ) sinf AnimalFactory ( umumiy statik funktsiya fabrikasi($animal) ( switch ($animal) ( "mushuk" ishi: $obj = new Cat(); break; "it" ishi: $obj = yangi Dog(); tanaffus; standart : throw new Exception("Hayvonlar zavodi hayvon turini yarata olmadi "" . $hayvon . """, 1000); ) return $obj; ) ) $cat = AnimalFactory::factory("mushuk"); // object(Cat)#1 echo $cat->getSpecies(); // cat $dog = AnimalFactory::factory("it"); // object(Dog)#1 echo $dog->getSpecies(); // it $gippo = AnimalFactory::factory("gippopotamus"); // Bu Istisnoni keltirib chiqaradi

    Men sizning e'tiboringizni zavod usuli ham naqsh ekanligiga qaratmoqchiman, u Zavod usuli deb ataladi.

    Quruvchi (quruvchi)

    Shunday qilib, biz allaqachon tushunganmizki, "Zavod" bu ichimliklar sotadigan avtomat, unda hamma narsa tayyor va siz shunchaki kerakli narsani aytasiz. "Quruvchi" - bu ichimliklar ishlab chiqaradigan zavod va barcha murakkab operatsiyalarni o'z ichiga oladi va so'rovga qarab murakkab ob'ektlarni oddiyroqlaridan (qadoqlash, yorliq, suv, lazzatlar va boshqalar) yig'ishi mumkin.

    Class Bottle ( public $name; public $litr; ) /** * barcha quruvchilar */ interfeysi kerak BottleBuilderInterface ( setName( umumiy funktsiya); setLiters umumiy funktsiyasi; getResult(); ) sinfi CocaColaBuilder BottleBuilderInterface ( xususiy $) ni amalga oshiradi. shisha; umumiy funktsiya __construct() ( $this->bottle = new Bottle(); ) umumiy funktsiya setName($value) ($this->bottle->name = $value; ) umumiy funktsiya setLiters($value) ($ this->shisha->litr = $value; ) umumiy funksiya getResult() ($this->shisha; ) qaytaring ) $juice = new CocaColaBuilder(); $juice->setName("Coca-Cola Light"); $juice->setLitr(2); $juice->getResult();

    Prototip

    Zavodga o'xshab, u ob'ektlarni yaratishga ham xizmat qiladi, ammo biroz boshqacha yondashuv bilan. O'zingizni barda tasavvur qiling-a, siz pivo ichgan edingiz va pivo tugayapti, bufetchiga aytasiz - menga xuddi shunday turdagi boshqasini tayyorlang. Bufetchi o'z navbatida siz ichayotgan pivoga qaraydi va siz so'raganingizdek nusxa ko'chiradi. PHP-da allaqachon ushbu naqshni amalga oshirish mavjud, u deyiladi.

    $newJuice = $juice klonlash;

    Dangasa ishga tushirish

    Masalan, xo'jayin har xil faoliyat turlari bo'yicha hisobotlar ro'yxatini ko'radi va bu hisobotlar allaqachon mavjud deb o'ylaydi, lekin aslida faqat hisobotlarning nomlari ko'rsatiladi va hisobotlarning o'zi hali yaratilmagan va faqat yaratiladi. buyurtma bo'yicha (masalan, hisobotni ko'rish tugmasini bosish orqali). Dangasa ishga tushirishning alohida holati ob'ektga kirish vaqtida uni yaratishdir. Siz Vikipediyada qiziqarli narsani topishingiz mumkin, lekin... nazariyaga ko'ra, php-dagi to'g'ri misol, masalan, funktsiya bo'ladi

    Adapter yoki o'ram (adapter, o'ram)

    Ushbu naqsh uning nomiga to'liq mos keladi. "Sovet" vilkasini evro rozetkasi orqali ishlashi uchun adapter kerak. "Adapter" aynan shunday qiladi - u bir-biri bilan to'g'ridan-to'g'ri ishlay olmaydigan boshqa ikkita o'rtasida oraliq ob'ekt bo'lib xizmat qiladi. Ta'rifga qaramay, amalda men Adapter va Wrapper o'rtasidagi farqni ko'rmoqdaman.

    Sinf MyClass ( umumiy funktsiya methodA() () ) class MyClassWrapper ( umumiy funktsiya __construct())( $this->myClass = new MyClass(); ) umumiy funksiya __call($name, $arguments)( Log::info(" Siz $name usulini chaqirmoqchisiz."); return call_user_func_array(array($this->myClass, $name), $arguments); ) ) $obj = new MyClassWrapper(); $obj->metodA();

    Bog'liqlik in'ektsiyasi

    Bog'liqlik in'ektsiyasi ba'zi funktsiyalar uchun javobgarlikning bir qismini boshqa ob'ektlarga o'tkazish imkonini beradi. Misol uchun, agar biz yangi xodimlarni yollashimiz kerak bo'lsa, biz o'z kadrlar bo'limimizni yarata olmaymiz, balki ishga yollash kompaniyasiga qaramlikni joriy qilamiz, bu esa, o'z navbatida, bizning birinchi iltimosimiz bo'yicha "bizga odam kerak" bo'lib ishlaydi. Kadrlar bo'limining o'zi yoki ushbu xizmatlarni taqdim etadigan boshqa kompaniya ("xizmat lokatori" yordamida) topadi.
    "Bog'liqlik in'ektsiyasi" sizga umumiy funksionallikni yo'qotmasdan kompaniyaning alohida qismlarini almashtirish va almashtirish imkonini beradi.

    AppleJuice () sinfi // bu usul bog'liqlik in'ektsiya naqshining ibtidoiy amalga oshirilishidir va bundan keyin siz getBottleJuice())( $obj = new funksiyasini ko'rasiz. Olma sharbati Olma sharbati)( return $obj; ) ) $bottleJuice = getBottleJuice();

    Endi tasavvur qiling-a, biz endi olma sharbatini emas, balki apelsin sharbatini xohlaymiz.

    AppleJuice() sinfi Apelsin sharbati() // bu usul getBottleJuice())( $obj = new) qaramlik inyeksiya funksiyasini amalga oshiradi Apelsin sharbati; // ob'ektni tekshirib ko'ring, agar ular bizga pivoni olib qo'ygan bo'lsa (pivo sharbat emas) if($obj instanceof Apelsin sharbati)( $objni qaytaring; ))

    Ko'rib turganingizdek, biz nafaqat sharbat turini, balki sharbat turini tekshirishni ham o'zgartirishimiz kerak edi, bu juda qulay emas. Dependency inversion printsipidan foydalanish ancha to'g'ri:

    Interfeys Juice () AppleJuice sinfi Juice () sinfi OrangeJuice Juice () funksiyasini qo‘llaydi getBottleJuice())( $obj = new OrangeJuice; // obyektni tekshiring, agar ular bizga pivo (pivo sharbat emas) sirg‘alib ketgan bo‘lsalar) if($obj) misol Sharbat)( $objni qaytaring; ))

    Ba'zida bog'liqlik inversiyasini Dependency in'ektsiyasi bilan chalkashtirib yuborishadi, lekin ularni chalkashtirishning hojati yo'q, chunki Bog'liqlik inversiyasi naqsh emas, balki printsipdir.

    Xizmatni aniqlash

    "Servis Locator" - "Dependency Injection" ni amalga oshirish usuli. U ishga tushirish kodiga qarab har xil turdagi ob'ektlarni qaytaradi. Quruvchi, zavod yoki boshqa narsa tomonidan yaratilgan sharbat paketimizni xaridor xohlagan joyga yetkazib berish vazifasi qolsin. Lokatorga “bizga yetkazib berish xizmatini bering” deymiz va xizmatdan sharbatni kerakli manzilga yetkazishini so‘raymiz. Bugun bitta xizmat bor, ertaga esa boshqa bo'lishi mumkin. Bu qanday maxsus xizmat ekanligi biz uchun muhim emas, bu xizmat biz aytgan narsalarni va qayerda aytib berishini bilish biz uchun muhim. O'z navbatida, xizmatlar “Etkazib berish<предмет>yoqilgan<адрес>».

    Agar biz haqiqiy hayot haqida gapiradigan bo'lsak, unda PDO PHP kengaytmasi Service Locatorning yaxshi namunasi bo'lishi mumkin, chunki Bugun biz MySQL ma'lumotlar bazasi bilan ishlaymiz, ertaga esa PostgreSQL bilan ishlashimiz mumkin. Siz allaqachon tushunganingizdek, bizning sinfimiz uchun u o'z ma'lumotlarini qaysi ma'lumotlar bazasiga yuborishi muhim emas, u buni qila olishi muhim.

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

    Dependency injection va Service Locator o'rtasidagi farq

    Agar siz hali e'tibor bermagan bo'lsangiz, men tushuntirmoqchiman. Bog'liqlik in'ektsiyasi Natijada, u xizmatni emas (biror joyda biror narsani etkazib berishi mumkin), balki ma'lumotlaridan foydalanadigan ob'ektni qaytaradi.

    Men sizga PHP-da Registry naqshini amalga oshirishim haqida aytib berishga harakat qilaman. Ro'yxatga olish kitobi global o'zgaruvchilar uchun OOP o'rnini bosuvchi vosita bo'lib, ma'lumotlarni saqlash va tizim modullari o'rtasida uzatish uchun mo'ljallangan. Shunga ko'ra, u standart xususiyatlarga ega - yozish, o'qish, o'chirish. Bu erda odatiy amalga oshirish.

    Shunday qilib, biz $key = $value - Registry::set($key, $value) $key - Registry::get($key) unset($key) - Registry::remove usullarini ahmoqona almashtiramiz. ($key ) Bu shunchaki noma'lum bo'lib qoladi - nima uchun bu qo'shimcha kod. Shunday qilib, keling, sinfimizga global o'zgaruvchilar qila olmaydigan narsalarni qilishni o'rgataylik. Keling, unga qalampir qo'shamiz.

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

    Naqshning odatiy vazifalariga men o'zgaruvchini o'zgarishlardan blokirovka qilish qobiliyatini qo'shdim, bu katta loyihalarda juda qulay, siz tasodifan hech narsa kiritmaysiz. Masalan, ma'lumotlar bazalari bilan ishlash uchun qulay
    define('DB_DNS', 'mysql:host=localhost;dbname=) ’);
    aniqlash ('DB_USER', ' ’);
    aniqlash('DB_PASSWORD', ' ’);
    aniqlash('DB_HANDLE');

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

    Endi kodni tushuntirish uchun, ma'lumotlarni saqlash uchun biz $data statik o'zgaruvchisidan foydalanamiz, $lock o'zgaruvchisi o'zgartirish uchun qulflangan kalitlar haqidagi ma'lumotlarni saqlaydi. Tarmoqda biz o'zgaruvchining qulflanganligini tekshiramiz va uni registrga o'zgartiramiz yoki qo'shamiz. O'chirishda biz qulfni ham tekshiramiz; qabul qiluvchi o'zgarishsiz qoladi, standart ixtiyoriy parametr bundan mustasno. Xo'sh, ba'zi sabablarga ko'ra kamdan-kam qo'llaniladigan istisnolarni ko'rib chiqishga e'tibor qaratish lozim.Aytgancha, menda istisnolar bo'yicha loyiha allaqachon bor, maqolani kuting. Quyida sinov uchun kod loyihasi bor, bu erda test haqida maqola bor, uni yozish ham zarar qilmaydi, garchi men TDD muxlisi emasman.

    Keyingi maqolada biz ma'lumotlarni ishga tushirishni qo'shish va "dangasalik" ni amalga oshirish orqali funksionallikni yanada kengaytiramiz.