20-bo‘lim

Amaliy loyiha - to'lov tizimi

Sakkizta naqshni bitta ishlaydigan tizimda birlashtirish - va qaysilarini ishlatmaslik.

🕑 18 daqiqa o‘qish 📄 704 so‘z 👁 2 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. 1-qism: domen
  2. 2-qism: Builder - savatni yig'ish
  3. 3-qism: tekshiruvlar zanjiri
  4. 4-qism: to'lov strategiyalari va dekoratorlar
  5. 5-qism: holat mashinasi
  6. 6-qism: hodisalar
  7. 7-qism: fasad - hammasini birlashtirish
  8. Nima uchun aynan shu naqshlar
  9. Tizimni kengaytirish
  10. Tizim haqida raqamlar
  11. Xulosa

Yakuniy bo'limda internet-do'kon uchun buyurtma qabul qilish tizimini quramiz. Har naqsh aniq bir muammoni yechish uchun kiritiladi - shakl uchun emas.

Tayyor tizimda quyidagi naqshlar bo'ladi:

NaqshQaysi muammoni yechadi
BuilderSavatni bosqichma-bosqich yig'ish
Chain of ResponsibilityTekshiruvlar ketma-ketligi
StrategyTo'lov usullari
Factory MethodUsulni sozlamadan yaratish
DecoratorJurnal va qayta urinish
StateBuyurtma bosqichlari
ObserverBuyurtmadan keyingi yon ishlar
FacadeBitta kirish nuqtasi

1-qism: domen #

PHP
final readonly class Pul
{
    private function __construct(public int $tiyin) {}

    public static function somdan(int $som): self
    {
        return new self($som * 100);
    }

    public static function nol(): self
    {
        return new self(0);
    }

    public function qosh(self $b): self
    {
        return new self($this->tiyin + $b->tiyin);
    }

    public function kopaytir(int $n): self
    {
        return new self($this->tiyin * $n);
    }

    public function foizAyir(int $foiz): self
    {
        return new self($this->tiyin - intdiv($this->tiyin * $foiz, 100));
    }

    public function som(): int
    {
        return intdiv($this->tiyin, 100);
    }

    public function __toString(): string
    {
        return number_format($this->som(), 0, '.', ' ') . " so'm";
    }
}

final readonly class Qator
{
    public function __construct(
        public string $nom,
        public Pul $narx,
        public int $soni,
        public int $ogirlik,
    ) {}

    public function jami(): Pul
    {
        return $this->narx->kopaytir($this->soni);
    }
}
PHP
$q = new Qator('Klaviatura', Pul::somdan(250000), 2, 1);
echo $q->nom, ': ', $q->jami(), PHP_EOL;
echo 'chegirma bilan: ', $q->jami()->foizAyir(15), PHP_EOL;
Natija
Klaviatura: 500 000 so'm
chegirma bilan: 425 000 so'm

Pul - nomlangan konstruktorlar (3-bo'lim) va readonly bilan yozilgan qiymat obyekti. U hech qachon o'zgarmaydi, har amal yangi obyekt qaytaradi.

2-qism: Builder - savatni yig'ish #

PHP
final class SavatQuruvchi
{
    /** @var list<Qator> */
    private array $qatorlar = [];
    private int $chegirma = 0;
    private string $mijoz = '';
    private int $masofa = 0;

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

    public function qosh(string $nom, int $narx, int $soni, int $ogirlik): self
    {
        $this->qatorlar[] = new Qator($nom, Pul::somdan($narx), $soni, $ogirlik);
        return $this;
    }

    public function chegirma(int $foiz): self
    {
        $this->chegirma = $foiz;
        return $this;
    }

    public function masofa(int $km): self
    {
        $this->masofa = $km;
        return $this;
    }

    public function qur(): Savat
    {
        if ($this->mijoz === '') {
            throw new LogicException('mijoz korsatilmagan');
        }

        return new Savat($this->mijoz, $this->qatorlar,
            $this->chegirma, $this->masofa);
    }
}

final readonly class Savat
{
    /** @param list<Qator> $qatorlar */
    public function __construct(
        public string $mijoz,
        public array $qatorlar,
        public int $chegirma,
        public int $masofa,
    ) {}

    public function mahsulotJami(): Pul
    {
        $jami = Pul::nol();
        foreach ($this->qatorlar as $q) {
            $jami = $jami->qosh($q->jami());
        }
        return $jami->foizAyir($this->chegirma);
    }

    public function ogirlik(): int
    {
        return array_sum(array_map(
            static fn(Qator $q): int => $q->ogirlik * $q->soni, $this->qatorlar));
    }

    public function soni(): int
    {
        return count($this->qatorlar);
    }
}

function savatQur(): Savat
{
    return (new SavatQuruvchi())
        ->mijoz('Husanboy Qodirov')
        ->qosh('Klaviatura', 250000, 2, 1)
        ->qosh('Monitor', 1800000, 1, 8)
        ->chegirma(10)
        ->masofa(30)
        ->qur();
}
PHP
$savat = savatQur();

printf("mijoz:     %s%s", $savat->mijoz, PHP_EOL);
printf("qatorlar:  %d%s", $savat->soni(), PHP_EOL);
printf("ogirlik:   %d kg%s", $savat->ogirlik(), PHP_EOL);
printf("jami:      %s (chegirma %d%%)%s",
    $savat->mahsulotJami(), $savat->chegirma, PHP_EOL);

try {
    (new SavatQuruvchi())->qosh('Kabel', 50000, 1, 1)->qur();
} catch (LogicException $x) {
    echo 'RAD: ', $x->getMessage(), PHP_EOL;
}
Natija
mijoz:     Husanboy Qodirov
qatorlar:  2
ogirlik:   10 kg
jami:      2 070 000 so'm (chegirma 10%)
RAD: mijoz korsatilmagan

3-qism: tekshiruvlar zanjiri #

PHP
final readonly class Natija
{
    private function __construct(
        public bool $ok,
        public string $xabar,
    ) {}

    public static function togri(): self
    {
        return new self(true, '');
    }

    public static function xato(string $xabar): self
    {
        return new self(false, $xabar);
    }
}

abstract class Tekshiruv
{
    private ?Tekshiruv $keyingi = null;

    public function keyin(Tekshiruv $t): Tekshiruv
    {
        $this->keyingi = $t;
        return $t;
    }

    final public function tekshir(Savat $s): Natija
    {
        $natija = $this->qoida($s);
        if (!$natija->ok) {
            return $natija;
        }

        return $this->keyingi?->tekshir($s) ?? Natija::togri();
    }

    abstract protected function qoida(Savat $s): Natija;
}

final class BoshEmas extends Tekshiruv
{
    protected function qoida(Savat $s): Natija
    {
        return $s->soni() > 0
            ? Natija::togri()
            : Natija::xato('savat bosh');
    }
}

final class OgirlikChegarasi extends Tekshiruv
{
    public function __construct(private readonly int $eng = 30) {}

    protected function qoida(Savat $s): Natija
    {
        return $s->ogirlik() <= $this->eng
            ? Natija::togri()
            : Natija::xato("ogirlik {$s->ogirlik()} kg, chegara {$this->eng} kg");
    }
}

final class ChegirmaChegarasi extends Tekshiruv
{
    protected function qoida(Savat $s): Natija
    {
        return $s->chegirma >= 0 && $s->chegirma <= 50
            ? Natija::togri()
            : Natija::xato("chegirma {$s->chegirma}% - ruxsat etilmagan");
    }
}

function tekshiruvZanjiri(): Tekshiruv
{
    $birinchi = new BoshEmas();
    $birinchi->keyin(new OgirlikChegarasi())->keyin(new ChegirmaChegarasi());

    return $birinchi;
}
PHP
$zanjir = tekshiruvZanjiri();

$holatlar = [
    'togri savat' => savatQur(),
    'bosh savat' => (new SavatQuruvchi())->mijoz('Malika')->qur(),
    'ogir savat' => (new SavatQuruvchi())->mijoz('Nodira')
        ->qosh('Seyf', 4000000, 1, 120)->qur(),
    'katta chegirma' => (new SavatQuruvchi())->mijoz('Aziza')
        ->qosh('Kabel', 50000, 1, 1)->chegirma(90)->qur(),
];

foreach ($holatlar as $nom => $savat) {
    $n = $zanjir->tekshir($savat);
    printf('%-16s %s%s', $nom,
        $n->ok ? 'OK' : 'RAD: ' . $n->xabar, PHP_EOL);
}
Natija
togri savat      OK
bosh savat       RAD: savat bosh
ogir savat       RAD: ogirlik 120 kg, chegara 30 kg
katta chegirma   RAD: chegirma 90% - ruxsat etilmagan

4-qism: to'lov strategiyalari va dekoratorlar #

PHP
interface Toolov
{
    public function nomi(): string;
    public function yech(Pul $summa, string $mijoz): Natija;
}

final class Karta implements Toolov
{
    public function nomi(): string { return 'karta'; }

    public function yech(Pul $summa, string $mijoz): Natija
    {
        return Natija::togri();
    }
}

// Ataylab nosoz - qayta urinishni ko'rsatish uchun
final class Payme implements Toolov
{
    private int $urinish = 0;

    public function __construct(private readonly int $nechtaXato = 0) {}

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

    public function yech(Pul $summa, string $mijoz): Natija
    {
        $this->urinish++;

        return $this->urinish <= $this->nechtaXato
            ? Natija::xato("payme javob bermadi ({$this->urinish}-urinish)")
            : Natija::togri();
    }
}

final class NaqdPul implements Toolov
{
    public function nomi(): string { return 'naqd'; }

    public function yech(Pul $summa, string $mijoz): Natija
    {
        return $summa->som() <= 5000000
            ? Natija::togri()
            : Natija::xato('naqd chegara: 5 000 000 som');
    }
}

// DEKORATOR: jurnal
final class JurnalliToolov implements Toolov
{
    /** @var list<string> */
    private array $qatorlar = [];

    public function __construct(private readonly Toolov $ichki) {}

    public function nomi(): string { return $this->ichki->nomi(); }

    public function yech(Pul $summa, string $mijoz): Natija
    {
        $n = $this->ichki->yech($summa, $mijoz);
        $this->qatorlar[] = sprintf('%s %s %s',
            $this->ichki->nomi(), $summa, $n->ok ? 'OK' : 'XATO');

        return $n;
    }

    /** @return list<string> */
    public function jurnal(): array { return $this->qatorlar; }
}

// DEKORATOR: qayta urinish
final class QaytaUrinuvchi implements Toolov
{
    public function __construct(
        private readonly Toolov $ichki,
        private readonly int $marta = 3,
    ) {}

    public function nomi(): string { return $this->ichki->nomi(); }

    public function yech(Pul $summa, string $mijoz): Natija
    {
        $oxirgi = Natija::xato('urinish qilinmadi');

        for ($i = 1; $i <= $this->marta; $i++) {
            $oxirgi = $this->ichki->yech($summa, $mijoz);
            if ($oxirgi->ok) {
                return $oxirgi;
            }
        }

        return $oxirgi;
    }
}

// FABRIKA: sozlamadan usul yaratish
enum ToolovTuri: string
{
    case Karta = 'karta';
    case Payme = 'payme';
    case Naqd = 'naqd';

    public function yarat(): Toolov
    {
        return match ($this) {
            ToolovTuri::Karta => new Karta(),
            ToolovTuri::Payme => new Payme(2),
            ToolovTuri::Naqd => new NaqdPul(),
        };
    }
}
PHP
foreach (ToolovTuri::cases() as $tur) {
    $t = new JurnalliToolov($tur->yarat());
    $n = $t->yech(Pul::somdan(2070000), 'Husanboy');

    printf('%-7s %s%s', $tur->value,
        $n->ok ? 'OK' : 'XATO: ' . $n->xabar, PHP_EOL);
    foreach ($t->jurnal() as $j) {
        echo '    ', $j, PHP_EOL;
    }
}
Natija
karta   OK
    karta 2 070 000 so'm OK
payme   XATO: payme javob bermadi (1-urinish)
    payme 2 070 000 so'm XATO
naqd    OK
    naqd 2 070 000 so'm OK

payme birinchi urinishda yiqildi. Endi uni qayta urinish dekoratori bilan o'raymiz:

PHP
$xom = ToolovTuri::Payme->yarat();
$jurnalli = new JurnalliToolov($xom);
$toolov = new QaytaUrinuvchi($jurnalli, 4);

$n = $toolov->yech(Pul::somdan(2070000), 'Husanboy');

printf('natija: %s%s', $n->ok ? 'OK' : 'XATO', PHP_EOL);
echo '--- jurnal', PHP_EOL;
foreach ($jurnalli->jurnal() as $i => $j) {
    printf('  %d. %s%s', $i + 1, $j, PHP_EOL);
}
Natija
natija: OK
--- jurnal
  1. payme 2 070 000 so'm XATO
  2. payme 2 070 000 so'm XATO
  3. payme 2 070 000 so'm OK
Qatlamlar tartibi bu yerda ham muhim

QaytaUrinuvchi(JurnalliToolov(Payme)) - jurnal ichkarida turibdi, shuning uchun har urinish yozildi.

Agar teskari qo'ysak - JurnalliToolov(QaytaUrinuvchi(Payme))

  • jurnalda faqat yakuniy natija bo'lardi.

9-bo'limda ko'rganimizdek, ikkalasi ham to'g'ri bo'lishi mumkin. Bu yerda har urinishni ko'rish kerak edi, shuning uchun jurnal ichkariga qo'yildi.

5-qism: holat mashinasi #

PHP
enum Bosqich: string
{
    case Yangi = 'yangi';
    case Tolangan = 'tolangan';
    case Jonatilgan = 'jonatilgan';
    case Bekor = 'bekor';

    /** @return list<self> */
    public function keyingilari(): array
    {
        return match ($this) {
            Bosqich::Yangi => [Bosqich::Tolangan, Bosqich::Bekor],
            Bosqich::Tolangan => [Bosqich::Jonatilgan, Bosqich::Bekor],
            Bosqich::Jonatilgan, Bosqich::Bekor => [],
        };
    }

    public function otishMumkinmi(self $keyingi): bool
    {
        return in_array($keyingi, $this->keyingilari(), true);
    }
}

final class Buyurtma
{
    private Bosqich $bosqich = Bosqich::Yangi;
    /** @var list<string> */
    private array $tarix = [];

    public function __construct(
        public readonly string $raqam,
        public readonly Savat $savat,
        public readonly Pul $jami,
    ) {}

    public function otkaz(Bosqich $yangi): Natija
    {
        if (!$this->bosqich->otishMumkinmi($yangi)) {
            return Natija::xato(
                "{$this->bosqich->value} -> {$yangi->value} mumkin emas");
        }

        $this->tarix[] = $this->bosqich->value . ' -> ' . $yangi->value;
        $this->bosqich = $yangi;

        return Natija::togri();
    }

    public function bosqich(): Bosqich { return $this->bosqich; }

    /** @return list<string> */
    public function tarix(): array { return $this->tarix; }
}
PHP
$b = new Buyurtma('BUY-1001', savatQur(), Pul::somdan(2070000));

foreach ([Bosqich::Tolangan, Bosqich::Jonatilgan, Bosqich::Bekor] as $keyingi) {
    $n = $b->otkaz($keyingi);
    printf('-> %-11s %s%s', $keyingi->value,
        $n->ok ? 'OK' : 'RAD: ' . $n->xabar, PHP_EOL);
}

echo 'tarix: ', implode(' | ', $b->tarix()), PHP_EOL;
Natija
-> tolangan    OK
-> jonatilgan  OK
-> bekor       RAD: jonatilgan -> bekor mumkin emas
tarix: yangi -> tolangan | tolangan -> jonatilgan

6-qism: hodisalar #

PHP
final readonly class BuyurtmaTolandi
{
    public function __construct(public Buyurtma $buyurtma) {}
}

final class Tarqatuvchi
{
    /** @var array<string, list<callable>> */
    private array $tinglovchilar = [];
    /** @var list<string> */
    private array $xatolar = [];

    public function tingla(string $hodisa, callable $t): self
    {
        $this->tinglovchilar[$hodisa][] = $t;
        return $this;
    }

    public function tarqat(object $hodisa): void
    {
        foreach ($this->tinglovchilar[$hodisa::class] ?? [] as $i => $t) {
            try {
                $t($hodisa);
            } catch (Throwable $x) {
                // bitta tinglovchi yiqilsa - qolganlari davom etadi
                $this->xatolar[] = sprintf('%s#%d: %s',
                    $hodisa::class, $i, $x->getMessage());
            }
        }
    }

    /** @return list<string> */
    public function xatolar(): array { return $this->xatolar; }
}

function tarqatuvchiQur(): Tarqatuvchi
{
    return (new Tarqatuvchi())
        ->tingla(BuyurtmaTolandi::class, static function (BuyurtmaTolandi $h): void {
            echo "    [email] {$h->buyurtma->savat->mijoz}: "
               . "{$h->buyurtma->raqam} qabul qilindi", PHP_EOL;
        })
        ->tingla(BuyurtmaTolandi::class, static function (BuyurtmaTolandi $h): void {
            throw new RuntimeException('sodiqlik xizmati javob bermadi');
        })
        ->tingla(BuyurtmaTolandi::class, static function (BuyurtmaTolandi $h): void {
            echo "    [ombor] {$h->buyurtma->savat->soni()} "
               . 'qator ajratildi', PHP_EOL;
        });
}
PHP
$tarqatuvchi = tarqatuvchiQur();
$buyurtma = new Buyurtma('BUY-1002', savatQur(), Pul::somdan(2070000));

$tarqatuvchi->tarqat(new BuyurtmaTolandi($buyurtma));

echo '--- xatolar', PHP_EOL;
foreach ($tarqatuvchi->xatolar() as $x) {
    echo '  ', $x, PHP_EOL;
}
Natija
    [email] Husanboy Qodirov: BUY-1002 qabul qilindi
    [ombor] 2 qator ajratildi
--- xatolar
  BuyurtmaTolandi#1: sodiqlik xizmati javob bermadi

Sodiqlik xizmati yiqildi, lekin ombor baribir ishladi - 14-bo'limdagi qoida amalda.

7-qism: fasad - hammasini birlashtirish #

PHP
final readonly class BuyurtmaNatijasi
{
    public function __construct(
        public bool $ok,
        public string $xabar,
        public ?Buyurtma $buyurtma = null,
    ) {}
}

final class Kassa
{
    private int $hisoblagich = 1000;

    public function __construct(
        private readonly Tekshiruv $tekshiruv,
        private readonly Tarqatuvchi $tarqatuvchi,
    ) {}

    public function rasmiylashtir(Savat $savat, ToolovTuri $tur): BuyurtmaNatijasi
    {
        // 1. tekshiruvlar zanjiri
        $n = $this->tekshiruv->tekshir($savat);
        if (!$n->ok) {
            return new BuyurtmaNatijasi(false, 'Tekshiruv: ' . $n->xabar);
        }

        // 2. buyurtma yaratiladi
        $buyurtma = new Buyurtma(
            'BUY-' . ++$this->hisoblagich,
            $savat,
            $savat->mahsulotJami(),
        );

        // 3. to'lov - fabrika + dekoratorlar
        $toolov = new QaytaUrinuvchi($tur->yarat(), 4);
        $n = $toolov->yech($buyurtma->jami, $savat->mijoz);
        if (!$n->ok) {
            return new BuyurtmaNatijasi(false, 'Tolov: ' . $n->xabar, $buyurtma);
        }

        // 4. holat o'tishi
        $n = $buyurtma->otkaz(Bosqich::Tolangan);
        if (!$n->ok) {
            return new BuyurtmaNatijasi(false, 'Holat: ' . $n->xabar, $buyurtma);
        }

        // 5. yon ishlar - hodisa orqali
        $this->tarqatuvchi->tarqat(new BuyurtmaTolandi($buyurtma));

        return new BuyurtmaNatijasi(true, 'Qabul qilindi', $buyurtma);
    }
}

function kassaQur(): Kassa
{
    return new Kassa(tekshiruvZanjiri(), tarqatuvchiQur());
}
PHP
$kassa = kassaQur();

echo '=== 1. muvaffaqiyatli buyurtma', PHP_EOL;
$n = $kassa->rasmiylashtir(savatQur(), ToolovTuri::Karta);
printf('%s | %s | %s%s',
    $n->ok ? 'OK' : 'XATO', $n->xabar,
    $n->buyurtma?->raqam ?? '-', PHP_EOL);
printf('bosqich: %s%s', $n->buyurtma?->bosqich()->value ?? '-', PHP_EOL);
Natija
=== 1. muvaffaqiyatli buyurtma
    [email] Husanboy Qodirov: BUY-1001 qabul qilindi
    [ombor] 2 qator ajratildi
OK | Qabul qilindi | BUY-1001
bosqich: tolangan

Nosoz provayder bilan - qayta urinish ishlaydi:

PHP
$kassa = kassaQur();

echo '=== 2. payme (2 marta yiqiladi, 3-da otadi)', PHP_EOL;
$n = $kassa->rasmiylashtir(savatQur(), ToolovTuri::Payme);
printf('%s | %s%s', $n->ok ? 'OK' : 'XATO', $n->xabar, PHP_EOL);

echo '=== 3. ogir savat - tekshiruvda toxtaydi', PHP_EOL;
$ogir = (new SavatQuruvchi())->mijoz('Kamola')
    ->qosh('Seyf', 4000000, 1, 120)->qur();
$n = $kassa->rasmiylashtir($ogir, ToolovTuri::Karta);
printf('%s | %s%s', $n->ok ? 'OK' : 'XATO', $n->xabar, PHP_EOL);

echo '=== 4. naqd chegarasi', PHP_EOL;
$qimmat = (new SavatQuruvchi())->mijoz('Dilnoza Yusupova')
    ->qosh('Server', 9000000, 1, 20)->qur();
$n = $kassa->rasmiylashtir($qimmat, ToolovTuri::Naqd);
printf('%s | %s%s', $n->ok ? 'OK' : 'XATO', $n->xabar, PHP_EOL);
printf('buyurtma bosqichi: %s%s',
    $n->buyurtma?->bosqich()->value ?? '-', PHP_EOL);
Natija
=== 2. payme (2 marta yiqiladi, 3-da otadi)
    [email] Husanboy Qodirov: BUY-1001 qabul qilindi
    [ombor] 2 qator ajratildi
OK | Qabul qilindi
=== 3. ogir savat - tekshiruvda toxtaydi
XATO | Tekshiruv: ogirlik 120 kg, chegara 30 kg
=== 4. naqd chegarasi
XATO | Tolov: naqd chegara: 5 000 000 som
buyurtma bosqichi: yangi

To'rtinchi holatga e'tibor bering: to'lov o'tmagani uchun buyurtma yangi bosqichida qoldi va hech qanday hodisa tarqatilmadi.

Buyurtma oqimi va unda ishlaydigan naqshlar SavatQuruvchi Builder Tekshiruv Chain of Resp. ToolovTuri Factory + Strategy QaytaUrinuvchi(Jurnalli(...)) Decorator Bosqich: yangi -> tolangan State (enum + o'tishlar jadvali) BuyurtmaTolandi Observer (hodisa obyekti) email tinglovchi ombor tinglovchi Kassa::rasmiylashtir() Facade - butun oqim bitta metod ortida Chaqiruvchi kod faqat Kassa va ToolovTuri ni biladi - qolgan sakkiz sinf undan yashiringan.
Sakkizta naqsh, sakkizta aniq muammo

Nima uchun aynan shu naqshlar #

PHP
$qaror = [
    ['Builder', 'savat bosqichli quriladi', 'ha'],
    ['Chain of Resp.', 'tekshiruvlar soni ozgaradi', 'ha'],
    ['Strategy', '3 tolov usuli', 'ha'],
    ['Factory', 'usul sozlamadan keladi', 'ha'],
    ['Decorator', 'jurnal + qayta urinish', 'ha'],
    ['State', 'notogri otish taqiqlanadi', 'ha'],
    ['Observer', 'yon ishlar, soni ozgaradi', 'ha'],
    ['Facade', 'oqim 4 joyda chaqiriladi', 'ha'],
    ['Singleton', 'kerak emas - injeksiya bor', 'yoq'],
    ['Prototype', 'nusxa olish kerak emas', 'yoq'],
    ['Composite', 'daraxt yoq', 'yoq'],
    ['Proxy', 'kechiktirish kerak emas', 'yoq'],
];

printf("%-16s %-32s %s%s", 'Naqsh', 'Sabab', 'Ishlatildi', PHP_EOL);
echo str_repeat('-', 62), PHP_EOL;
foreach ($qaror as $q) {
    printf("%-16s %-32s %s%s", ...[...$q, PHP_EOL]);
}

$ishlatilgan = count(array_filter($qaror,
    static fn(array $q): bool => $q[2] === 'ha'));
printf('%sishlatildi: %d, ishlatilmadi: %d%s',
    PHP_EOL, $ishlatilgan, count($qaror) - $ishlatilgan, PHP_EOL);
Natija
Naqsh            Sabab                            Ishlatildi
--------------------------------------------------------------
Builder          savat bosqichli quriladi         ha
Chain of Resp.   tekshiruvlar soni ozgaradi       ha
Strategy         3 tolov usuli                    ha
Factory          usul sozlamadan keladi           ha
Decorator        jurnal + qayta urinish           ha
State            notogri otish taqiqlanadi        ha
Observer         yon ishlar, soni ozgaradi        ha
Facade           oqim 4 joyda chaqiriladi         ha
Singleton        kerak emas - injeksiya bor       yoq
Prototype        nusxa olish kerak emas           yoq
Composite        daraxt yoq                       yoq
Proxy            kechiktirish kerak emas          yoq

ishlatildi: 8, ishlatilmadi: 4
Ishlatilmagan naqshlar ham qaror

Yuqoridagi jadvalning pastki qismi - loyihaning eng muhim qismlaridan biri.

To'rtta naqsh ataylab ishlatilmadi, chunki ular yechadigan muammo bu tizimda yo'q:

NaqshQachon kerak bo'lardi
SingletonGlobal holat majburiy bo'lganda (deyarli hech qachon)
PrototypeBuyurtma andozalari qo'shilsa
CompositeMahsulot to'plamlari (komplekt ichida komplekt) bo'lsa
ProxyKatalogni kechiktirib yuklash kerak bo'lsa

19-bo'limdagi asosiy fikr shu edi: naqshni muammo chaqiradi, bilim emas.

Tizimni kengaytirish #

Yangi to'lov usuli qo'shamiz - hech qanday mavjud faylni ochmasdan bo'lmaydi, chunki enum da yangi case kerak. Lekin o'zgarish bitta joyda:

PHP
final class BolibTolash implements Toolov
{
    public function __construct(private readonly int $oylar = 6) {}

    public function nomi(): string { return "bolib-{$this->oylar}"; }

    public function yech(Pul $summa, string $mijoz): Natija
    {
        if ($summa->som() < 500000) {
            return Natija::xato('bolib tolash uchun eng kam summa 500 000');
        }
        $oylik = intdiv($summa->som(), $this->oylar);
        echo "    [bolib] oyiga {$oylik} som x {$this->oylar}", PHP_EOL;

        return Natija::togri();
    }
}

// Dekoratorlar, tekshiruvlar, holat mashinasi, hodisalar -
// hech biri o'zgarmadi
$t = new QaytaUrinuvchi(new BolibTolash(6), 2);
$n = $t->yech(Pul::somdan(2070000), 'Husanboy');
printf('%s: %s%s', $t->nomi(), $n->ok ? 'OK' : $n->xabar, PHP_EOL);

$n = $t->yech(Pul::somdan(200000), 'Malika');
printf('kichik summa: %s%s', $n->ok ? 'OK' : $n->xabar, PHP_EOL);
Natija
    [bolib] oyiga 345000 som x 6
bolib-6: OK
kichik summa: bolib tolash uchun eng kam summa 500 000

Yangi tekshiruv qoidasi ham xuddi shunday - faqat zanjirga qo'shiladi:

PHP
final class MijozIsmi extends Tekshiruv
{
    protected function qoida(Savat $s): Natija
    {
        return mb_strlen($s->mijoz) >= 3
            ? Natija::togri()
            : Natija::xato('mijoz ismi juda qisqa');
    }
}

$zanjir = new BoshEmas();
$zanjir->keyin(new MijozIsmi())
       ->keyin(new OgirlikChegarasi())
       ->keyin(new ChegirmaChegarasi());

$qisqa = (new SavatQuruvchi())->mijoz('Ali')
    ->qosh('Kabel', 50000, 1, 1)->qur();
$juda = (new SavatQuruvchi())->mijoz('Ab')
    ->qosh('Kabel', 50000, 1, 1)->qur();

printf('"Ali": %s%s', $zanjir->tekshir($qisqa)->ok ? 'OK' : 'RAD', PHP_EOL);
printf('"Ab":  %s%s',
    $zanjir->tekshir($juda)->ok ? 'OK' : 'RAD: '
        . $zanjir->tekshir($juda)->xabar, PHP_EOL);
Natija
"Ali": OK
"Ab":  RAD: mijoz ismi juda qisqa

Tizim haqida raqamlar #

PHP
$sinflar = [
    'domen' => ['Pul', 'Qator', 'Savat', 'Buyurtma', 'Natija'],
    'quruvchi' => ['SavatQuruvchi'],
    'tekshiruv' => ['Tekshiruv', 'BoshEmas', 'OgirlikChegarasi',
                    'ChegirmaChegarasi'],
    'tolov' => ['Toolov', 'Karta', 'Payme', 'NaqdPul', 'ToolovTuri'],
    'dekorator' => ['JurnalliToolov', 'QaytaUrinuvchi'],
    'holat' => ['Bosqich'],
    'hodisa' => ['BuyurtmaTolandi', 'Tarqatuvchi'],
    'fasad' => ['Kassa', 'BuyurtmaNatijasi'],
];

foreach ($sinflar as $guruh => $royxat) {
    printf('%-11s %2d: %s%s', $guruh, count($royxat),
        implode(', ', array_slice($royxat, 0, 3))
            . (count($royxat) > 3 ? '...' : ''), PHP_EOL);
}

printf('%sjami %d ta tur%s', PHP_EOL,
    array_sum(array_map('count', $sinflar)), PHP_EOL);
echo 'chaqiruvchi kod biladigan turlar: Kassa, ToolovTuri, ',
    'SavatQuruvchi = 3', PHP_EOL;
Natija
domen        5: Pul, Qator, Savat...
quruvchi     1: SavatQuruvchi
tekshiruv    4: Tekshiruv, BoshEmas, OgirlikChegarasi...
tolov        5: Toolov, Karta, Payme...
dekorator    2: JurnalliToolov, QaytaUrinuvchi
holat        1: Bosqich
hodisa       2: BuyurtmaTolandi, Tarqatuvchi
fasad        2: Kassa, BuyurtmaNatijasi

jami 22 ta tur
chaqiruvchi kod biladigan turlar: Kassa, ToolovTuri, SavatQuruvchi = 3
22 ta tur - bu ko'pmi?

Halol javob: bog'liq.

Agar bu do'kon bir yil ishlab, uchta to'lov usuli va to'rtta tekshiruvi bo'lsa - bu me'yorda.

Agar u bir martalik skript bo'lsa va hech qachon kengaytirilmasa - 22 tur o'rniga 100 qatorlik bitta fayl yetarli edi.

Muhim ko'rsatkich - chaqiruvchi kod nechta turni biladi. Bizda bu uchta: SavatQuruvchi, Kassa, ToolovTuri.

Qolgan 19 turi ichkarida va ular o'zgarganda tashqi kod tegilmaydi. Naqshlarning asosiy va'dasi shu edi.

Yakuniy loyiha
  1. Ushbu tizimni noldan yozing (kod ko'chirmang, qayta yozing).
  2. Yangi to'lov usuli qo'shing - nechta mavjud fayl o'zgardi?
  3. Yangi tekshiruv qoidasi qo'shing.
  4. Tekshiruvlar tartibini almashtiring va natija o'zgarishini ko'ring.
  5. Jurnal dekoratorini tashqariga chiqaring - jurnal qanday o'zgardi?
  6. Holat mashinasiga Yetkazilgan bosqichini qo'shing.
  7. Yangi hodisa turi qo'shing (BuyurtmaBekorQilindi).
  8. Bitta tinglovchida istisno tashlang - qolganlari ishladimi?
  9. Har naqsh uchun "nima uchun kerak" degan bir jumla yozing.
  10. Ishlatmagan to'rtta naqshdan birini qo'shishga sabab toping - topolmasangiz qo'shmang.

Xulosa #

  • Bu loyihada sakkizta naqsh ishlatildi, har biri aniq bir muammoni yechish uchun.
  • Builder savatni bosqichli qurdi va qur() da tekshirdi.
  • Chain of Responsibility tekshiruvlarni mustaqil sinflarga ajratdi.
  • Strategy va Factory to'lov usullarini sozlamadan tanlash imkonini berdi.
  • Decorator jurnal va qayta urinishni asosiy kodga tegmasdan qo'shdi.
  • State noto'g'ri bosqich o'tishlarini taqiqladi.
  • Observer yon ishlarni ajratdi va bitta tinglovchining yiqilishi qolganlarini to'xtatmadi.
  • Facade butun oqimni bitta metod ortiga yashirdi.
  • To'rtta naqsh ataylab ishlatilmadi - ularning muammosi bu tizimda yo'q edi.
  • Asosiy o'lchov: 22 turdan chaqiruvchi kod faqat uchtasini biladi.

Darslik shu bilan yakunlandi. Endi eng muhim maslahat: kodni naqshlarga moslashtirmang - naqshlarni kodingizdagi haqiqiy muammolarga moslashtiring.

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.