9-bo‘lim
Decorator
Interfeysni saqlagan holda xulq qo'shish - va qatlamlar tartibi nima uchun muhim.
Ushbu bo‘lim mundarijasi
Decorator - obyektni o'sha interfeysdagi boshqa obyekt bilan o'rab, unga yangi xulq qo'shish naqshi.
Adapter interfeysni o'zgartiradi, Decorator esa uni saqlaydi. Shu sababli dekoratorlarni bir-birining ustiga xohlaganingizcha qo'yish mumkin.
Muammo: vorislar portlashi #
Bizda SMS yuboruvchi bor. Unga jurnal qo'shish kerak:
interface Yuboruvchi
{
public function yubor(string $manzil, string $matn): bool;
}
// `final` emas - quyida uni ataylab meros bilan kengaytiramiz
class AsosiySms implements Yuboruvchi
{
public function yubor(string $manzil, string $matn): bool
{
echo " -> {$manzil}: {$matn}", PHP_EOL;
return true;
}
}
// Meros bilan: jurnal qo'shamiz
final class JurnalliSms extends AsosiySms
{
public function yubor(string $manzil, string $matn): bool
{
echo " [jurnal] boshlandi", PHP_EOL;
$natija = parent::yubor($manzil, $matn);
echo " [jurnal] natija: ", $natija ? 'ok' : 'xato', PHP_EOL;
return $natija;
}
}
(new JurnalliSms())->yubor('+998901112233', 'Salom');
[jurnal] boshlandi
-> +998901112233: Salom
[jurnal] natija: ok
Ishladi. Endi qayta urinish ham kerak. Keyin tezlik chegarasi. Keyin shifrlash.
Uchta qo'shimcha xulq uchun barcha kombinatsiyalarni yozish kerak:
| Kombinatsiya | Sinf nomi |
|---|---|
| jurnal | JurnalliSms |
| qayta urinish | QaytaSms |
| chegara | ChegaraliSms |
| jurnal + qayta urinish | JurnalliQaytaSms |
| jurnal + chegara | JurnalliChegaraliSms |
| qayta urinish + chegara | QaytaChegaraliSms |
| uchalasi | JurnalliQaytaChegaraliSms |
7 ta sinf uchta xulq uchun. To'rtinchi xulq qo'shilsa - 15 ta. Formulasi: 2ⁿ - 1.
Ustiga - bularning hammasi AsosiySms dan meros oladi. Agar
Telegram yuboruvchi ham kerak bo'lsa, hammasi ikki barobar
ko'payadi.
Bu sinflar portlashi deb ataladi.
Yechim: o'rovchi qatlamlar #
Dekorator ham Yuboruvchi, lekin ichida boshqa
Yuboruvchi ni saqlaydi:
final class JurnalQatlami implements Yuboruvchi
{
public function __construct(private readonly Yuboruvchi $ichki) {}
public function yubor(string $manzil, string $matn): bool
{
echo " [jurnal] boshlandi: {$manzil}", PHP_EOL;
$natija = $this->ichki->yubor($manzil, $matn);
echo ' [jurnal] natija: ', $natija ? 'ok' : 'xato', PHP_EOL;
return $natija;
}
}
$y = new JurnalQatlami(new AsosiySms());
$y->yubor('+998901112233', 'Salom');
[jurnal] boshlandi: +998901112233
-> +998901112233: Salom
[jurnal] natija: ok
Natija bir xil, lekin muhim farq bor: JurnalQatlami
AsosiySms haqida hech narsa bilmaydi. U faqat
Yuboruvchi interfeysini biladi.
Demak uni istalgan yuboruvchi ustiga qo'yish mumkin.
Ikkinchi va uchinchi qatlam #
// Ataylab xato beruvchi yuboruvchi - qayta urinishni sinash uchun
final class NosozSms implements Yuboruvchi
{
private int $urinish = 0;
public function __construct(private readonly int $nechtaXato) {}
public function yubor(string $manzil, string $matn): bool
{
$this->urinish++;
if ($this->urinish <= $this->nechtaXato) {
echo " -> urinish {$this->urinish}: XATO", PHP_EOL;
return false;
}
echo " -> urinish {$this->urinish}: yuborildi", PHP_EOL;
return true;
}
}
final class QaytaUrinish implements Yuboruvchi
{
public function __construct(
private readonly Yuboruvchi $ichki,
private readonly int $marta = 3,
) {}
public function yubor(string $manzil, string $matn): bool
{
for ($i = 1; $i <= $this->marta; $i++) {
if ($this->ichki->yubor($manzil, $matn)) {
return true;
}
echo " [qayta] {$i}-urinish muvaffaqiyatsiz", PHP_EOL;
}
return false;
}
}
final class TezlikChegarasi implements Yuboruvchi
{
private int $yuborilgan = 0;
public function __construct(
private readonly Yuboruvchi $ichki,
private readonly int $chegara,
) {}
public function yubor(string $manzil, string $matn): bool
{
if ($this->yuborilgan >= $this->chegara) {
echo " [chegara] limit tugadi ({$this->chegara})", PHP_EOL;
return false;
}
$this->yuborilgan++;
return $this->ichki->yubor($manzil, $matn);
}
}
// Ikki marta xato beradi, uchinchisida yuboradi
$y = new QaytaUrinish(new NosozSms(2), 4);
$natija = $y->yubor('+998901112233', 'Salom');
echo 'natija: ', $natija ? 'ok' : 'xato', PHP_EOL;
-> urinish 1: XATO
[qayta] 1-urinish muvaffaqiyatsiz
-> urinish 2: XATO
[qayta] 2-urinish muvaffaqiyatsiz
-> urinish 3: yuborildi
natija: ok
Endi uchalasini birga qo'yamiz:
$y = new JurnalQatlami(
new TezlikChegarasi(
new QaytaUrinish(new NosozSms(1), 3),
2,
),
);
foreach (['+998901112233', '+998935556677', '+998977778899'] as $m) {
echo "--- {$m}", PHP_EOL;
$y->yubor($m, 'Test');
}
--- +998901112233
[jurnal] boshlandi: +998901112233
-> urinish 1: XATO
[qayta] 1-urinish muvaffaqiyatsiz
-> urinish 2: yuborildi
[jurnal] natija: ok
--- +998935556677
[jurnal] boshlandi: +998935556677
-> urinish 3: yuborildi
[jurnal] natija: ok
--- +998977778899
[jurnal] boshlandi: +998977778899
[chegara] limit tugadi (2)
[jurnal] natija: xato
Tartib muhim #
// A: chegara tashqarida - qayta urinishlar limitga kirmaydi
echo '=== Chegara(Qayta(...))', PHP_EOL;
$a = new TezlikChegarasi(new QaytaUrinish(new NosozSms(2), 4), 2);
var_dump($a->yubor('+998901112233', 'Salom'));
// B: chegara ichkarida - har urinish limitni yeydi
echo '=== Qayta(Chegara(...))', PHP_EOL;
$b = new QaytaUrinish(new TezlikChegarasi(new NosozSms(2), 2), 4);
var_dump($b->yubor('+998901112233', 'Salom'));
=== Chegara(Qayta(...))
-> urinish 1: XATO
[qayta] 1-urinish muvaffaqiyatsiz
-> urinish 2: XATO
[qayta] 2-urinish muvaffaqiyatsiz
-> urinish 3: yuborildi
bool(true)
=== Qayta(Chegara(...))
-> urinish 1: XATO
[qayta] 1-urinish muvaffaqiyatsiz
-> urinish 2: XATO
[qayta] 2-urinish muvaffaqiyatsiz
[chegara] limit tugadi (2)
[qayta] 3-urinish muvaffaqiyatsiz
[chegara] limit tugadi (2)
[qayta] 4-urinish muvaffaqiyatsiz
bool(false)
Ikkala holatda ham aynan uchta sinf ishlatildi. Faqat
tartib boshqacha - va natija true dan false ga
o'zgardi.
| Tartib | Ma'nosi |
|---|---|
Chegara(Qayta(...)) | Chegara mantiqiy xabarlarni sanaydi |
Qayta(Chegara(...)) | Chegara har urinishni sanaydi |
Ikkalasi ham to'g'ri bo'lishi mumkin - bu talabga bog'liq. Muhimi shuki, tartibni bilib turib tanlash kerak.
Amaliy tavsiya:
| Qatlam | Odatda qayerda |
|---|---|
| Jurnal / o'lchov | Eng tashqarida (hamma narsani ko'rish uchun) |
| Kesh | Tashqariroq (keraksiz ishni oldini oladi) |
| Qayta urinish | Ichkariroq (faqat haqiqiy chaqiruvni takrorlaydi) |
| Xavfsizlik tekshiruvi | Eng tashqarida |
Dekoratorni dinamik yig'ish #
Qatlamlar ro'yxatdan yig'ilishi mumkin:
/**
* @param list<callable(Yuboruvchi): Yuboruvchi> $qatlamlar
*/
function qatlamlarniYig(Yuboruvchi $asos, array $qatlamlar): Yuboruvchi
{
// teskari tartibda o'raymiz: ro'yxatdagi birinchisi eng tashqarida bo'ladi
foreach (array_reverse($qatlamlar) as $qatlam) {
$asos = $qatlam($asos);
}
return $asos;
}
$sozlama = ['jurnal', 'chegara'];
$xarita = [
'jurnal' => fn(Yuboruvchi $y): Yuboruvchi => new JurnalQatlami($y),
'chegara' => fn(Yuboruvchi $y): Yuboruvchi => new TezlikChegarasi($y, 1),
'qayta' => fn(Yuboruvchi $y): Yuboruvchi => new QaytaUrinish($y, 2),
];
$y = qatlamlarniYig(new AsosiySms(),
array_map(fn(string $n): callable => $xarita[$n], $sozlama));
$y->yubor('+998901112233', 'Birinchi');
$y->yubor('+998935556677', 'Ikkinchi');
[jurnal] boshlandi: +998901112233
-> +998901112233: Birinchi
[jurnal] natija: ok
[jurnal] boshlandi: +998935556677
[chegara] limit tugadi (1)
[jurnal] natija: xato
HTTP freymvorklardagi middleware - Decorator naqshining eng mashhur qo'llanilishi.
Har middleware so'rovni oladi, biror ish qiladi, keyingisiga uzatadi, javobni oladi va yana biror ish qiladi:
| Middleware | Dekorator qatlami |
|---|---|
| CORS sarlavhalari | Javobni o'zgartiradi |
| Autentifikatsiya | Ichkariga o'tkazmasligi mumkin |
| Jurnal | Ikkala tomonni yozadi |
| Gzip | Javobni siqadi |
Sozlamalar faylidagi middleware ro'yxati - yuqoridagi
$sozlama massivining aynan o'zi. Va u yerda ham tartib
muhim.
PHP ning o'zida Decorator #
// Oqim (stream) filtrlari - o'rnatilgan dekoratorlar
$fayl = tempnam(sys_get_temp_dir(), 'dec');
file_put_contents($fayl, "birinchi\nikkinchi\nuchinchi\n");
// oddiy o'qish
echo '--- oddiy', PHP_EOL;
echo file_get_contents($fayl);
// filtr qatlami bilan o'qish
echo '--- katta harf filtri', PHP_EOL;
echo file_get_contents('php://filter/read=string.toupper/resource=' . $fayl);
// ikkita filtr ketma-ket
echo '--- toupper + rot13', PHP_EOL;
echo file_get_contents(
'php://filter/read=string.toupper|string.rot13/resource=' . $fayl);
unlink($fayl);
--- oddiy
birinchi
ikkinchi
uchinchi
--- katta harf filtri
BIRINCHI
IKKINCHI
UCHINCHI
--- toupper + rot13
OVEVAPUV
VXXVAPUV
HPUVAPUV
Filtrlar | bilan zanjirlanadi va ular ham tartibga
bog'liq - aynan dekorator kabi.
Naqshning narxi #
$chuqur = new JurnalQatlami(
new TezlikChegarasi(
new QaytaUrinish(
new JurnalQatlami(new AsosiySms()), 2), 5));
echo 'tashqi tur: ', $chuqur::class, PHP_EOL;
echo 'AsosiySms mi: ', $chuqur instanceof AsosiySms ? 'ha' : 'yoq', PHP_EOL;
echo 'Yuboruvchi mi: ', $chuqur instanceof Yuboruvchi ? 'ha' : 'yoq', PHP_EOL;
tashqi tur: JurnalQatlami
AsosiySms mi: yoq
Yuboruvchi mi: ha
instanceof va konkret tur yo'qoladiO'ralgan obyekt endi AsosiySms emas. Agar kodingizda
instanceof AsosiySms yoki $obj::class === 'AsosiySms'
tekshiruvi bo'lsa - u buziladi.
Boshqa narxlar:
| Narx | Izoh |
|---|---|
| Chaqiruv steki chuqurlashadi | Nosozlik qidirish qiyinlashadi |
| Obyektlar soni ko'payadi | Har qatlam alohida obyekt |
| Qaysi qatlam nima qilganini topish | Jurnalsiz deyarli imkonsiz |
| Interfeysga metod qo'shish | Har dekoratorga qo'shish kerak |
Oxirgisi eng og'riqli: Yuboruvchi ga holat() metodi
qo'shsangiz, uni beshta dekoratorda ham yozish kerak - va
ularning barchasi shunchaki $this->ichki->holat() deydi.
Shu sababli dekorator uchun interfeys kichik bo'lishi kerak (2-bo'limdagi I tamoyili).
- Bir interfeysni bajaruvchi asosiy sinf yozing.
- Uchta xulqni meros bilan qo'shsangiz, nechta sinf kerakligini sanang.
- Uchta dekorator yozing: jurnal, qayta urinish, chegara.
- Ularni turli tartibda birlashtiring.
- Tartib natijani o'zgartirgan holatni toping va sababini yozing.
- Qatlamlarni ro'yxatdan dinamik yig'uvchi funksiya yozing.
php://filterbilan ikkita filtrni zanjirlang.- Filtrlar tartibini almashtiring - natija o'zgardimi?
- O'ralgan obyektda
instanceofni tekshiring. - Interfeysga yangi metod qo'shing va nechta faylni tahrirlaganingizni sanang.
Xulosa #
- Decorator - interfeysni saqlagan holda xulq qo'shadi.
- Meros bilan qilinsa, sinflar soni 2ⁿ - 1 ga o'sadi.
- Dekorator o'zi ham interfeysni bajaradi va ichida o'sha interfeysdagi obyektni saqlaydi.
- Shu sababli qatlamlarni cheksiz ustma-ust qo'yish mumkin.
- Qatlam o'ralgan obyektning konkret turini bilmaydi.
- Tartib natijani o'zgartiradi - buni bilib turib tanlash kerak.
- Jurnal va xavfsizlik odatda tashqarida, qayta urinish ichkarida turadi.
- Qatlamlar ro'yxatdan dinamik yig'ilishi mumkin - bu PSR-15 middleware.
- PHP ning
php://filteroqim filtrlari - tilga o'rnatilgan dekorator. - Narxi:
instanceofyo'qoladi, stek chuqurlashadi, interfeysga metod qo'shish qimmat.
Keyingi bo'limda murakkab tizimga oddiy kirish nuqtasi beradigan Facade ni ko'ramiz.
O‘qish tarixini saqlamoqchimisiz?
Tizimga kirsangiz, tugatgan bo‘limlaringiz saqlanadi va qoldirgan joyingizdan davom etasiz.
Xatolik topdingizmi?
Imlo xatosi, ishlamaydigan kod yoki noto‘g‘ri ma‘lumotni ko‘rsangiz - bizga xabar bering. Har bir xabar administrator tomonidan ko‘rib chiqiladi.