Odamlar uchun design pattern'lar
Design pattern'larning dasturlash tiliga bog'liq bo'lmagan, juda soddalashtirilgan tushuntirishi
Muallif: Kamran Ahmed
Design pattern'lar — takrorlanuvchi muammolarga yechimlar; muayyan muammolarni qanday hal qilish bo'yicha yo'l-yo'riqlar. Ular ilovangizga ulab qo'yib, mo''jiza yuz berishini kutib o'tiradigan sinflar, package'lar yoki kutubxonalar emas. Aksincha, ular muayyan vaziyatlarda muayyan muammolarni qanday hal qilish bo'yicha yo'l-yo'riqlardir.
Design pattern'lar — takrorlanuvchi muammolarga yechimlar; muayyan muammolarni qanday hal qilish bo'yicha yo'l-yo'riqlar
Wikipedia ularni shunday ta'riflaydi
Software engineering'da software design pattern — dasturiy ta'minotni loyihalashda ma'lum bir kontekstda tez-tez uchraydigan muammoga umumiy, qayta ishlatiladigan yechim. U to'g'ridan-to'g'ri manba kodiga yoki mashina kodiga aylantirilishi mumkin bo'lgan tayyor dizayn emas. U ko'plab turli vaziyatlarda qo'llash mumkin bo'lgan, muammoni qanday hal qilish haqidagi tavsif yoki andozadir.
Ehtiyot bo'ling
Dasturchilar, asosan yangi boshlovchilar, ortiqcha o'ylab, design pattern'larni zo'rlab qo'llash xatosiga yo'l qo'yishadi va natijada dahshatli, maintain qilib bo'lmaydigan chalkashlik paydo bo'ladi. Asosiy qoida — codebase'ni iloji boricha sodda saqlash; development'ni boshlaganingizdan so'ng codebase'da pattern'lar takrorlanayotganini ko'ra boshlaysiz va ana shunda tegishli design pattern'larni implement qilishga kirishishingiz mumkin.
- Design pattern'lar barcha muammolaringizni hal qiladigan sehrli tayoqcha emas.
- Ularni zo'rlab qo'llashga urinmang; agar shunday qilsangiz, yomon oqibatlar bo'lishi muqarrar.
- Yodda tuting: design pattern'lar muammolarga yechim, muammo qidiradigan yechim emas; shuning uchun ortiqcha o'ylamang.
- Agar to'g'ri joyda va to'g'ri usulda ishlatilsa, ular sizga najot bo'lishi mumkin; aks holda ular dahshatli darajada chalkash kodga olib kelishi mumkin.
Shuni ham yodda tutingki, quyidagi kod namunalari PHP-7'da yozilgan, ammo bu sizni to'xtatmasligi kerak, chunki tushunchalar baribir bir xil.
Design pattern turlari
Ushbu qo'llanma Gang of Four (GoF) design pattern'lari haqida; bu nom ushbu design pattern'larni ilk bor taqdim etgan kitobning to'rt muallifiga ishora qiladi. Design pattern'larning uch turi bor:
Creational Design Patterns
Oddiy so'z bilan aytganda
Creational pattern'lar obyektni yoki o'zaro bog'liq obyektlar guruhini qanday instance qilish (yaratish) masalasiga qaratilgan.
Wikipedia'da shunday deyilgan
Software engineering'da creational design pattern'lar — obyekt yaratish mexanizmlari bilan shug'ullanadigan va obyektlarni vaziyatga mos tarzda yaratishga harakat qiladigan design pattern'lar. Obyekt yaratishning oddiy shakli dizayn muammolariga yoki dizaynga qo'shimcha murakkablik qo'shilishiga olib kelishi mumkin. Creational design pattern'lar bu muammoni obyekt yaratilishini qandaydir tarzda nazorat qilish orqali hal qiladi.
Creational pattern'larning 6 turi bor
🏠 Simple Factory
Hayotiy misol
Tasavvur qiling, siz uy qurayapsiz va sizga eshiklar kerak. Siz yo duradgorlik kiyimingizni kiyib, yog'och, yelim, mix va eshik yasash uchun zarur bo'lgan barcha asboblarni olib kelib, eshikni uyingizning o'zida yasashga kirishishingiz mumkin, yoki shunchaki fabrikaga qo'ng'iroq qilib, tayyor eshikni o'zingizga yetkazib berishlarini so'rashingiz mumkin — shunda eshik yasash haqida hech narsa o'rganishingiz ham, uni yasash bilan bog'liq tartibsizlik bilan ovora bo'lishingiz ham shart emas.
Oddiy so'z bilan aytganda
Simple factory client'ga instance yaratish logikasini hech qanday oshkor qilmasdan, uning uchun shunchaki instance hosil qilib beradi
Wikipedia'da shunday deyilgan
Object-oriented programming (OOP)'da factory — boshqa obyektlarni yaratish uchun mo'ljallangan obyekt; rasmiy ta'rifga ko'ra, factory — qandaydir metod chaqiruvidan turli prototype yoki sinfdagi obyektlarni qaytaradigan funksiya yoki metod bo'lib, bu chaqiruv "new" deb faraz qilinadi.
Dasturiy misol
Avvalo bizda eshik interfeysi va uning implementation'i bor
interface Door
{
public function getWidth(): float;
public function getHeight(): float;
}
class WoodenDoor implements Door
{
protected $width;
protected $height;
public function __construct(float $width, float $height)
{
$this->width = $width;
$this->height = $height;
}
public function getWidth(): float
{
return $this->width;
}
public function getHeight(): float
{
return $this->height;
}
}
So'ngra bizda eshikni yasab, uni qaytaradigan eshik factory'si bor
class DoorFactory
{
public static function makeDoor($width, $height): Door
{
return new WoodenDoor($width, $height);
}
}
Va keyin undan quyidagicha foydalanish mumkin
// Make me a door of 100x200
$door = DoorFactory::makeDoor(100, 200);
echo 'Width: ' . $door->getWidth();
echo 'Height: ' . $door->getHeight();
// Make me a door of 50x100
$door2 = DoorFactory::makeDoor(50, 100);
Qachon ishlatish kerak?
Obyekt yaratish shunchaki bir nechta qiymat berishdan iborat bo'lmay, qandaydir logikani ham o'z ichiga olsa, bir xil kodni hamma joyda takrorlash o'rniga uni alohida factory'ga joylashtirish mantiqan to'g'ri bo'ladi.
🏭 Factory Method
Hayotiy misol
Ishga qabul qilish bo'yicha menejerning holatini ko'rib chiqing. Bir kishining har bir lavozim uchun o'zi suhbat o'tkazishi imkonsiz. Bo'sh ish o'rniga qarab, u suhbat bosqichlarini qaysi odamlarga topshirishni hal qilishi kerak.
Oddiy so'z bilan aytganda
U instance yaratish logikasini child class'larga topshirish imkonini beradi.
Wikipedia'da shunday deyilgan
Class-based programming'da factory method pattern — yaratiladigan obyektning aniq sinfini ko'rsatmasdan obyekt yaratish muammosini hal qilish uchun factory method'lardan foydalanadigan creational pattern. Bunga constructor'ni chaqirish o'rniga factory method'ni chaqirib obyekt yaratish orqali erishiladi; bu metod yo interfeysda e'lon qilinib, child class'lar tomonidan implement qilinadi, yoki base class'da implement qilinib, zarur bo'lsa derived class'lar tomonidan override qilinadi.
Dasturiy misol
Yuqoridagi ishga qabul qilish menejeri misolimizni olaylik. Avvalo bizda suhbat o'tkazuvchi uchun interfeys va uning bir nechta implementation'i bor
interface Interviewer
{
public function askQuestions();
}
class Developer implements Interviewer
{
public function askQuestions()
{
echo 'Asking about design patterns!';
}
}
class CommunityExecutive implements Interviewer
{
public function askQuestions()
{
echo 'Asking about community building';
}
}
Endi HiringManager sinfimizni yaratamiz
abstract class HiringManager
{
// Factory method
abstract protected function makeInterviewer(): Interviewer;
public function takeInterview()
{
$interviewer = $this->makeInterviewer();
$interviewer->askQuestions();
}
}
Endi istalgan child class uni extend qilib, kerakli suhbat o'tkazuvchini taqdim etishi mumkin
class DevelopmentManager extends HiringManager
{
protected function makeInterviewer(): Interviewer
{
return new Developer();
}
}
class MarketingManager extends HiringManager
{
protected function makeInterviewer(): Interviewer
{
return new CommunityExecutive();
}
}
va keyin undan quyidagicha foydalanish mumkin
$devManager = new DevelopmentManager();
$devManager->takeInterview(); // Output: Asking about design patterns
$marketingManager = new MarketingManager();
$marketingManager->takeInterview(); // Output: Asking about community building.
Qachon ishlatish kerak?
Sinfda qandaydir umumiy qayta ishlash mavjud bo'lsa-yu, lekin kerakli subclass runtime'da dinamik ravishda aniqlansa foydali. Boshqacha aytganda, client o'ziga aynan qaysi subclass kerak bo'lishini bilmagan holatda.
🔨 Abstract Factory
Hayotiy misol
Simple Factory'dagi eshik misolimizni kengaytiramiz. Ehtiyojingizga qarab, yog'och eshikni yog'och eshiklar do'konidan, temir eshikni temir buyumlar do'konidan yoki PVC eshikni tegishli do'kondan olishingiz mumkin. Bundan tashqari, eshikni o'rnatish uchun turli mutaxassislikdagi usta kerak bo'lishi mumkin, masalan, yog'och eshik uchun duradgor, temir eshik uchun payvandchi va hokazo. Ko'rib turganingizdek, endi eshiklar o'rtasida dependency bor: yog'och eshikka duradgor, temir eshikka payvandchi kerak va hokazo.
Oddiy so'z bilan aytganda
Factory'lar factory'si; alohida, lekin o'zaro bog'liq/bir-biriga qaram factory'larni ularning concrete class'larini ko'rsatmasdan bir guruhga birlashtiradigan factory.
Wikipedia'da shunday deyilgan
Abstract factory pattern umumiy mavzuga ega bo'lgan alohida factory'lar guruhini ularning concrete class'larini ko'rsatmasdan encapsulate qilish usulini taqdim etadi
Dasturiy misol
Yuqoridagi eshik misolini kodga o'giramiz. Avvalo bizda Door interfeysi va uning bir nechta implementation'i bor
interface Door
{
public function getDescription();
}
class WoodenDoor implements Door
{
public function getDescription()
{
echo 'I am a wooden door';
}
}
class IronDoor implements Door
{
public function getDescription()
{
echo 'I am an iron door';
}
}
So'ngra bizda har bir eshik turi uchun o'rnatuvchi mutaxassislar bor
interface DoorFittingExpert
{
public function getDescription();
}
class Welder implements DoorFittingExpert
{
public function getDescription()
{
echo 'I can only fit iron doors';
}
}
class Carpenter implements DoorFittingExpert
{
public function getDescription()
{
echo 'I can only fit wooden doors';
}
}
Endi bizda o'zaro bog'liq obyektlar oilasini yaratishga imkon beradigan abstract factory bor, ya'ni yog'och eshik factory'si yog'och eshik va yog'och eshik o'rnatuvchi mutaxassisni, temir eshik factory'si esa temir eshik va temir eshik o'rnatuvchi mutaxassisni yaratadi
interface DoorFactory
{
public function makeDoor(): Door;
public function makeFittingExpert(): DoorFittingExpert;
}
// Wooden factory to return carpenter and wooden door
class WoodenDoorFactory implements DoorFactory
{
public function makeDoor(): Door
{
return new WoodenDoor();
}
public function makeFittingExpert(): DoorFittingExpert
{
return new Carpenter();
}
}
// Iron door factory to get iron door and the relevant fitting expert
class IronDoorFactory implements DoorFactory
{
public function makeDoor(): Door
{
return new IronDoor();
}
public function makeFittingExpert(): DoorFittingExpert
{
return new Welder();
}
}
Va keyin undan quyidagicha foydalanish mumkin
$woodenFactory = new WoodenDoorFactory();
$door = $woodenFactory->makeDoor();
$expert = $woodenFactory->makeFittingExpert();
$door->getDescription(); // Output: I am a wooden door
$expert->getDescription(); // Output: I can only fit wooden doors
// Same for Iron Factory
$ironFactory = new IronDoorFactory();
$door = $ironFactory->makeDoor();
$expert = $ironFactory->makeFittingExpert();
$door->getDescription(); // Output: I am an iron door
$expert->getDescription(); // Output: I can only fit iron doors
Ko'rib turganingizdek, yog'och eshik factory'si carpenter va wooden door'ni, temir eshik factory'si esa iron door va welder'ni encapsulate qilgan. Shu tariqa u yaratilgan har bir eshik uchun noto'g'ri o'rnatuvchi mutaxassisni olib qo'ymasligimizni ta'minlashga yordam berdi.
Qachon ishlatish kerak?
Yaratish logikasi unchalik oddiy bo'lmagan, o'zaro bog'liq dependency'lar mavjud bo'lganda
👷 Builder
Hayotiy misol
Tasavvur qiling, siz Hardee's'dasiz va muayyan taomni buyurtma qilasiz, aytaylik, "Big Hardee"ni, va ular uni sizga hech qanday savolsiz topshirishadi; bu simple factory'ga misol. Ammo yaratish logikasi ko'proq bosqichlarni o'z ichiga olishi mumkin bo'lgan holatlar ham bor. Masalan, siz Subway'da o'zingizga moslashtirilgan taom xohlaysiz: burgeringiz qanday tayyorlanishi bo'yicha bir nechta tanlovingiz bor, masalan, qanday non xohlaysiz? Qanday souslarni xohlaysiz? Qaysi pishloqni xohlaysiz? va hokazo. Bunday hollarda builder pattern yordamga keladi.
Oddiy so'z bilan aytganda
Constructor pollution'dan qochgan holda obyektning turli variantlarini yaratishga imkon beradi. Obyektning bir nechta varianti bo'lishi mumkin bo'lganda foydali. Yoki obyektni yaratishda ko'plab bosqichlar ishtirok etganda.
Wikipedia'da shunday deyilgan
Builder pattern — telescoping constructor anti-pattern'iga yechim topish maqsadida yaratilgan, obyekt yaratishga oid software design pattern.
Shunday ekan, telescoping constructor anti-pattern nima ekanligi haqida biroz qo'shimcha qilay. Qachondir har birimiz quyidagiga o'xshash constructor'ni ko'rganmiz:
public function __construct($size, $cheese = true, $pepperoni = true, $tomato = false, $lettuce = true)
{
}
Ko'rib turganingizdek, constructor parametrlari soni tezda nazoratdan chiqib ketishi va parametrlar tartibini tushunish qiyinlashishi mumkin. Bundan tashqari, kelajakda yana ko'proq variantlar qo'shmoqchi bo'lsangiz, bu parametrlar ro'yxati o'sishda davom etishi mumkin. Bu telescoping constructor anti-pattern deb ataladi.
Dasturiy misol
Oqilona muqobil — builder pattern'dan foydalanish. Avvalo bizda yasamoqchi bo'lgan burgerimiz bor
class Burger
{
protected $size;
protected $cheese = false;
protected $pepperoni = false;
protected $lettuce = false;
protected $tomato = false;
public function __construct(BurgerBuilder $builder)
{
$this->size = $builder->size;
$this->cheese = $builder->cheese;
$this->pepperoni = $builder->pepperoni;
$this->lettuce = $builder->lettuce;
$this->tomato = $builder->tomato;
}
}
So'ngra bizda builder bor
class BurgerBuilder
{
public $size;
public $cheese = false;
public $pepperoni = false;
public $lettuce = false;
public $tomato = false;
public function __construct(int $size)
{
$this->size = $size;
}
public function addPepperoni()
{
$this->pepperoni = true;
return $this;
}
public function addLettuce()
{
$this->lettuce = true;
return $this;
}
public function addCheese()
{
$this->cheese = true;
return $this;
}
public function addTomato()
{
$this->tomato = true;
return $this;
}
public function build(): Burger
{
return new Burger($this);
}
}
Va keyin undan quyidagicha foydalanish mumkin:
$burger = (new BurgerBuilder(14))
->addPepperoni()
->addLettuce()
->addTomato()
->build();
Qachon ishlatish kerak?
Obyektning bir nechta varianti bo'lishi mumkin bo'lganda va constructor telescoping'dan qochish kerak bo'lganda. Factory pattern'dan asosiy farqi shundaki: factory pattern yaratish bir bosqichli jarayon bo'lganda, builder pattern esa yaratish ko'p bosqichli jarayon bo'lganda ishlatiladi.
🐑 Prototype
Hayotiy misol
Dollini eslaysizmi? O'sha klonlangan qo'y! Tafsilotlarga kirmaylik, lekin bu yerdagi asosiy jihat shundaki, gap butunlay klonlash haqida
Oddiy so'z bilan aytganda
Mavjud obyekt asosida, uni klonlash orqali obyekt yaratish.
Wikipedia'da shunday deyilgan
Prototype pattern — dasturiy ta'minot ishlab chiqishdagi creational design pattern. U yaratiladigan obyektlar turi prototip instance orqali aniqlanganda ishlatiladi; yangi obyektlar hosil qilish uchun aynan shu instance klonlanadi.
Qisqasi, u obyektni noldan yaratib, uni sozlash bilan ovora bo'lish o'rniga mavjud obyektning nusxasini yaratib, uni o'z ehtiyojlaringizga moslab o'zgartirishga imkon beradi.
Dasturiy misol
PHP'da buni clone yordamida osongina qilish mumkin
class Sheep
{
protected $name;
protected $category;
public function __construct(string $name, string $category = 'Mountain Sheep')
{
$this->name = $name;
$this->category = $category;
}
public function setName(string $name)
{
$this->name = $name;
}
public function getName()
{
return $this->name;
}
public function setCategory(string $category)
{
$this->category = $category;
}
public function getCategory()
{
return $this->category;
}
}
So'ngra uni quyidagicha klonlash mumkin
$original = new Sheep('Jolly');
echo $original->getName(); // Jolly
echo $original->getCategory(); // Mountain Sheep
// Clone and modify what is required
$cloned = clone $original;
$cloned->setName('Dolly');
echo $cloned->getName(); // Dolly
echo $cloned->getCategory(); // Mountain sheep
Shuningdek, klonlash xatti-harakatini o'zgartirish uchun __clone magic method'idan ham foydalanishingiz mumkin.
Qachon ishlatish kerak?
Mavjud obyektga o'xshash obyekt kerak bo'lganda yoki obyektni yaratish klonlashga nisbatan qimmatga tushganda.
💍 Singleton
Hayotiy misol
Bir vaqtning o'zida mamlakatda faqat bitta prezident bo'lishi mumkin. Burch chaqirgan har safar aynan o'sha prezident harakatga kelishi kerak. Bu yerda prezident — singleton.
Oddiy so'z bilan aytganda
Muayyan sinfdan faqat bitta obyekt yaratilishini kafolatlaydi.
Wikipedia'da shunday deyilgan
Software engineering'da singleton pattern — sinfdan instance yaratishni bitta obyekt bilan cheklaydigan software design pattern. Bu tizim bo'ylab harakatlarni muvofiqlashtirish uchun aynan bitta obyekt kerak bo'lganda foydali.
Singleton pattern aslida anti-pattern hisoblanadi va undan haddan tashqari ko'p foydalanishdan qochish kerak. U albatta yomon degani emas va ba'zi asosli use-case'larga ega bo'lishi mumkin, lekin uni ehtiyotkorlik bilan ishlatish kerak, chunki u ilovangizga global state kiritadi va uni bir joyda o'zgartirish boshqa qismlarga ta'sir qilishi mumkin, bu esa debug qilishni ancha qiyinlashtirib yuborishi mumkin. Ularning yana bir yomon tomoni — ular kodingizni tightly coupled qiladi, bundan tashqari singleton'ni mock qilish qiyin bo'lishi mumkin.
Dasturiy misol
Singleton yaratish uchun constructor'ni private qiling, klonlashni o'chiring, sinfni kengaytirishni taqiqlang va instance'ni saqlash uchun static o'zgaruvchi yarating
final class President
{
private static $instance;
private function __construct()
{
// Hide the constructor
}
public static function getInstance(): President
{
if (!self::$instance) {
self::$instance = new self();
}
return self::$instance;
}
private function __clone()
{
// Disable cloning
}
private function __wakeup()
{
// Disable unserialize
}
}
So'ngra undan foydalanish uchun
$president1 = President::getInstance();
$president2 = President::getInstance();
var_dump($president1 === $president2); // true
Structural Design Patterns
Oddiy so'z bilan aytganda
Structural pattern'lar asosan obyektlar kompozitsiyasi bilan, boshqacha aytganda, entity'lar bir-biridan qanday foydalanishi bilan shug'ullanadi. Yoki yana bir izoh: ular "Dasturiy component qanday quriladi?" degan savolga javob berishga yordam beradi.
Wikipedia'da shunday deyilgan
Software engineering'da structural design pattern'lar — entity'lar o'rtasidagi munosabatlarni amalga oshirishning oddiy usulini aniqlash orqali dizaynni osonlashtiradigan design pattern'lar.
Structural pattern'larning 7 turi bor
🔌 Adapter
Hayotiy misol
Tasavvur qiling, xotira kartangizda bir nechta rasm bor va ularni kompyuteringizga o'tkazishingiz kerak. Ularni o'tkazish uchun sizga kompyuteringiz port'lariga mos keladigan, xotira kartasini kompyuterga ulash imkonini beradigan qandaydir adapter kerak. Bu holatda karta o'quvchi qurilma — adapter. Yana bir misol — mashhur quvvat adapteri; uch tishli vilkani ikki tishli rozetkaga ulab bo'lmaydi, buning uchun uni ikki tishli rozetkaga mos qiladigan quvvat adapteridan foydalanish kerak. Yana bir misol — bir kishi aytgan so'zlarni boshqasiga tarjima qilib beradigan tarjimon
Oddiy so'z bilan aytganda
Adapter pattern aks holda mos kelmaydigan obyektni boshqa sinf bilan mos ishlashi uchun adapter'ga o'rash imkonini beradi.
Wikipedia'da shunday deyilgan
Software engineering'da adapter pattern — mavjud sinf interfeysidan boshqa interfeys sifatida foydalanish imkonini beradigan software design pattern. U ko'pincha mavjud sinflarni ularning manba kodini o'zgartirmasdan boshqa sinflar bilan ishlashga moslash uchun ishlatiladi.
Dasturiy misol
Ovchi sherlarni ovlaydigan o'yinni tasavvur qiling.
Avvalo bizda barcha turdagi sherlar implement qilishi kerak bo'lgan Lion interfeysi bor
interface Lion
{
public function roar();
}
class AfricanLion implements Lion
{
public function roar()
{
}
}
class AsianLion implements Lion
{
public function roar()
{
}
}
Ovchi esa ovlash uchun Lion interfeysining istalgan implementation'ini kutadi.
class Hunter
{
public function hunt(Lion $lion)
{
$lion->roar();
}
}
Endi aytaylik, o'yinimizga WildDog qo'shishimiz kerak, toki ovchi uni ham ovlay olsin. Lekin buni to'g'ridan-to'g'ri qila olmaymiz, chunki itning interfeysi boshqacha. Uni ovchimizga mos qilish uchun mos keladigan adapter yaratishimiz kerak bo'ladi
// This needs to be added to the game
class WildDog
{
public function bark()
{
}
}
// Adapter around wild dog to make it compatible with our game
class WildDogAdapter implements Lion
{
protected $dog;
public function __construct(WildDog $dog)
{
$this->dog = $dog;
}
public function roar()
{
$this->dog->bark();
}
}
Endi esa WildDog'dan o'yinimizda WildDogAdapter yordamida foydalanish mumkin.
$wildDog = new WildDog();
$wildDogAdapter = new WildDogAdapter($wildDog);
$hunter = new Hunter();
$hunter->hunt($wildDogAdapter);
🚡 Bridge
Hayotiy misol
Tasavvur qiling, sizda turli sahifalardan iborat veb-sayt bor va siz foydalanuvchiga theme'ni o'zgartirish imkonini berishingiz kerak. Nima qilgan bo'lardingiz? Har bir theme uchun har bir sahifaning bir nechta nusxasini yaratasizmi yoki shunchaki alohida theme yaratib, uni foydalanuvchi tanloviga qarab yuklaysizmi? Bridge pattern sizga ikkinchi yo'lni tanlash imkonini beradi, ya'ni

Oddiy so'z bilan aytganda
Bridge pattern — inheritance'dan ko'ra composition'ni afzal ko'rish haqida. Implementation tafsilotlari bir ierarxiyadan alohida ierarxiyaga ega bo'lgan boshqa obyektga ko'chiriladi.
Wikipedia'da shunday deyilgan
Bridge pattern — software engineering'da qo'llaniladigan design pattern bo'lib, uning maqsadi "abstraction'ni uning implementation'idan ajratish, shunda ikkalasi bir-biridan mustaqil ravishda o'zgara oladi"
Dasturiy misol
Yuqoridagi WebPage misolimizni kodga o'giramiz. Bu yerda bizda WebPage ierarxiyasi bor
interface WebPage
{
public function __construct(Theme $theme);
public function getContent();
}
class About implements WebPage
{
protected $theme;
public function __construct(Theme $theme)
{
$this->theme = $theme;
}
public function getContent()
{
return "About page in " . $this->theme->getColor();
}
}
class Careers implements WebPage
{
protected $theme;
public function __construct(Theme $theme)
{
$this->theme = $theme;
}
public function getContent()
{
return "Careers page in " . $this->theme->getColor();
}
}
Va alohida theme ierarxiyasi
interface Theme
{
public function getColor();
}
class DarkTheme implements Theme
{
public function getColor()
{
return 'Dark Black';
}
}
class LightTheme implements Theme
{
public function getColor()
{
return 'Off white';
}
}
class AquaTheme implements Theme
{
public function getColor()
{
return 'Light blue';
}
}
Va ikkala ierarxiya birgalikda
$darkTheme = new DarkTheme();
$about = new About($darkTheme);
$careers = new Careers($darkTheme);
echo $about->getContent(); // "About page in Dark Black";
echo $careers->getContent(); // "Careers page in Dark Black";
🌿 Composite
Hayotiy misol
Har bir tashkilot xodimlardan tashkil topgan. Har bir xodim bir xil xususiyatlarga ega, ya'ni maoshi bor, ma'lum mas'uliyatlari bor, kimgadir hisobot berishi yoki bermasligi mumkin, qo'l ostida xodimlari bo'lishi yoki bo'lmasligi mumkin va hokazo.
Oddiy so'z bilan aytganda
Composite pattern client'larga alohida obyektlarga bir xil tarzda munosabatda bo'lish imkonini beradi.
Wikipedia'da shunday deyilgan
Software engineering'da composite pattern — partitioning design pattern. Composite pattern obyektlar guruhiga obyektning bitta instance'iga qanday munosabatda bo'linsa, xuddi shunday munosabatda bo'lish kerakligini tavsiflaydi. Composite'ning maqsadi qism-butun ierarxiyalarini ifodalash uchun obyektlarni daraxt tuzilmalariga "birlashtirish"dir. Composite pattern'ni implement qilish client'larga alohida obyektlar va kompozitsiyalarga bir xil tarzda munosabatda bo'lish imkonini beradi.
Dasturiy misol
Yuqoridagi xodimlar misolimizni olaylik. Bu yerda bizda turli xodim turlari bor
interface Employee
{
public function __construct(string $name, float $salary);
public function getName(): string;
public function setSalary(float $salary);
public function getSalary(): float;
public function getRoles(): array;
}
class Developer implements Employee
{
protected $salary;
protected $name;
protected $roles;
public function __construct(string $name, float $salary)
{
$this->name = $name;
$this->salary = $salary;
}
public function getName(): string
{
return $this->name;
}
public function setSalary(float $salary)
{
$this->salary = $salary;
}
public function getSalary(): float
{
return $this->salary;
}
public function getRoles(): array
{
return $this->roles;
}
}
class Designer implements Employee
{
protected $salary;
protected $name;
protected $roles;
public function __construct(string $name, float $salary)
{
$this->name = $name;
$this->salary = $salary;
}
public function getName(): string
{
return $this->name;
}
public function setSalary(float $salary)
{
$this->salary = $salary;
}
public function getSalary(): float
{
return $this->salary;
}
public function getRoles(): array
{
return $this->roles;
}
}
So'ngra bizda bir nechta turli turdagi xodimlardan iborat tashkilot bor
class Organization
{
protected $employees;
public function addEmployee(Employee $employee)
{
$this->employees[] = $employee;
}
public function getNetSalaries(): float
{
$netSalary = 0;
foreach ($this->employees as $employee) {
$netSalary += $employee->getSalary();
}
return $netSalary;
}
}
Va keyin undan quyidagicha foydalanish mumkin
// Prepare the employees
$john = new Developer('John Doe', 12000);
$jane = new Designer('Jane Doe', 15000);
// Add them to organization
$organization = new Organization();
$organization->addEmployee($john);
$organization->addEmployee($jane);
echo "Net salaries: " . $organization->getNetSalaries(); // Net Salaries: 27000
☕ Decorator
Hayotiy misol
Tasavvur qiling, siz bir nechta xizmat ko'rsatadigan avtoservis ustaxonasini boshqarasiz. Xo'sh, mijozdan olinadigan hisobni qanday hisoblaysiz? Bitta xizmatni tanlaysiz va yakuniy narxga erishguningizcha unga ko'rsatilgan xizmatlar narxlarini dinamik ravishda qo'shib borasiz. Bu yerda har bir xizmat turi — decorator.
Oddiy so'z bilan aytganda
Decorator pattern obyektni decorator sinfining obyektiga o'rash orqali runtime'da uning xatti-harakatini dinamik ravishda o'zgartirishga imkon beradi.
Wikipedia'da shunday deyilgan
Object-oriented programming'da decorator pattern — xatti-harakatni alohida obyektga, xuddi shu sinfdagi boshqa obyektlarning xatti-harakatiga ta'sir qilmagan holda, statik yoki dinamik ravishda qo'shish imkonini beradigan design pattern. Decorator pattern ko'pincha Single Responsibility Principle'ga amal qilishda foydali, chunki u funksionallikni har biri o'ziga xos mas'uliyat sohasiga ega bo'lgan sinflar o'rtasida taqsimlash imkonini beradi.
Dasturiy misol
Misol uchun qahvani olaylik. Avvalo bizda qahva interfeysini implement qiladigan oddiy qahva bor
interface Coffee
{
public function getCost();
public function getDescription();
}
class SimpleCoffee implements Coffee
{
public function getCost()
{
return 10;
}
public function getDescription()
{
return 'Simple coffee';
}
}
Biz kodni kengaytiriladigan qilmoqchimiz, toki kerak bo'lganda uni o'zgartirish uchun variantlar qo'shish mumkin bo'lsin. Keling, bir nechta qo'shimchalar (decorator'lar) yaratamiz
class MilkCoffee implements Coffee
{
protected $coffee;
public function __construct(Coffee $coffee)
{
$this->coffee = $coffee;
}
public function getCost()
{
return $this->coffee->getCost() + 2;
}
public function getDescription()
{
return $this->coffee->getDescription() . ', milk';
}
}
class WhipCoffee implements Coffee
{
protected $coffee;
public function __construct(Coffee $coffee)
{
$this->coffee = $coffee;
}
public function getCost()
{
return $this->coffee->getCost() + 5;
}
public function getDescription()
{
return $this->coffee->getDescription() . ', whip';
}
}
class VanillaCoffee implements Coffee
{
protected $coffee;
public function __construct(Coffee $coffee)
{
$this->coffee = $coffee;
}
public function getCost()
{
return $this->coffee->getCost() + 3;
}
public function getDescription()
{
return $this->coffee->getDescription() . ', vanilla';
}
}
Endi qahva tayyorlaymiz
$someCoffee = new SimpleCoffee();
echo $someCoffee->getCost(); // 10
echo $someCoffee->getDescription(); // Simple Coffee
$someCoffee = new MilkCoffee($someCoffee);
echo $someCoffee->getCost(); // 12
echo $someCoffee->getDescription(); // Simple Coffee, milk
$someCoffee = new WhipCoffee($someCoffee);
echo $someCoffee->getCost(); // 17
echo $someCoffee->getDescription(); // Simple Coffee, milk, whip
$someCoffee = new VanillaCoffee($someCoffee);
echo $someCoffee->getCost(); // 20
echo $someCoffee->getDescription(); // Simple Coffee, milk, whip, vanilla
📦 Facade
Hayotiy misol
Kompyuterni qanday yoqasiz? "Quvvat tugmasini bosaman" deysiz! Siz shunday deb o'ylaysiz, chunki kompyuter tashqi tomondan taqdim etadigan oddiy interfeysdan foydalanasiz, ichkarida esa buni amalga oshirish uchun u juda ko'p ishlarni bajarishi kerak. Murakkab subsystem'ga bo'lgan ana shu oddiy interfeys — facade.
Oddiy so'z bilan aytganda
Facade pattern murakkab subsystem'ga soddalashtirilgan interfeys taqdim etadi.
Wikipedia'da shunday deyilgan
Facade — kattaroq kod qismiga, masalan, sinflar kutubxonasiga soddalashtirilgan interfeys taqdim etadigan obyekt.
Dasturiy misol
Yuqoridagi kompyuter misolimizni olaylik. Bu yerda bizda kompyuter sinfi bor
class Computer
{
public function getElectricShock()
{
echo "Ouch!";
}
public function makeSound()
{
echo "Beep beep!";
}
public function showLoadingScreen()
{
echo "Loading..";
}
public function bam()
{
echo "Ready to be used!";
}
public function closeEverything()
{
echo "Bup bup bup buzzzz!";
}
public function sooth()
{
echo "Zzzzz";
}
public function pullCurrent()
{
echo "Haaah!";
}
}
Bu yerda esa bizda facade bor
class ComputerFacade
{
protected $computer;
public function __construct(Computer $computer)
{
$this->computer = $computer;
}
public function turnOn()
{
$this->computer->getElectricShock();
$this->computer->makeSound();
$this->computer->showLoadingScreen();
$this->computer->bam();
}
public function turnOff()
{
$this->computer->closeEverything();
$this->computer->pullCurrent();
$this->computer->sooth();
}
}
Endi facade'dan foydalanish uchun
$computer = new ComputerFacade(new Computer());
$computer->turnOn(); // Ouch! Beep beep! Loading.. Ready to be used!
$computer->turnOff(); // Bup bup buzzz! Haah! Zzzzz
🍃 Flyweight
Hayotiy misol
Biror rastadan yangi damlangan choy ichganmisiz? Ular ko'pincha siz so'raganingizdan ko'proq piyola tayyorlashadi va qolganini boshqa mijozlar uchun saqlab qo'yishadi, shu bilan resurslarni, masalan, gaz va hokazolarni tejashadi. Flyweight pattern aynan shu haqida, ya'ni birgalikda foydalanish haqida.
Oddiy so'z bilan aytganda
U o'xshash obyektlar bilan iloji boricha ko'proq narsani birgalikda ishlatish orqali xotira sarfini yoki hisoblash xarajatlarini kamaytirish uchun qo'llaniladi.
Wikipedia'da shunday deyilgan
Kompyuter dasturlashda flyweight — software design pattern. Flyweight — boshqa o'xshash obyektlar bilan iloji boricha ko'proq ma'lumotni birgalikda ishlatish orqali xotira sarfini kamaytiradigan obyekt; bu oddiy takroriy ifodalash qabul qilib bo'lmaydigan miqdorda xotira sarflaydigan holatlarda obyektlardan juda ko'p sonda foydalanish usulidir.
Dasturiy misol
Yuqoridagi choy misolimizni kodga o'giramiz. Avvalo bizda choy turlari va choy tayyorlovchi bor
// Anything that will be cached is flyweight.
// Types of tea here will be flyweights.
class KarakTea
{
}
// Acts as a factory and saves the tea
class TeaMaker
{
protected $availableTea = [];
public function make($preference)
{
if (empty($this->availableTea[$preference])) {
$this->availableTea[$preference] = new KarakTea();
}
return $this->availableTea[$preference];
}
}
So'ngra bizda buyurtmalarni qabul qilib, ularni mijozlarga tortadigan TeaShop bor
class TeaShop
{
protected $orders;
protected $teaMaker;
public function __construct(TeaMaker $teaMaker)
{
$this->teaMaker = $teaMaker;
}
public function takeOrder(string $teaType, int $table)
{
$this->orders[$table] = $this->teaMaker->make($teaType);
}
public function serve()
{
foreach ($this->orders as $table => $tea) {
echo "Serving tea to table# " . $table;
}
}
}
Va undan quyidagicha foydalanish mumkin
$teaMaker = new TeaMaker();
$shop = new TeaShop($teaMaker);
$shop->takeOrder('less sugar', 1);
$shop->takeOrder('more milk', 2);
$shop->takeOrder('without sugar', 5);
$shop->serve();
// Serving tea to table# 1
// Serving tea to table# 2
// Serving tea to table# 5
🎱 Proxy
Hayotiy misol
Hech qachon eshikdan o'tish uchun kirish kartasidan foydalanganmisiz? Bunday eshikni ochishning bir nechta usuli bor, ya'ni uni kirish kartasi yordamida yoki xavfsizlik tizimini chetlab o'tadigan tugmani bosib ochish mumkin. Eshikning asosiy funksiyasi — ochilish, lekin unga qandaydir funksionallik qo'shish uchun uning ustiga proxy qo'shilgan. Keling, buni quyidagi kod misoli yordamida yaxshiroq tushuntiray.
Oddiy so'z bilan aytganda
Proxy pattern yordamida bir sinf boshqa sinfning funksionalligini ifodalaydi.
Wikipedia'da shunday deyilgan
Proxy, eng umumiy shaklida, boshqa biror narsaga interfeys vazifasini bajaradigan sinf. Proxy — client parda ortidagi haqiqiy xizmat ko'rsatuvchi obyektga murojaat qilish uchun chaqiradigan wrapper yoki agent obyekt. Proxy'dan foydalanish shunchaki haqiqiy obyektga uzatishdan iborat bo'lishi yoki qo'shimcha logika taqdim etishi mumkin. Proxy'da qo'shimcha funksionallik taqdim etilishi mumkin, masalan, haqiqiy obyektdagi operatsiyalar ko'p resurs talab qilganda keshlash yoki haqiqiy obyektdagi operatsiyalar chaqirilishidan oldin dastlabki shartlarni tekshirish.
Dasturiy misol
Yuqoridagi xavfsiz eshik misolimizni olaylik. Avvalo bizda eshik interfeysi va eshikning bitta implementation'i bor
interface Door
{
public function open();
public function close();
}
class LabDoor implements Door
{
public function open()
{
echo "Opening lab door";
}
public function close()
{
echo "Closing the lab door";
}
}
So'ngra bizda istalgan eshikni himoyalash uchun proxy bor
class SecuredDoor
{
protected $door;
public function __construct(Door $door)
{
$this->door = $door;
}
public function open($password)
{
if ($this->authenticate($password)) {
$this->door->open();
} else {
echo "Big no! It ain't possible.";
}
}
public function authenticate($password)
{
return $password === '$ecr@t';
}
public function close()
{
$this->door->close();
}
}
Undan qanday foydalanish mumkinligi esa quyida
$door = new SecuredDoor(new LabDoor());
$door->open('invalid'); // Big no! It ain't possible.
$door->open('$ecr@t'); // Opening lab door
$door->close(); // Closing lab door
Yana bir misol — qandaydir data-mapper implementation'i. Masalan, men yaqinda shu pattern yordamida MongoDB uchun ODM (Object Data Mapper) yaratdim: unda __call() magic method'idan foydalanib, mongo sinflari atrofida proxy yozdim. Barcha metod chaqiruvlari asl mongo sinfiga proxy qilinar va olingan natija o'zgarishsiz qaytarilar edi, lekin find yoki findOne holatida ma'lumotlar kerakli sinf obyektlariga map qilinib, Cursor o'rniga obyekt qaytarilardi.
Behavioral Design Patterns
Oddiy so'z bilan aytganda
U obyektlar o'rtasida mas'uliyatlarni taqsimlash bilan shug'ullanadi. Ularni structural pattern'lardan ajratib turadigan jihat shundaki, ular faqat tuzilmani belgilab qolmay, balki ular o'rtasidagi xabar almashish/aloqa pattern'larini ham belgilab beradi. Boshqacha aytganda, ular "Dasturiy component'da xatti-harakat qanday ishga tushiriladi?" degan savolga javob berishga yordam beradi.
Wikipedia'da shunday deyilgan
Software engineering'da behavioral design pattern'lar — obyektlar o'rtasidagi umumiy aloqa pattern'larini aniqlaydigan va bu pattern'larni amalga oshiradigan design pattern'lar. Shu orqali bu pattern'lar ushbu aloqani amalga oshirishdagi moslashuvchanlikni oshiradi.
Behavioral design pattern'larning 10 turi bor
- Chain of Responsibility
- Command
- Iterator
- Mediator
- Memento
- Observer
- Visitor
- Strategy
- State
- Template Method
🔗 Chain of Responsibility
Hayotiy misol
Masalan, hisobingizda uchta to'lov usuli (
A,BvaC) sozlangan va ularning har birida turli miqdorda pul bor.A'da 100 USD,B'da 300 USD,C'da esa 1000 USD bor, to'lovlar uchun ustuvorlik esa avvalA, keyinB, keyinCtarzida tanlangan. Siz 210 USD turadigan narsani sotib olmoqchisiz. Chain of Responsibility'dan foydalanilganda, avvaloAhisobi xaridni amalga oshira oladimi-yo'qmi tekshiriladi; agar ha bo'lsa, xarid amalga oshiriladi va zanjir uziladi. Agar yo'q bo'lsa, so'rovBhisobiga o'tib, undagi mablag' tekshiriladi; agar yetarli bo'lsa, zanjir uziladi, aks holda so'rov mos handler'ni topguncha oldinga uzatilishda davom etadi. Bu yerdaA,BvaC— zanjirning halqalari, butun bu hodisa esa Chain of Responsibility.
Oddiy so'z bilan aytganda
U obyektlar zanjirini qurishga yordam beradi. So'rov bir uchidan kiradi va mos handler'ni topguncha obyektdan obyektga o'tib boradi.
Wikipedia'da shunday deyilgan
Object-oriented design'da chain-of-responsibility pattern — command obyektlari manbai va bir qator qayta ishlovchi obyektlardan iborat design pattern. Har bir qayta ishlovchi obyekt o'zi qayta ishlay oladigan command obyektlari turlarini belgilaydigan logikani o'z ichiga oladi; qolganlari esa zanjirdagi keyingi qayta ishlovchi obyektga uzatiladi.
Dasturiy misol
Yuqoridagi hisob misolimizni kodga o'giramiz. Avvalo bizda hisoblarni bir-biriga zanjir qilib ulash logikasiga ega bo'lgan bazaviy hisob va bir nechta hisoblar bor
abstract class Account
{
protected $successor;
protected $balance;
public function setNext(Account $account)
{
$this->successor = $account;
}
public function pay(float $amountToPay)
{
if ($this->canPay($amountToPay)) {
echo sprintf('Paid %s using %s' . PHP_EOL, $amountToPay, get_called_class());
} elseif ($this->successor) {
echo sprintf('Cannot pay using %s. Proceeding ..' . PHP_EOL, get_called_class());
$this->successor->pay($amountToPay);
} else {
throw new Exception('None of the accounts have enough balance');
}
}
public function canPay($amount): bool
{
return $this->balance >= $amount;
}
}
class Bank extends Account
{
protected $balance;
public function __construct(float $balance)
{
$this->balance = $balance;
}
}
class Paypal extends Account
{
protected $balance;
public function __construct(float $balance)
{
$this->balance = $balance;
}
}
class Bitcoin extends Account
{
protected $balance;
public function __construct(float $balance)
{
$this->balance = $balance;
}
}
Endi yuqorida belgilangan halqalar (ya'ni Bank, Paypal, Bitcoin) yordamida zanjirni tayyorlaymiz
// Let's prepare a chain like below
// $bank->$paypal->$bitcoin
//
// First priority bank
// If bank can't pay then paypal
// If paypal can't pay then bit coin
$bank = new Bank(100); // Bank with balance 100
$paypal = new Paypal(200); // Paypal with balance 200
$bitcoin = new Bitcoin(300); // Bitcoin with balance 300
$bank->setNext($paypal);
$paypal->setNext($bitcoin);
// Let's try to pay using the first priority i.e. bank
$bank->pay(259);
// Output will be
// ==============
// Cannot pay using bank. Proceeding ..
// Cannot pay using paypal. Proceeding ..:
// Paid 259 using Bitcoin!
👮 Command
Hayotiy misol
Umumiy misol — restoranda ovqat buyurtma qilishingiz. Siz (ya'ni
Client) ofitsiantdan (ya'niInvoker) ovqat (ya'niCommand) olib kelishni so'raysiz, ofitsiant esa so'rovni shunchaki nimani va qanday pishirishni biladigan oshpazga (ya'niReceiver) uzatadi. Yana bir misol — siz (ya'niClient) pult (Invoker) yordamida televizorni (ya'niReceiver) yoqishingiz (ya'niCommand).
Oddiy so'z bilan aytganda
Harakatlarni obyektlarga encapsulate qilish imkonini beradi. Bu pattern ortidagi asosiy g'oya — client'ni receiver'dan decouple qilish vositasini taqdim etish.
Wikipedia'da shunday deyilgan
Object-oriented programming'da command pattern — biror harakatni bajarish yoki keyinroq event'ni ishga tushirish uchun zarur bo'lgan barcha ma'lumotni encapsulate qilishda obyektdan foydalaniladigan behavioral design pattern. Bu ma'lumot metod nomi, metodga egalik qiluvchi obyekt va metod parametrlari qiymatlarini o'z ichiga oladi.
Dasturiy misol
Avvalo bizda bajarilishi mumkin bo'lgan har bir harakatning implementation'iga ega receiver bor
// Receiver
class Bulb
{
public function turnOn()
{
echo "Bulb has been lit";
}
public function turnOff()
{
echo "Darkness!";
}
}
so'ngra bizda har bir command implement qiladigan interfeys va command'lar to'plami bor
interface Command
{
public function execute();
public function undo();
public function redo();
}
// Command
class TurnOn implements Command
{
protected $bulb;
public function __construct(Bulb $bulb)
{
$this->bulb = $bulb;
}
public function execute()
{
$this->bulb->turnOn();
}
public function undo()
{
$this->bulb->turnOff();
}
public function redo()
{
$this->execute();
}
}
class TurnOff implements Command
{
protected $bulb;
public function __construct(Bulb $bulb)
{
$this->bulb = $bulb;
}
public function execute()
{
$this->bulb->turnOff();
}
public function undo()
{
$this->bulb->turnOn();
}
public function redo()
{
$this->execute();
}
}
So'ngra bizda Invoker bor; client har qanday command'ni qayta ishlash uchun u bilan o'zaro ishlaydi
// Invoker
class RemoteControl
{
public function submit(Command $command)
{
$command->execute();
}
}
Nihoyat, undan client'imizda qanday foydalanishimiz mumkinligini ko'raylik
$bulb = new Bulb();
$turnOn = new TurnOn($bulb);
$turnOff = new TurnOff($bulb);
$remote = new RemoteControl();
$remote->submit($turnOn); // Bulb has been lit!
$remote->submit($turnOff); // Darkness!
Command pattern'dan transaction'ga asoslangan tizimni implement qilish uchun ham foydalanish mumkin. Bunda command'larni bajarishingiz bilanoq ularning tarixini yuritib borasiz. Agar oxirgi command muvaffaqiyatli bajarilsa, hammasi joyida, aks holda shunchaki tarix bo'ylab iteratsiya qilib, bajarilgan barcha command'lar uchun undo'ni bajarib chiqasiz.
➿ Iterator
Hayotiy misol
Eski radiopriyomnik iterator'ga yaxshi misol bo'la oladi: foydalanuvchi biror kanaldan boshlab, so'ngra tegishli kanallar bo'ylab o'tish uchun keyingi yoki oldingi tugmalaridan foydalanishi mumkin edi. Yoki MP3 pleyer yoki televizorni olaylik: ketma-ket kanallar bo'ylab o'tish uchun keyingi va oldingi tugmalarni bosishingiz mumkin; boshqacha aytganda, ularning barchasi tegishli kanallar, qo'shiqlar yoki radiostansiyalar bo'ylab iteratsiya qilish uchun interfeys taqdim etadi.
Oddiy so'z bilan aytganda
U obyekt elementlariga uning ichki ko'rinishini oshkor qilmasdan murojaat qilish usulini taqdim etadi.
Wikipedia'da shunday deyilgan
Object-oriented programming'da iterator pattern — container bo'ylab o'tish va container elementlariga murojaat qilish uchun iterator'dan foydalaniladigan design pattern. Iterator pattern algoritmlarni container'lardan decouple qiladi; ba'zi hollarda algoritmlar majburan container'ga xos bo'ladi va shu sababli ularni decouple qilib bo'lmaydi.
Dasturiy misol
PHP'da buni SPL (Standard PHP Library) yordamida implement qilish ancha oson. Yuqoridagi radiostansiyalar misolimizni kodga o'giramiz. Avvalo bizda RadioStation bor
class RadioStation
{
protected $frequency;
public function __construct(float $frequency)
{
$this->frequency = $frequency;
}
public function getFrequency(): float
{
return $this->frequency;
}
}
So'ngra bizda iterator'imiz bor
use Countable;
use Iterator;
class StationList implements Countable, Iterator
{
/** @var RadioStation[] $stations */
protected $stations = [];
/** @var int $counter */
protected $counter;
public function addStation(RadioStation $station)
{
$this->stations[] = $station;
}
public function removeStation(RadioStation $toRemove)
{
$toRemoveFrequency = $toRemove->getFrequency();
$this->stations = array_filter($this->stations, function (RadioStation $station) use ($toRemoveFrequency) {
return $station->getFrequency() !== $toRemoveFrequency;
});
}
public function count(): int
{
return count($this->stations);
}
public function current(): RadioStation
{
return $this->stations[$this->counter];
}
public function key()
{
return $this->counter;
}
public function next()
{
$this->counter++;
}
public function rewind()
{
$this->counter = 0;
}
public function valid(): bool
{
return isset($this->stations[$this->counter]);
}
}
Va keyin undan quyidagicha foydalanish mumkin
$stationList = new StationList();
$stationList->addStation(new RadioStation(89));
$stationList->addStation(new RadioStation(101));
$stationList->addStation(new RadioStation(102));
$stationList->addStation(new RadioStation(103.2));
foreach($stationList as $station) {
echo $station->getFrequency() . PHP_EOL;
}
$stationList->removeStation(new RadioStation(89)); // Will remove station 89
👽 Mediator
Hayotiy misol
Umumiy misol — mobil telefoningizda kimdir bilan gaplashganingizda, siz bilan u o'rtasida tarmoq provayderi turadi va suhbatingiz to'g'ridan-to'g'ri yuborilish o'rniga u orqali o'tadi. Bu holatda tarmoq provayderi — mediator.
Oddiy so'z bilan aytganda
Mediator pattern ikki obyekt (colleague'lar deb ataladi) o'rtasidagi o'zaro ta'sirni boshqarish uchun uchinchi tomon obyektini (mediator deb ataladi) qo'shadi. U bir-biri bilan muloqot qiladigan sinflar o'rtasidagi coupling'ni kamaytirishga yordam beradi. Chunki endi ular bir-birining implementation'i haqida bilishlari shart emas.
Wikipedia'da shunday deyilgan
Software engineering'da mediator pattern obyektlar to'plami o'zaro qanday ta'sir qilishini encapsulate qiladigan obyektni belgilaydi. Bu pattern dasturning ishlash xatti-harakatini o'zgartira olishi sababli behavioral pattern hisoblanadi.
Dasturiy misol
Bu yerda foydalanuvchilar (ya'ni colleague'lar) bir-biriga xabar yuboradigan chat xonasining (ya'ni mediator) eng oddiy misoli keltirilgan.
Avvalo bizda mediator, ya'ni chat xonasi bor
interface ChatRoomMediator
{
public function showMessage(User $user, string $message);
}
// Mediator
class ChatRoom implements ChatRoomMediator
{
public function showMessage(User $user, string $message)
{
$time = date('M d, y H:i');
$sender = $user->getName();
echo $time . '[' . $sender . ']:' . $message;
}
}
So'ngra bizda foydalanuvchilarimiz, ya'ni colleague'lar bor
class User {
protected $name;
protected $chatMediator;
public function __construct(string $name, ChatRoomMediator $chatMediator) {
$this->name = $name;
$this->chatMediator = $chatMediator;
}
public function getName() {
return $this->name;
}
public function send($message) {
$this->chatMediator->showMessage($this, $message);
}
}
Va undan foydalanish
$mediator = new ChatRoom();
$john = new User('John Doe', $mediator);
$jane = new User('Jane Doe', $mediator);
$john->send('Hi there!');
$jane->send('Hey!');
// Output will be
// Feb 14, 10:58 [John]: Hi there!
// Feb 14, 10:58 [Jane]: Hey!
💾 Memento
Hayotiy misol
Kalkulyator (ya'ni originator) misolini olaylik: har safar biror hisob-kitobni bajarganingizda oxirgi hisob-kitob xotiraga (ya'ni memento) saqlanadi, shunda siz unga qaytishingiz va ehtimol ba'zi amal tugmalari (ya'ni caretaker) yordamida uni tiklashingiz mumkin.
Oddiy so'z bilan aytganda
Memento pattern obyektning joriy holatini keyinchalik muammosiz tiklash mumkin bo'ladigan tarzda olish va saqlash haqida.
Wikipedia'da shunday deyilgan
Memento pattern — obyektni oldingi holatiga qaytarish imkoniyatini beradigan software design pattern (rollback orqali undo).
Odatda qandaydir undo funksionalligini taqdim etish kerak bo'lganda foydali.
Dasturiy misol
Misol uchun vaqti-vaqti bilan holatni saqlab boradigan va siz xohlasangiz uni tiklashingiz mumkin bo'lgan matn muharririni olaylik.
Avvalo bizda muharrir holatini saqlay oladigan memento obyektimiz bor
class EditorMemento
{
protected $content;
public function __construct(string $content)
{
$this->content = $content;
}
public function getContent()
{
return $this->content;
}
}
So'ngra bizda memento obyektdan foydalanadigan muharririmiz, ya'ni originator bor
class Editor
{
protected $content = '';
public function type(string $words)
{
$this->content = $this->content . ' ' . $words;
}
public function getContent()
{
return $this->content;
}
public function save()
{
return new EditorMemento($this->content);
}
public function restore(EditorMemento $memento)
{
$this->content = $memento->getContent();
}
}
Va keyin undan quyidagicha foydalanish mumkin
$editor = new Editor();
// Type some stuff
$editor->type('This is the first sentence.');
$editor->type('This is second.');
// Save the state to restore to : This is the first sentence. This is second.
$saved = $editor->save();
// Type some more
$editor->type('And this is third.');
// Output: Content before Saving
echo $editor->getContent(); // This is the first sentence. This is second. And this is third.
// Restoring to last saved state
$editor->restore($saved);
$editor->getContent(); // This is the first sentence. This is second.
😎 Observer
Hayotiy misol
Yaxshi misol — ish qidiruvchilar: ular biror ish e'lonlari saytiga obuna bo'lishadi va mos ish imkoniyati paydo bo'lganda ularga xabar beriladi.
Oddiy so'z bilan aytganda
Obyektlar o'rtasida dependency belgilaydi, shunda biror obyekt o'z holatini o'zgartirganda, unga bog'liq bo'lgan barcha obyektlarga xabar beriladi.
Wikipedia'da shunday deyilgan
Observer pattern — subject deb ataladigan obyekt observer'lar deb ataladigan, o'ziga bog'liq obyektlar ro'yxatini yuritadigan va holatdagi har qanday o'zgarish haqida ularga, odatda ularning metodlaridan birini chaqirish orqali, avtomatik ravishda xabar beradigan software design pattern.
Dasturiy misol
Yuqoridagi misolimizni kodga o'giramiz. Avvalo bizda ish e'loni haqida xabardor qilinishi kerak bo'lgan ish qidiruvchilar bor
class JobPost
{
protected $title;
public function __construct(string $title)
{
$this->title = $title;
}
public function getTitle()
{
return $this->title;
}
}
class JobSeeker implements Observer
{
protected $name;
public function __construct(string $name)
{
$this->name = $name;
}
public function onJobPosted(JobPost $job)
{
// Do something with the job posting
echo 'Hi ' . $this->name . '! New job posted: '. $job->getTitle();
}
}
So'ngra bizda ish qidiruvchilar obuna bo'ladigan ish e'lonlarimiz bor
class EmploymentAgency implements Observable
{
protected $observers = [];
protected function notify(JobPost $jobPosting)
{
foreach ($this->observers as $observer) {
$observer->onJobPosted($jobPosting);
}
}
public function attach(Observer $observer)
{
$this->observers[] = $observer;
}
public function addJob(JobPost $jobPosting)
{
$this->notify($jobPosting);
}
}
So'ngra undan quyidagicha foydalanish mumkin
// Create subscribers
$johnDoe = new JobSeeker('John Doe');
$janeDoe = new JobSeeker('Jane Doe');
// Create publisher and attach subscribers
$jobPostings = new EmploymentAgency();
$jobPostings->attach($johnDoe);
$jobPostings->attach($janeDoe);
// Add a new job and see if subscribers get notified
$jobPostings->addJob(new JobPost('Software Engineer'));
// Output
// Hi John Doe! New job posted: Software Engineer
// Hi Jane Doe! New job posted: Software Engineer
🏃 Visitor
Hayotiy misol
Dubayga tashrif buyurayotgan kishini tasavvur qiling. Unga Dubayga kirish uchun shunchaki yo'l (ya'ni viza) kerak. Yetib kelgach, u bu yerdagi biror joyga borish uchun ruxsat so'rashi yoki qandaydir ovoragarchilikka tushishi shart bo'lmagan holda Dubaydagi istalgan joyga o'zi borib ko'rishi mumkin; unga shunchaki joy haqida xabar bering, u o'sha yerga borib ko'radi. Visitor pattern aynan shunday qilish imkonini beradi: u borib ko'riladigan joylarni qo'shishga yordam beradi, shunda sayyoh hech qanday ovoragarchiliksiz iloji boricha ko'proq joyga tashrif buyura oladi.
Oddiy so'z bilan aytganda
Visitor pattern obyektlarni o'zgartirmasdan ularga qo'shimcha operatsiyalar qo'shish imkonini beradi.
Wikipedia'da shunday deyilgan
Object-oriented programming va software engineering'da visitor design pattern — algoritmni u ishlaydigan obyekt tuzilmasidan ajratish usuli. Bu ajratishning amaliy natijasi — mavjud obyekt tuzilmalarini o'zgartirmasdan ularga yangi operatsiyalar qo'shish imkoniyati. Bu open/closed principle'ga amal qilish usullaridan biri.
Dasturiy misol
Keling, bir nechta turli xil hayvonlar bor va ularni ovoz chiqartirishimiz kerak bo'lgan hayvonot bog'i simulyatsiyasi misolini olaylik. Buni visitor pattern yordamida kodga o'giramiz
// Visitee
interface Animal
{
public function accept(AnimalOperation $operation);
}
// Visitor
interface AnimalOperation
{
public function visitMonkey(Monkey $monkey);
public function visitLion(Lion $lion);
public function visitDolphin(Dolphin $dolphin);
}
So'ngra bizda hayvonlar uchun implementation'larimiz bor
class Monkey implements Animal
{
public function shout()
{
echo 'Ooh oo aa aa!';
}
public function accept(AnimalOperation $operation)
{
$operation->visitMonkey($this);
}
}
class Lion implements Animal
{
public function roar()
{
echo 'Roaaar!';
}
public function accept(AnimalOperation $operation)
{
$operation->visitLion($this);
}
}
class Dolphin implements Animal
{
public function speak()
{
echo 'Tuut tuttu tuutt!';
}
public function accept(AnimalOperation $operation)
{
$operation->visitDolphin($this);
}
}
Keling, visitor'imizni implement qilamiz
class Speak implements AnimalOperation
{
public function visitMonkey(Monkey $monkey)
{
$monkey->shout();
}
public function visitLion(Lion $lion)
{
$lion->roar();
}
public function visitDolphin(Dolphin $dolphin)
{
$dolphin->speak();
}
}
Va keyin undan quyidagicha foydalanish mumkin
$monkey = new Monkey();
$lion = new Lion();
$dolphin = new Dolphin();
$speak = new Speak();
$monkey->accept($speak); // Ooh oo aa aa!
$lion->accept($speak); // Roaaar!
$dolphin->accept($speak); // Tuut tutt tuutt!
Buni shunchaki hayvonlar uchun inheritance ierarxiyasini yaratish orqali ham qilishimiz mumkin edi, lekin u holda hayvonlarga yangi harakatlar qo'shishimiz kerak bo'lgan har safar ularni o'zgartirishimizga to'g'ri kelardi. Endi esa ularni o'zgartirishimiz shart emas. Masalan, aytaylik, bizdan hayvonlarga sakrash xatti-harakatini qo'shish so'raldi; buni shunchaki yangi visitor yaratish orqali qo'shishimiz mumkin, ya'ni
class Jump implements AnimalOperation
{
public function visitMonkey(Monkey $monkey)
{
echo 'Jumped 20 feet high! on to the tree!';
}
public function visitLion(Lion $lion)
{
echo 'Jumped 7 feet! Back on the ground!';
}
public function visitDolphin(Dolphin $dolphin)
{
echo 'Walked on water a little and disappeared';
}
}
Va undan foydalanish uchun
$jump = new Jump();
$monkey->accept($speak); // Ooh oo aa aa!
$monkey->accept($jump); // Jumped 20 feet high! on to the tree!
$lion->accept($speak); // Roaaar!
$lion->accept($jump); // Jumped 7 feet! Back on the ground!
$dolphin->accept($speak); // Tuut tutt tuutt!
$dolphin->accept($jump); // Walked on water a little and disappeared
💡 Strategy
Hayotiy misol
Saralash misolini ko'rib chiqing: biz bubble sort'ni implement qildik, lekin ma'lumotlar o'sa boshladi va bubble sort juda sekinlasha boshladi. Bu muammoni hal qilish uchun Quick sort'ni implement qildik. Ammo endi quick sort algoritmi katta dataset'lar uchun yaxshiroq ishlasa-da, kichikroq dataset'lar uchun juda sekin edi. Buni hal qilish uchun biz shunday strategy implement qildik: kichik dataset'lar uchun bubble sort, kattaroqlari uchun esa quick sort ishlatiladi.
Oddiy so'z bilan aytganda
Strategy pattern vaziyatga qarab algoritm yoki strategy'ni almashtirish imkonini beradi.
Wikipedia'da shunday deyilgan
Kompyuter dasturlashda strategy pattern (policy pattern nomi bilan ham tanilgan) — algoritm xatti-harakatini runtime'da tanlash imkonini beradigan behavioral software design pattern.
Dasturiy misol
Yuqoridagi misolimizni kodga o'giramiz. Avvalo bizda strategy interfeysi va turli strategy implementation'lari bor
interface SortStrategy
{
public function sort(array $dataset): array;
}
class BubbleSortStrategy implements SortStrategy
{
public function sort(array $dataset): array
{
echo "Sorting using bubble sort";
// Do sorting
return $dataset;
}
}
class QuickSortStrategy implements SortStrategy
{
public function sort(array $dataset): array
{
echo "Sorting using quick sort";
// Do sorting
return $dataset;
}
}
So'ngra bizda istalgan strategy'dan foydalanadigan client'imiz bor
class Sorter
{
protected $sorter;
public function __construct(SortStrategy $sorter)
{
$this->sorter = $sorter;
}
public function sort(array $dataset): array
{
return $this->sorter->sort($dataset);
}
}
Va undan quyidagicha foydalanish mumkin
$dataset = [1, 5, 4, 3, 2, 8];
$sorter = new Sorter(new BubbleSortStrategy());
$sorter->sort($dataset); // Output : Sorting using bubble sort
$sorter = new Sorter(new QuickSortStrategy());
$sorter->sort($dataset); // Output : Sorting using quick sort
💢 State
Hayotiy misol
Tasavvur qiling, siz qandaydir chizish ilovasidan foydalanyapsiz va chizish uchun mo'yqalamni tanlaysiz. Endi mo'yqalam tanlangan rangga qarab o'z xatti-harakatini o'zgartiradi, ya'ni qizil rangni tanlagan bo'lsangiz, u qizil rangda chizadi, ko'k bo'lsa — ko'k rangda va hokazo.
Oddiy so'z bilan aytganda
U holat o'zgarganda sinfning xatti-harakatini o'zgartirish imkonini beradi.
Wikipedia'da shunday deyilgan
State pattern — state machine'ni object-oriented usulda implement qiladigan behavioral software design pattern. State pattern'da state machine har bir alohida holatni state pattern interfeysining derived class'i sifatida implement qilish va holatlar o'rtasidagi o'tishlarni pattern'ning superclass'ida belgilangan metodlarni chaqirish orqali amalga oshirish yo'li bilan implement qilinadi. State pattern'ni pattern interfeysida belgilangan metodlarni chaqirish orqali joriy strategy'ni almashtira oladigan strategy pattern sifatida talqin qilish mumkin.
Dasturiy misol
Matn muharriri misolini olaylik: u yozilayotgan matnning holatini o'zgartirish imkonini beradi, ya'ni qalin shriftni tanlagan bo'lsangiz, u qalin harflarda yoza boshlaydi, kursivni tanlagan bo'lsangiz — kursivda va hokazo.
Avvalo bizda state interfeysi va bir nechta state implementation'lari bor
interface WritingState
{
public function write(string $words);
}
class UpperCase implements WritingState
{
public function write(string $words)
{
echo strtoupper($words);
}
}
class LowerCase implements WritingState
{
public function write(string $words)
{
echo strtolower($words);
}
}
class DefaultText implements WritingState
{
public function write(string $words)
{
echo $words;
}
}
So'ngra bizda muharririmiz bor
class TextEditor
{
protected $state;
public function __construct(WritingState $state)
{
$this->state = $state;
}
public function setState(WritingState $state)
{
$this->state = $state;
}
public function type(string $words)
{
$this->state->write($words);
}
}
Va keyin undan quyidagicha foydalanish mumkin
$editor = new TextEditor(new DefaultText());
$editor->type('First line');
$editor->setState(new UpperCase());
$editor->type('Second line');
$editor->type('Third line');
$editor->setState(new LowerCase());
$editor->type('Fourth line');
$editor->type('Fifth line');
// Output:
// First line
// SECOND LINE
// THIRD LINE
// fourth line
// fifth line
📒 Template Method
Hayotiy misol
Aytaylik, biz uy qurdiryapmiz. Qurilish bosqichlari quyidagicha bo'lishi mumkin
- Uyning poydevorini tayyorlash
- Devorlarni qurish
- Tom yopish
- Boshqa qavatlarni qo'shish
Bu bosqichlar tartibini hech qachon o'zgartirib bo'lmaydi, ya'ni devorlarni qurmasdan oldin tomni qura olmaysiz va hokazo, lekin har bir bosqichni o'zgartirish mumkin, masalan, devorlar yog'ochdan, poliesterdan yoki toshdan qilinishi mumkin.
Oddiy so'z bilan aytganda
Template method muayyan algoritm qanday bajarilishi mumkinligining skeletini belgilaydi, lekin bu bosqichlarning implementation'ini child class'larga qoldiradi.
Wikipedia'da shunday deyilgan
Software engineering'da template method pattern — operatsiya ichida algoritmning dastur skeletini belgilaydigan va ba'zi bosqichlarni subclass'larga qoldiradigan behavioral design pattern. U algoritm tuzilmasini o'zgartirmasdan algoritmning muayyan bosqichlarini qayta belgilash imkonini beradi.
Dasturiy misol
Tasavvur qiling, bizda ilovamizni test qilish, lint qilish, build qilish, build hisobotlarini (ya'ni code coverage hisobotlari, linting hisoboti va h.k.) yaratish va test serverga deploy qilishga yordam beradigan build vositasi bor.
Avvalo bizda build algoritmi skeletini belgilaydigan base class'imiz bor
abstract class Builder
{
// Template method
final public function build()
{
$this->test();
$this->lint();
$this->assemble();
$this->deploy();
}
abstract public function test();
abstract public function lint();
abstract public function assemble();
abstract public function deploy();
}
So'ngra bizda implementation'larimiz bo'lishi mumkin
class AndroidBuilder extends Builder
{
public function test()
{
echo 'Running android tests';
}
public function lint()
{
echo 'Linting the android code';
}
public function assemble()
{
echo 'Assembling the android build';
}
public function deploy()
{
echo 'Deploying android build to server';
}
}
class IosBuilder extends Builder
{
public function test()
{
echo 'Running ios tests';
}
public function lint()
{
echo 'Linting the ios code';
}
public function assemble()
{
echo 'Assembling the ios build';
}
public function deploy()
{
echo 'Deploying ios build to server';
}
}
Va keyin undan quyidagicha foydalanish mumkin
$androidBuilder = new AndroidBuilder();
$androidBuilder->build();
// Output:
// Running android tests
// Linting the android code
// Assembling the android build
// Deploying android build to server
$iosBuilder = new IosBuilder();
$iosBuilder->build();
// Output:
// Running ios tests
// Linting the ios code
// Assembling the ios build
// Deploying ios build to server
Yakunlaymiz, do'stlar
Mana, shu bilan deyarli yakunladik. Men bu qo'llanmani yaxshilashda davom etaman, shuning uchun keyinroq qaytib ko'rish uchun ushbu repository'ni kuzatib borishingiz/unga star qo'yishingiz mumkin. Shuningdek, architectural pattern'lar haqida ham xuddi shunday yozish rejam bor, kuzatib boring.