13-bo‘lim

Strategy

Algoritmni ish vaqtida almashtirish - interfeys, yopilma yoki enum bilan.

🕑 13 daqiqa o‘qish 📄 777 so‘z 👁 1 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Muammo: shartlar ichida yashiringan algoritmlar
  2. Yechim: har algoritm - alohida sinf
  3. Kontekst - strategiyani ishlatuvchi sinf
  4. Strategiya yopilma sifatida
  5. Strategiya enum sifatida
  6. Strategiyalar registri
  7. Qachon Strategy kerak emas
  8. Strategy va Template Method
  9. Xulosa

Strategy - bir vazifani bajarishning bir necha usulini alohida sinflarga ajratib, ularni ish vaqtida almashtirish naqshi.

1-bo'limda uni qisqacha ko'rgan edik. Endi batafsil.

Muammo: shartlar ichida yashiringan algoritmlar #

Yetkazib berish narxini hisoblaymiz:

PHP
function yetkazishNarxi(string $usul, int $ogirlik, int $masofa): int
{
    if ($usul === 'pochta') {
        return 15000 + $ogirlik * 500;
    } elseif ($usul === 'kuryer') {
        $narx = 25000 + $masofa * 800;
        return $ogirlik > 10 ? $narx + 20000 : $narx;
    } elseif ($usul === 'olib_ketish') {
        return 0;
    } elseif ($usul === 'tezkor') {
        return (25000 + $masofa * 800) * 2;
    }
    throw new InvalidArgumentException("Nomalum usul: {$usul}");
}

foreach (['pochta', 'kuryer', 'olib_ketish', 'tezkor'] as $u) {
    printf('%-12s %8d som%s', $u, yetkazishNarxi($u, 12, 30), PHP_EOL);
}
Natija
pochta          21000 som
kuryer          69000 som
olib_ketish         0 som
tezkor          98000 som
To'rtta algoritm bitta funksiyada
MuammoIzoh
Alohida sinash qiyinHar testda $usul satrini uzatish kerak
Bir-biriga ta'sirtezkor kuryer formulasini takrorlaydi
Yangi usul = shu faylni ochishOchiq/yopiq buziladi
Parametrlar birlashib ketganolib_ketish ga $masofa kerak emas

Oxirgisi nozik belgi: funksiya hamma algoritm uchun hamma parametrni qabul qiladi. Bu ular haqiqatda turli narsa ekanining alomati.

Yechim: har algoritm - alohida sinf #

PHP
interface YetkazishUsuli
{
    public function nomi(): string;
    public function narx(int $ogirlik, int $masofa): int;
    public function muddat(): string;
}

final class Pochta implements YetkazishUsuli
{
    public function nomi(): string { return 'Pochta'; }

    public function narx(int $ogirlik, int $masofa): int
    {
        return 15000 + $ogirlik * 500;
    }

    public function muddat(): string { return '5-7 kun'; }
}

final class Kuryer implements YetkazishUsuli
{
    public function __construct(private readonly int $ogirChegara = 10) {}

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

    public function narx(int $ogirlik, int $masofa): int
    {
        $narx = 25000 + $masofa * 800;
        return $ogirlik > $this->ogirChegara ? $narx + 20000 : $narx;
    }

    public function muddat(): string { return '1-2 kun'; }
}

final class OlibKetish implements YetkazishUsuli
{
    public function nomi(): string { return 'Olib ketish'; }

    public function narx(int $ogirlik, int $masofa): int { return 0; }

    public function muddat(): string { return 'bugun'; }
}

// Tezkor - kuryerni qayta ishlatadi
final class Tezkor implements YetkazishUsuli
{
    public function __construct(private readonly YetkazishUsuli $asos) {}

    public function nomi(): string { return 'Tezkor ' . $this->asos->nomi(); }

    public function narx(int $ogirlik, int $masofa): int
    {
        return $this->asos->narx($ogirlik, $masofa) * 2;
    }

    public function muddat(): string { return '3 soat'; }
}
PHP
/** @var list<YetkazishUsuli> $usullar */
$usullar = [
    new Pochta(),
    new Kuryer(),
    new OlibKetish(),
    new Tezkor(new Kuryer()),
];

printf("%-16s %10s  %s%s", 'Usul', 'Narx', 'Muddat', PHP_EOL);
echo str_repeat('-', 40), PHP_EOL;

foreach ($usullar as $u) {
    printf("%-16s %8d som  %s%s",
        $u->nomi(), $u->narx(12, 30), $u->muddat(), PHP_EOL);
}
Natija
Usul                   Narx  Muddat
----------------------------------------
Pochta              21000 som  5-7 kun
Kuryer              69000 som  1-2 kun
Olib ketish             0 som  bugun
Tezkor Kuryer      138000 som  3 soat
Natija o'zgardi - va bu eski koddagi xatoni ochdi

if zanjirida tezkor uchun 98 000 so'm chiqqan edi, bu yerda esa 138 000.

Sababi eski kodda:

KOD
} elseif ($usul === 'tezkor') {
    return (25000 + $masofa * 800) * 2;
}

Bu qator kuryer formulasini ko'chirib olgan, lekin 10 kg dan og'ir yuk uchun qo'shiladigan 20 000 so'mni tashlab ketgan. 12 kg lik yuk uchun narx kam hisoblangan.

Yangi kodda Tezkor Kuryer ni qayta ishlatadi, shuning uchun bunday farq bo'lishi mumkin emas.

Takrorlangan formula ertami-kechmi ajralib ketadi. Naqshning asosiy foydasi ko'pincha shu - takrorlanishni yo'qotish.

Tezkor - strategiya ichida strategiya

Tezkor boshqa usulni ichiga oladi va narxini ikkilantiradi. Bu 9-bo'limdagi Decorator ning aynan o'zi.

Naqshlar shunday birga ishlaydi: Tezkor bir vaqtning o'zida strategiya ham (chunki YetkazishUsuli ni bajaradi), dekorator ham (chunki boshqa usulni o'raydi).

Bu shuni ko'rsatadi: naqsh nomi kod tuzilmasidan emas, uning maqsadidan kelib chiqadi.

Kontekst - strategiyani ishlatuvchi sinf #

PHP
final class Savat
{
    /** @var list<array{nom: string, narx: int, ogirlik: int}> */
    private array $mahsulotlar = [];

    public function __construct(
        private YetkazishUsuli $yetkazish,
        private readonly int $masofa,
    ) {}

    public function qosh(string $nom, int $narx, int $ogirlik): self
    {
        $this->mahsulotlar[] = compact('nom', 'narx', 'ogirlik');
        return $this;
    }

    // strategiyani ish vaqtida almashtirish
    public function yetkazishniQoy(YetkazishUsuli $u): void
    {
        $this->yetkazish = $u;
    }

    public function hisob(): string
    {
        $mahsulot = array_sum(array_column($this->mahsulotlar, 'narx'));
        $ogirlik = array_sum(array_column($this->mahsulotlar, 'ogirlik'));
        $yetkazish = $this->yetkazish->narx($ogirlik, $this->masofa);

        return sprintf(
            "mahsulot: %d som | ogirlik: %d kg%s%-16s %7d som%sJAMI: %d som",
            $mahsulot, $ogirlik, PHP_EOL,
            $this->yetkazish->nomi(), $yetkazish, PHP_EOL,
            $mahsulot + $yetkazish,
        );
    }
}
PHP
$savat = (new Savat(new Pochta(), 30))
    ->qosh('Klaviatura', 250000, 1)
    ->qosh('Monitor', 1800000, 8);

echo $savat->hisob(), PHP_EOL;

echo '--- foydalanuvchi kuryerni tanladi', PHP_EOL;
$savat->yetkazishniQoy(new Kuryer());
echo $savat->hisob(), PHP_EOL;
Natija
mahsulot: 2050000 som | ogirlik: 9 kg
Pochta             19500 som
JAMI: 2069500 som
--- foydalanuvchi kuryerni tanladi
mahsulot: 2050000 som | ogirlik: 9 kg
Kuryer             49000 som
JAMI: 2099000 som

Savat qaysi usul ishlayotganini bilmaydi. U faqat interfeysni chaqiradi.

Strategy - algoritm tashqaridan beriladi Savat (kontekst) algoritmni BILMAYDI narx() YetkazishUsuli nomi() · narx() · muddat() (interfeys) Pochta Kuryer OlibKetish Kim tanlaydi Foydalanuvchi formada Sozlama faylida Biznes qoidasi Savat emas! PHP da uch xil yozish mumkin 1. interface + sinflar - holat, sozlama va nom kerak bo'lganda 2. callable / closure - algoritm bir qatorlik bo'lganda 3. enum + match - variantlar ro'yxati barqaror bo'lganda
Kontekst algoritmni bilmaydi - u faqat interfeysni chaqiradi

Strategiya yopilma sifatida #

Algoritm sodda bo'lsa, alohida sinf ortiqcha:

PHP
final class Royxat
{
    /** @param list<array{nom: string, narx: int}> $qatorlar */
    public function __construct(private readonly array $qatorlar) {}

    /** @param callable(array, array): int $solishtiruvchi */
    public function tartibla(callable $solishtiruvchi): array
    {
        $nusxa = $this->qatorlar;
        usort($nusxa, $solishtiruvchi);
        return $nusxa;
    }
}

$royxat = new Royxat([
    ['nom' => 'Monitor', 'narx' => 1800000],
    ['nom' => 'Klaviatura', 'narx' => 250000],
    ['nom' => 'Sichqoncha', 'narx' => 120000],
]);

// strategiyalar - oddiy funksiyalar
$narxBoyicha = static fn(array $a, array $b): int => $a['narx'] <=> $b['narx'];
$nomBoyicha = static fn(array $a, array $b): int => strcmp($a['nom'], $b['nom']);

foreach (['narx' => $narxBoyicha, 'nom' => $nomBoyicha] as $nom => $s) {
    echo "--- {$nom} boyicha", PHP_EOL;
    foreach ($royxat->tartibla($s) as $q) {
        printf('  %-12s %8d%s', $q['nom'], $q['narx'], PHP_EOL);
    }
}
Natija
--- narx boyicha
  Sichqoncha     120000
  Klaviatura     250000
  Monitor       1800000
--- nom boyicha
  Klaviatura     250000
  Monitor       1800000
  Sichqoncha     120000
usort - standart kutubxonadagi Strategy

usort, array_map, array_filter, array_walk - hammasi strategiya qabul qiladi.

Bu shuni ko'rsatadi: naqsh sinf bo'lishi shart emas. Muhimi - algoritmni tashqaridan berish imkoniyati.

Qachon sinf, qachon yopilma:

YopilmaInterfeys va sinf
Bir-ikki qatorlikKo'p qatorli
Nomi kerak emasNomi kerak (nomi())
Holat yo'qSozlamalar bor ($ogirChegara)
Bir joyda ishlatiladiKo'p joyda
Sinash shart emasAlohida sinaladi

Strategiya enum sifatida #

Variantlar ro'yxati barqaror bo'lsa:

PHP
enum Tartib: string
{
    case NarxOsish = 'narx_osish';
    case NarxKamayish = 'narx_kamayish';
    case Alifbo = 'alifbo';

    public function solishtir(array $a, array $b): int
    {
        return match ($this) {
            Tartib::NarxOsish => $a['narx'] <=> $b['narx'],
            Tartib::NarxKamayish => $b['narx'] <=> $a['narx'],
            Tartib::Alifbo => strcmp($a['nom'], $b['nom']),
        };
    }

    public function sarlavha(): string
    {
        return match ($this) {
            Tartib::NarxOsish => 'Arzondan qimmatga',
            Tartib::NarxKamayish => 'Qimmatdan arzonga',
            Tartib::Alifbo => 'Alifbo boyicha',
        };
    }
}

$qatorlar = [
    ['nom' => 'Monitor', 'narx' => 1800000],
    ['nom' => 'Klaviatura', 'narx' => 250000],
    ['nom' => 'Sichqoncha', 'narx' => 120000],
];

// URL parametridan keladi: ?tartib=narx_kamayish
$tanlov = Tartib::tryFrom('narx_kamayish') ?? Tartib::Alifbo;

echo $tanlov->sarlavha(), PHP_EOL;
usort($qatorlar, $tanlov->solishtir(...));
foreach ($qatorlar as $q) {
    printf('  %-12s %8d%s', $q['nom'], $q['narx'], PHP_EOL);
}

echo '--- barcha variantlar (forma uchun)', PHP_EOL;
foreach (Tartib::cases() as $t) {
    printf('  %-14s %s%s', $t->value, $t->sarlavha(), PHP_EOL);
}
Natija
Qimmatdan arzonga
  Monitor       1800000
  Klaviatura     250000
  Sichqoncha     120000
--- barcha variantlar (forma uchun)
  narx_osish     Arzondan qimmatga
  narx_kamayish  Qimmatdan arzonga
  alifbo         Alifbo boyicha
$tanlov->solishtir(...) - birinchi darajali callable

PHP 8.1 dan $obyekt->metod(...) yozuvi metodni funksiya sifatida oladi (uch nuqta - aynan shunday yoziladi, bu qisqartma emas).

Eski usul: [$tanlov, 'solishtir'] yoki fn($a, $b) => $tanlov->solishtir($a, $b).

Yangi yozuv qisqaroq va IDE uni tushunadi.

Strategiyalar registri #

Ko'p strategiya bo'lsa, ularni bir joyda saqlash qulay:

PHP
final class UsulRegistri
{
    /** @var array<string, YetkazishUsuli> */
    private array $usullar = [];

    public function qoy(string $kalit, YetkazishUsuli $u): self
    {
        $this->usullar[$kalit] = $u;
        return $this;
    }

    public function ol(string $kalit): YetkazishUsuli
    {
        return $this->usullar[$kalit]
            ?? throw new InvalidArgumentException("Nomalum usul: {$kalit}");
    }

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

$registr = (new UsulRegistri())
    ->qoy('pochta', new Pochta())
    ->qoy('kuryer', new Kuryer())
    ->qoy('tezkor', new Tezkor(new Kuryer()));

echo 'mavjud: ', implode(', ', $registr->kalitlar()), PHP_EOL;

$u = $registr->ol('tezkor');
printf('%s: %d som%s', $u->nomi(), $u->narx(5, 20), PHP_EOL);

try {
    $registr->ol('kema');
} catch (InvalidArgumentException $x) {
    echo 'Xato: ', $x->getMessage(), PHP_EOL;
}
Natija
mavjud: pochta, kuryer, tezkor
Tezkor Kuryer: 82000 som
Xato: Nomalum usul: kema

Qachon Strategy kerak emas #

PHP
// Bitta algoritm, o'zgarishi kutilmaydi
function qqsHisobla(int $summa): int
{
    return intdiv($summa * 12, 100);
}

echo qqsHisobla(1000000), PHP_EOL;
Natija
120000
Bitta variant uchun strategiya yozmang

QqsInterfeysi + OddiyQqs + QqsFabrikasi - bu uch fayl bir qatorlik formulani o'rash uchun.

Strategy kerak bo'lishining aniq belgilari:

BelgiBormi
Kamida ikkita haqiqiy variantMajburiy
Tanlov ish vaqtida qilinadiMajburiy
Variantlar mustaqil o'zgaradiKuchli belgi
Har birini alohida sinash kerakKuchli belgi

Ikkala majburiy belgi yo'q bo'lsa - oddiy funksiya yozing.

Va yodda tuting: QQS stavkasi o'zgarsa, bu yangi algoritm emas, shunchaki boshqa son. Uni parametr qilish yetarli.

Strategy va Template Method #

Ikkalasi ham "algoritmni almashtirish" haqida:

PHP
$farq = [
    ['Mexanizm', 'Kompozitsiya', 'Meros'],
    ['Almashtirish', 'Ish vaqtida', 'Kompilyatsiyada'],
    ['Nechta nuqta', 'Butun algoritm', 'Ayrim qadamlar'],
    ['Bogliqlik', 'Kuchsiz', 'Kuchli'],
];

printf("%-14s %-16s %s%s", '', 'Strategy', 'Template Method', PHP_EOL);
echo str_repeat('-', 50), PHP_EOL;
foreach ($farq as $q) {
    printf("%-14s %-16s %s%s", ...[...$q, PHP_EOL]);
}
Natija
               Strategy         Template Method
--------------------------------------------------
Mexanizm       Kompozitsiya     Meros
Almashtirish   Ish vaqtida      Kompilyatsiyada
Nechta nuqta   Butun algoritm   Ayrim qadamlar
Bogliqlik      Kuchsiz          Kuchli

Template Method ni 16-bo'limda ko'ramiz.

Amaliy topshiriq
  1. To'rt algoritmli if zanjirini yozing.
  2. Har bir algoritm qaysi parametrlarni ishlatishini belgilang.
  3. Ularni interfeys va sinflarga ajrating.
  4. Kontekst sinf yozing va strategiyani almashtiring.
  5. Bir strategiya ichida boshqasini ishlatib ko'ring.
  6. Xuddi shu masalani yopilmalar bilan yozing.
  7. Qachon sinf, qachon yopilma kerakligini jadval qiling.
  8. enum variantini yozing va cases() bilan forma ro'yxatini chiqaring.
  9. Strategiyalar registrini yozing.
  10. Strategy kerak emas bo'lgan ikkita funksiyani kodingizdan toping.

Xulosa #

  • Strategy - bir vazifaning bir necha usulini almashtirish naqshi.
  • Naqshsiz kodda algoritmlar if/elseif ichida yashirinadi va bir-biriga aralashadi.
  • Belgi: funksiya hamma variant uchun hamma parametrni qabul qiladi.
  • Har algoritm alohida sinfda bo'lsa - alohida sinaladi va mustaqil o'zgaradi.
  • Kontekst qaysi algoritm ishlayotganini bilmaydi.
  • Strategiyani ish vaqtida almashtirish mumkin.
  • PHP da uch shakl: interfeys+sinf, yopilma, enum+match.
  • usort, array_map - standart kutubxonadagi strategiyalar.
  • $obyekt->metod(...) bilan metodni funksiya sifatida uzatish mumkin.
  • Kamida ikkita haqiqiy variant bo'lmasa - naqsh kerak emas.

Keyingi bo'limda o'zgarishlardan bir necha obyektni xabardor qiladigan Observer ni ko'ramiz.

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.