7-bo‘lim
Loyihalash naqshlari
Yaratuvchi, tuzilmaviy va xulq-atvor naqshlari - amaliy misollar bilan eng ko'p ishlatiladiganlari.
Ushbu bo‘lim mundarijasi
Naqsh - tez-tez uchraydigan muammoning sinovdan o'tgan yechimi. U tayyor kod emas, balki yondashuv sxemasi.
Naqshlar nima uchun kerak? #
- Sinovdan o'tgan yechim - g'ildirakni qaytadan ixtiro qilmaysiz
- Umumiy til - "bu yerda Strategiya ishlatamiz" degan gap butun jamoaga tushunarli
- Kengaytirish - naqshlar odatda o'zgarishga moslashuvchan
Kamchiligi: ularni haddan tashqari ishlatish. Har bir masalaga naqsh qidirish - keraksiz murakkablik keltiradi.
Strategiya #
Muammo: bir vazifani bajarishning bir necha usuli bor va ular almashtirilishi kerak.
// 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.
// 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);
}
}
$buyurtma = new Buyurtma(new EkspressYetkazish(), 3.5, 12);
echo $buyurtma->yetkazishNarxi();
49850
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.
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);
}
}
$fabrika = new YetkazishFabrikasi();
$strategiya = $fabrika->yarating($sorov->get('yetkazish_usuli'));
$buyurtma = new Buyurtma($strategiya, $vazn, $masofa);
- 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.
// 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.
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,
);
}
}
$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.
// 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'],
);
}
}
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.
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;
}
}
// Qatlamlarni istalgancha birlashtiramiz
$yuboruvchi = new JurnalDekoratori(
new ChegaraDekoratori(
new QaytaUrinishDekoratori(
new EmailYuboruvchi()
),
$nazoratchi
),
$jurnal
);
$yuboruvchi->yuboring('[email protected]', 'Buyurtmangiz tayyor');
Kuzatuvchi (Observer) #
Muammo: bir obyektdagi o'zgarish haqida boshqalarga xabar berish kerak.
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
}
}
$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));
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.
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;
}
// ...
}
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.
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;
}
}
| Muammo | Izoh |
|---|---|
| Global holat | Kod qayerdan o'zgarganini bilib bo'lmaydi |
| Testlash qiyin | Testlar orasida holat saqlanib qoladi |
| Yashirin bog'liqlik | Konstruktorda ko'rinmaydi |
| Parallel ishda muammo | Ko'p oqimli muhitda xavfli |
Muqobil: bog'liqlikni konstruktor orqali uzating:
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.
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? #
// 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.
- Birinchi marta - shunchaki yozing
- Ikkinchi marta - takrorlanishni sezing, lekin hali chidang
- Uchinchi marta - endi abstraksiya chiqaring
Erta abstraksiya ko'pincha noto'g'ri abstraksiya bo'ladi.
Amaliy tanlov jadvali #
| Muammo | Naqsh |
|---|---|
| Bir vazifaning bir necha usuli | Strategiya |
| Murakkab obyekt yaratish | Fabrika, Quruvchi |
| Mos kelmaydigan interfeys | Adapter |
| Xatti-harakat qo'shish | Dekorator |
| O'zgarish haqida xabar berish | Kuzatuvchi |
| Ma'lumot saqlashni yashirish | Repozitoriy |
| Murakkab tizimni soddalashtirish | Fasad |
| Amalni obyektga aylantirish | Buyruq |
| Algoritm skeleti bir xil, qadamlar farq | Shablon metod |
| Obyekt xatti-harakati holatga bog'liq | Holat |
if/elseifzanjirini toping va uni Strategiya bilan qayta yozing.- Uchta to'lov usuli uchun Strategiya interfeysini yozing.
- Ular uchun Fabrika qo'shing.
- Ko'p parametrli konstruktorni Quruvchi bilan almashtiring.
- Tashqi kutubxona uchun Adapter yozing.
- Kesh va jurnal Dekoratorlarini yozing.
- Hodisa dispetcheri va uchta tinglovchi yozing.
- Repozitoriy interfeysi va uning ikkita amalga oshirishini yozing.
- Loyihangizdagi Singleton ni konstruktor injeksiyasiga o'tkazing.
- 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.
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.