7-bo‘lim

Loyihalash naqshlari

Yaratuvchi, tuzilmaviy va xulq-atvor naqshlari - amaliy misollar bilan eng ko'p ishlatiladiganlari.

🕑 13 daqiqa o‘qish 📄 813 so‘z 👁 6 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Naqshlar nima uchun kerak?
  2. Strategiya
  3. Fabrika
  4. Quruvchi (Builder)
  5. Adapter
  6. Dekorator
  7. Kuzatuvchi (Observer)
  8. Repozitoriy
  9. Yagona (Singleton)
  10. Fasad
  11. Naqshlarni qachon ishlatmaslik kerak?
  12. Amaliy tanlov jadvali
  13. Xulosa

Naqsh - tez-tez uchraydigan muammoning sinovdan o'tgan yechimi. U tayyor kod emas, balki yondashuv sxemasi.

Naqshlar nima uchun kerak? #

Uchta foyda
  1. Sinovdan o'tgan yechim - g'ildirakni qaytadan ixtiro qilmaysiz
  2. Umumiy til - "bu yerda Strategiya ishlatamiz" degan gap butun jamoaga tushunarli
  3. Kengaytirish - naqshlar odatda o'zgarishga moslashuvchan

Kamchiligi: ularni haddan tashqari ishlatish. Har bir masalaga naqsh qidirish - keraksiz murakkablik keltiradi.

Uch guruh naqshlar Yaratuvchi obyekt qanday yaratiladi • Fabrika • Quruvchi (Builder) • Yagona (Singleton) • Prototip Tuzilmaviy obyektlar qanday birlashadi • Adapter • Dekorator • Fasad • Proksi, Kompozit Xulq-atvor obyektlar qanday gaplashadi • Strategiya • Kuzatuvchi (Observer) • Buyruq (Command) • Shablon metod, Holat
Gang of Four kitobida 23 ta klassik naqsh tavsiflangan

Strategiya #

Muammo: bir vazifani bajarishning bir necha usuli bor va ular almashtirilishi kerak.

PHP
// YOMON: shartlar zanjiri
class YetkazishHisoblagichi
{
    public function hisoblang(string $usul, float $vazn, float $masofa): float
    {
        if ($usul === 'kuryer') {
            return 15000 + $masofa * 500;
        }

        if ($usul === 'pochta') {
            return 8000 + $vazn * 2000;
        }

        if ($usul === 'ekspress') {
            return 35000 + $masofa * 800 + $vazn * 1500;
        }

        throw new InvalidArgumentException("Noma'lum usul: {$usul}");
    }
}

Yangi usul qo'shish uchun mavjud klassni o'zgartirish kerak.

PHP
// YAXSHI: Strategiya naqshi
interface YetkazishStrategiyasi
{
    public function hisoblang(float $vazn, float $masofa): float;
    public function nomi(): string;
}

class KuryerYetkazish implements YetkazishStrategiyasi
{
    public function hisoblang(float $vazn, float $masofa): float
    {
        return 15000 + $masofa * 500;
    }

    public function nomi(): string
    {
        return 'Kuryer orqali';
    }
}

class PochtaYetkazish implements YetkazishStrategiyasi
{
    public function hisoblang(float $vazn, float $masofa): float
    {
        return 8000 + $vazn * 2000;
    }

    public function nomi(): string
    {
        return 'Pochta orqali';
    }
}

class EkspressYetkazish implements YetkazishStrategiyasi
{
    public function hisoblang(float $vazn, float $masofa): float
    {
        return 35000 + $masofa * 800 + $vazn * 1500;
    }

    public function nomi(): string
    {
        return 'Ekspress yetkazish';
    }
}

class Buyurtma
{
    public function __construct(
        private YetkazishStrategiyasi $yetkazish,
        private float $vazn,
        private float $masofa,
    ) {}

    public function yetkazishNarxi(): float
    {
        return $this->yetkazish->hisoblang($this->vazn, $this->masofa);
    }
}
PHP
$buyurtma = new Buyurtma(new EkspressYetkazish(), 3.5, 12);
echo $buyurtma->yetkazishNarxi();
Natija
49850
Yangi usul qo'shish

Endi "dron orqali yetkazish" qo'shish uchun yangi klass yozasiz. Mavjud kodning birorta satri ham o'zgarmaydi.

Bu ochiq-yopiq prinsipi (keyingi bo'limda) amalda.

Fabrika #

Muammo: obyektni yaratish mantiqi murakkab yoki shartga bog'liq.

PHP
class YetkazishFabrikasi
{
    private const USULLAR = [
        'kuryer'   => KuryerYetkazish::class,
        'pochta'   => PochtaYetkazish::class,
        'ekspress' => EkspressYetkazish::class,
    ];

    public function yarating(string $usul): YetkazishStrategiyasi
    {
        if (!isset(self::USULLAR[$usul])) {
            throw new NomalumYetkazishUsuli($usul);
        }

        $klass = self::USULLAR[$usul];

        return new $klass();
    }

    public function mavjudUsullar(): array
    {
        return array_keys(self::USULLAR);
    }
}
PHP
$fabrika = new YetkazishFabrikasi();
$strategiya = $fabrika->yarating($sorov->get('yetkazish_usuli'));

$buyurtma = new Buyurtma($strategiya, $vazn, $masofa);
Fabrika qachon kerak?
  • Yaratish mantiqi bir necha joyda takrorlansa
  • Yaratish shartga bog'liq bo'lsa
  • Obyekt yaratish uchun ko'p qadam kerak bo'lsa

Oddiy new Klass() yetarli bo'lsa - fabrika keraksiz.

Quruvchi (Builder) #

Muammo: obyektda juda ko'p ixtiyoriy parametr bor.

PHP
// YOMON: konstruktor teleskopi
$hisobot = new Hisobot(
    'Oylik savdo', '2026-08-01', '2026-08-31',
    true, false, true, 'pdf', null, ['savdo', 'qaytarish'], true
);

Bu qatorni bir oydan keyin hech kim o'qiy olmaydi.

PHP
class HisobotQuruvchi
{
    private string $sarlavha = 'Hisobot';
    private ?DateTimeImmutable $boshlanish = null;
    private ?DateTimeImmutable $tugash = null;
    private string $format = 'pdf';
    private array $bolimlar = [];
    private bool $grafiklarBilan = false;
    private bool $xulosaBilan = true;

    public function sarlavha(string $sarlavha): self
    {
        $this->sarlavha = $sarlavha;
        return $this;
    }

    public function davr(DateTimeImmutable $dan, DateTimeImmutable $gacha): self
    {
        $this->boshlanish = $dan;
        $this->tugash = $gacha;
        return $this;
    }

    public function format(string $format): self
    {
        $this->format = $format;
        return $this;
    }

    public function bolim(string $nom): self
    {
        $this->bolimlar[] = $nom;
        return $this;
    }

    public function grafiklarBilan(): self
    {
        $this->grafiklarBilan = true;
        return $this;
    }

    public function quring(): Hisobot
    {
        if ($this->boshlanish === null || $this->tugash === null) {
            throw new LogicException('Hisobot davri ko\'rsatilmagan');
        }

        return new Hisobot(
            $this->sarlavha, $this->boshlanish, $this->tugash,
            $this->format, $this->bolimlar,
            $this->grafiklarBilan, $this->xulosaBilan,
        );
    }
}
PHP
$hisobot = (new HisobotQuruvchi())
    ->sarlavha('Oylik savdo hisoboti')
    ->davr(new DateTimeImmutable('2026-08-01'), new DateTimeImmutable('2026-08-31'))
    ->format('pdf')
    ->bolim('savdo')
    ->bolim('qaytarish')
    ->grafiklarBilan()
    ->quring();

Endi kod o'zini-o'zi hujjatlaydi.

Adapter #

Muammo: mavjud klass interfeysi sizga kerakli interfeysga mos kelmaydi.

PHP
// Bizning interfeys
interface TolovTizimi
{
    public function tolang(int $summa, string $valyuta): TolovNatijasi;
}

// Tashqi kutubxona - biz uni o'zgartira olmaymiz
class PaymeKutubxonasi
{
    public function createTransaction(array $params): array
    {
        // ['amount' => 100000, 'currency' => 'UZS', ...]
        return ['transaction_id' => 'tx_123', 'state' => 1];
    }
}

// Adapter
class PaymeAdapter implements TolovTizimi
{
    public function __construct(private PaymeKutubxonasi $payme) {}

    public function tolang(int $summa, string $valyuta): TolovNatijasi
    {
        $javob = $this->payme->createTransaction([
            'amount'   => $summa * 100,      // tiyinga aylantiramiz
            'currency' => $valyuta,
        ]);

        return new TolovNatijasi(
            muvaffaqiyatli: $javob['state'] === 1,
            tranzaksiyaId: $javob['transaction_id'],
        );
    }
}
Adapter nima yutuq beradi?

Ilovangiz faqat TolovTizimi interfeysini biladi.

Ertaga boshqa to'lov provayderiga o'tsangiz - yangi adapter yozasiz, qolgan kod o'zgarmaydi.

Bu naqsh tashqi kutubxonalar bilan ishlashda deyarli har doim foydali.

Dekorator #

Muammo: obyektga yangi xatti-harakat qo'shish kerak, lekin uni o'zgartirmasdan.

PHP
interface XabarYuboruvchi
{
    public function yuboring(string $qabuluvchi, string $matn): void;
}

class EmailYuboruvchi implements XabarYuboruvchi
{
    public function yuboring(string $qabuluvchi, string $matn): void
    {
        mail($qabuluvchi, 'Xabar', $matn);
    }
}

// Dekoratorlar
class JurnalDekoratori implements XabarYuboruvchi
{
    public function __construct(
        private XabarYuboruvchi $keyingi,
        private LoggerInterface $jurnal,
    ) {}

    public function yuboring(string $qabuluvchi, string $matn): void
    {
        $this->jurnal->info('Xabar yuborilmoqda', ['kimga' => $qabuluvchi]);

        $this->keyingi->yuboring($qabuluvchi, $matn);

        $this->jurnal->info('Xabar yuborildi');
    }
}

class ChegaraDekoratori implements XabarYuboruvchi
{
    public function __construct(
        private XabarYuboruvchi $keyingi,
        private ChegaraNazoratchisi $nazorat,
    ) {}

    public function yuboring(string $qabuluvchi, string $matn): void
    {
        if (!$this->nazorat->ruxsatBormi($qabuluvchi)) {
            throw new ChegaraOshdi($qabuluvchi);
        }

        $this->keyingi->yuboring($qabuluvchi, $matn);
    }
}

class QaytaUrinishDekoratori implements XabarYuboruvchi
{
    public function __construct(
        private XabarYuboruvchi $keyingi,
        private int $urinishlar = 3,
    ) {}

    public function yuboring(string $qabuluvchi, string $matn): void
    {
        $oxirgiXato = null;

        for ($i = 1; $i <= $this->urinishlar; $i++) {
            try {
                $this->keyingi->yuboring($qabuluvchi, $matn);
                return;
            } catch (Throwable $xato) {
                $oxirgiXato = $xato;
                sleep(2 ** $i);
            }
        }

        throw $oxirgiXato;
    }
}
PHP
// Qatlamlarni istalgancha birlashtiramiz
$yuboruvchi = new JurnalDekoratori(
    new ChegaraDekoratori(
        new QaytaUrinishDekoratori(
            new EmailYuboruvchi()
        ),
        $nazoratchi
    ),
    $jurnal
);

$yuboruvchi->yuboring('[email protected]', 'Buyurtmangiz tayyor');
Dekoratorlar - piyoz qatlamlari Jurnal Chegara nazorati Qayta urinish EmailYuboruvchi Har bir qatlam mustaqil, tartibini o'zgartirish mumkin
Asosiy klass o'zgarmaydi, imkoniyatlar tashqaridan qo'shiladi

Kuzatuvchi (Observer) #

Muammo: bir obyektdagi o'zgarish haqida boshqalarga xabar berish kerak.

PHP
interface HodisaTinglovchisi
{
    public function ishlang(object $hodisa): void;
}

class HodisaDispetcheri
{
    private array $tinglovchilar = [];

    public function tinglang(string $hodisaKlassi, HodisaTinglovchisi $tinglovchi): void
    {
        $this->tinglovchilar[$hodisaKlassi][] = $tinglovchi;
    }

    public function elon(object $hodisa): void
    {
        foreach ($this->tinglovchilar[$hodisa::class] ?? [] as $tinglovchi) {
            $tinglovchi->ishlang($hodisa);
        }
    }
}

// Hodisa
final class BuyurtmaYaratildi
{
    public function __construct(
        public readonly int $buyurtmaId,
        public readonly int $mijozId,
        public readonly float $summa,
    ) {}
}

// Tinglovchilar
class EmailXabarnoma implements HodisaTinglovchisi
{
    public function ishlang(object $hodisa): void
    {
        // Mijozga tasdiqlash xati
    }
}

class ZaxiraKamaytirish implements HodisaTinglovchisi
{
    public function ishlang(object $hodisa): void
    {
        // Ombordan mahsulotni kamaytirish
    }
}

class SodiqlikBallari implements HodisaTinglovchisi
{
    public function ishlang(object $hodisa): void
    {
        // Mijozga ball qo'shish
    }
}
PHP
$dispetcher = new HodisaDispetcheri();
$dispetcher->tinglang(BuyurtmaYaratildi::class, new EmailXabarnoma());
$dispetcher->tinglang(BuyurtmaYaratildi::class, new ZaxiraKamaytirish());
$dispetcher->tinglang(BuyurtmaYaratildi::class, new SodiqlikBallari());

$dispetcher->elon(new BuyurtmaYaratildi(142, 7, 850000));
Kuzatuvchi naqshining xavfi

Oqimni kuzatish qiyinlashadi. "Nima uchun bu email yuborildi?" degan savolga javob topish uchun barcha tinglovchilarni ko'rish kerak.

Yechim: tinglovchilarni bir joyda ro'yxatdan o'tkazing va nomlarini tushunarli qiling.

Repozitoriy #

Muammo: biznes mantiq ma'lumot saqlash usulini bilmasligi kerak.

PHP
interface MahsulotRepozitoriyi
{
    public function toping(int $id): ?Mahsulot;
    public function kategoriyaBoyicha(int $kategoriyaId): array;
    public function saqlang(Mahsulot $mahsulot): Mahsulot;
    public function ochiring(int $id): void;
}

class MysqlMahsulotRepozitoriyi implements MahsulotRepozitoriyi
{
    public function __construct(private PDO $pdo) {}

    public function toping(int $id): ?Mahsulot
    {
        $stmt = $this->pdo->prepare('SELECT * FROM mahsulotlar WHERE id = ?');
        $stmt->execute([$id]);

        $qator = $stmt->fetch();

        return $qator ? Mahsulot::qatordan($qator) : null;
    }

    // ...
}

// Test uchun
class XotiradaMahsulotRepozitoriyi implements MahsulotRepozitoriyi
{
    private array $mahsulotlar = [];
    private int $keyingiId = 1;

    public function toping(int $id): ?Mahsulot
    {
        return $this->mahsulotlar[$id] ?? null;
    }

    public function saqlang(Mahsulot $mahsulot): Mahsulot
    {
        $id = $mahsulot->id ?? $this->keyingiId++;
        $this->mahsulotlar[$id] = $mahsulot;

        return $mahsulot;
    }

    // ...
}
Testlash uchun bebaho

Modul testlarida XotiradaMahsulotRepozitoriyi ishlatiladi:

  • Baza kerak emas
  • Testlar yuz barobar tezroq
  • Har bir test toza holatdan boshlanadi

Yagona (Singleton) #

Muammo: klassning faqat bitta nusxasi bo'lishi kerak.

PHP
class Konfiguratsiya
{
    private static ?self $nusxa = null;
    private array $sozlamalar = [];

    private function __construct()
    {
        $this->sozlamalar = require __DIR__ . '/../config/app.php';
    }

    public static function nusxa(): self
    {
        return self::$nusxa ??= new self();
    }

    public function ol(string $kalit, mixed $standart = null): mixed
    {
        return $this->sozlamalar[$kalit] ?? $standart;
    }
}
Singleton - eng ko'p tanqid qilinadigan naqsh
MuammoIzoh
Global holatKod qayerdan o'zgarganini bilib bo'lmaydi
Testlash qiyinTestlar orasida holat saqlanib qoladi
Yashirin bog'liqlikKonstruktorda ko'rinmaydi
Parallel ishda muammoKo'p oqimli muhitda xavfli

Muqobil: bog'liqlikni konstruktor orqali uzating:

PHP
class BuyurtmaServisi
{
    public function __construct(private Konfiguratsiya $sozlama) {}
}

Endi bog'liqlik ochiq ko'rinadi va testda soxta obyekt berish mumkin.

Zamonaviy loyihalarda Singleton o'rniga DI konteyner ishlatiladi.

Fasad #

Muammo: murakkab tizim bilan ishlash uchun sodda interfeys kerak.

PHP
class BuyurtmaFasadi
{
    public function __construct(
        private SavatServisi $savat,
        private TolovServisi $tolov,
        private OmborServisi $ombor,
        private XabarnomaServisi $xabarnoma,
        private HujjatServisi $hujjat,
    ) {}

    public function buyurtmaniRasmiylashtiring(int $mijozId, string $tolovUsuli): Buyurtma
    {
        $savat = $this->savat->oling($mijozId);
        $this->ombor->zaxiraniTekshiring($savat->elementlar());

        $buyurtma = $this->savat->buyurtmagaAylantiring($savat);
        $this->tolov->amalgaOshiring($buyurtma, $tolovUsuli);
        $this->ombor->kamaytiring($buyurtma->elementlar());

        $chek = $this->hujjat->chekYarating($buyurtma);
        $this->xabarnoma->tasdiqlashYuboring($buyurtma, $chek);

        $this->savat->tozalang($mijozId);

        return $buyurtma;
    }
}

Kontroller endi bitta metod chaqiradi, beshta servisni bilmaydi.

Naqshlarni qachon ishlatmaslik kerak? #

Ortiqcha muhandislik
PHP
// Ikki qatorlik ish uchun uchta klass
interface SalomStrategiyasi { public function salom(): string; }
class OzbekchaSalom implements SalomStrategiyasi {
    public function salom(): string { return 'Salom'; }
}
class SalomFabrikasi {
    public function yarating(string $til): SalomStrategiyasi { /* ... */ }
}

Bu shunchaki echo 'Salom'; bo'lishi kerak edi.

Qoida: naqshni muammo paydo bo'lganda qo'llang, oldindan emas.

Uch marta qoidasi
  1. Birinchi marta - shunchaki yozing
  2. Ikkinchi marta - takrorlanishni sezing, lekin hali chidang
  3. Uchinchi marta - endi abstraksiya chiqaring

Erta abstraksiya ko'pincha noto'g'ri abstraksiya bo'ladi.

Amaliy tanlov jadvali #

MuammoNaqsh
Bir vazifaning bir necha usuliStrategiya
Murakkab obyekt yaratishFabrika, Quruvchi
Mos kelmaydigan interfeysAdapter
Xatti-harakat qo'shishDekorator
O'zgarish haqida xabar berishKuzatuvchi
Ma'lumot saqlashni yashirishRepozitoriy
Murakkab tizimni soddalashtirishFasad
Amalni obyektga aylantirishBuyruq
Algoritm skeleti bir xil, qadamlar farqShablon metod
Obyekt xatti-harakati holatga bog'liqHolat
Amaliy topshiriq
  1. if/elseif zanjirini toping va uni Strategiya bilan qayta yozing.
  2. Uchta to'lov usuli uchun Strategiya interfeysini yozing.
  3. Ular uchun Fabrika qo'shing.
  4. Ko'p parametrli konstruktorni Quruvchi bilan almashtiring.
  5. Tashqi kutubxona uchun Adapter yozing.
  6. Kesh va jurnal Dekoratorlarini yozing.
  7. Hodisa dispetcheri va uchta tinglovchi yozing.
  8. Repozitoriy interfeysi va uning ikkita amalga oshirishini yozing.
  9. Loyihangizdagi Singleton ni konstruktor injeksiyasiga o'tkazing.
  10. Ortiqcha ishlatilgan naqshni toping va soddalashtiring.

Xulosa #

  • Naqsh - tez-tez uchraydigan muammoning sinovdan o'tgan yechimi.
  • Uch guruh: yaratuvchi, tuzilmaviy, xulq-atvor.
  • Strategiya - shartlar zanjirini almashtiradi, yangi variant qo'shishni osonlashtiradi.
  • Fabrika murakkab yaratish mantiqini bir joyga jamlaydi.
  • Quruvchi ko'p ixtiyoriy parametrli obyektlar uchun.
  • Adapter - tashqi kutubxonalar bilan ishlashda deyarli har doim foydali.
  • Dekorator asosiy klassni o'zgartirmasdan imkoniyat qo'shadi.
  • Kuzatuvchi bog'liqlikni kamaytiradi, lekin oqimni yashiradi.
  • Repozitoriy testlashni sezilarli osonlashtiradi.
  • Singleton dan qoching - konstruktor injeksiyasini ishlating.
  • Naqshni muammo paydo bo'lganda qo'llang; uch marta qoidasiga amal qiling.

Keyingi bo'limda SOLID prinsiplarini o'rganamiz.

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.