13-bo‘lim
Strategy
Algoritmni ish vaqtida almashtirish - interfeys, yopilma yoki enum bilan.
Ushbu bo‘lim mundarijasi
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:
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);
}
pochta 21000 som
kuryer 69000 som
olib_ketish 0 som
tezkor 98000 som
| Muammo | Izoh |
|---|---|
| Alohida sinash qiyin | Har testda $usul satrini uzatish kerak |
| Bir-biriga ta'sir | tezkor kuryer formulasini takrorlaydi |
| Yangi usul = shu faylni ochish | Ochiq/yopiq buziladi |
| Parametrlar birlashib ketgan | olib_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 #
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'; }
}
/** @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);
}
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
if zanjirida tezkor uchun 98 000 so'm chiqqan edi,
bu yerda esa 138 000.
Sababi eski kodda:
} 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 strategiyaTezkor 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 #
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,
);
}
}
$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;
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.
Strategiya yopilma sifatida #
Algoritm sodda bo'lsa, alohida sinf ortiqcha:
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);
}
}
--- narx boyicha
Sichqoncha 120000
Klaviatura 250000
Monitor 1800000
--- nom boyicha
Klaviatura 250000
Monitor 1800000
Sichqoncha 120000
usort - standart kutubxonadagi Strategyusort, 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:
| Yopilma | Interfeys va sinf |
|---|---|
| Bir-ikki qatorlik | Ko'p qatorli |
| Nomi kerak emas | Nomi kerak (nomi()) |
| Holat yo'q | Sozlamalar bor ($ogirChegara) |
| Bir joyda ishlatiladi | Ko'p joyda |
| Sinash shart emas | Alohida sinaladi |
Strategiya enum sifatida #
Variantlar ro'yxati barqaror bo'lsa:
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);
}
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 callablePHP 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:
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;
}
mavjud: pochta, kuryer, tezkor
Tezkor Kuryer: 82000 som
Xato: Nomalum usul: kema
Qachon Strategy kerak emas #
// Bitta algoritm, o'zgarishi kutilmaydi
function qqsHisobla(int $summa): int
{
return intdiv($summa * 12, 100);
}
echo qqsHisobla(1000000), PHP_EOL;
120000
QqsInterfeysi + OddiyQqs + QqsFabrikasi - bu uch fayl
bir qatorlik formulani o'rash uchun.
Strategy kerak bo'lishining aniq belgilari:
| Belgi | Bormi |
|---|---|
| Kamida ikkita haqiqiy variant | Majburiy |
| Tanlov ish vaqtida qilinadi | Majburiy |
| Variantlar mustaqil o'zgaradi | Kuchli belgi |
| Har birini alohida sinash kerak | Kuchli 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:
$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]);
}
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.
- To'rt algoritmli
ifzanjirini yozing. - Har bir algoritm qaysi parametrlarni ishlatishini belgilang.
- Ularni interfeys va sinflarga ajrating.
- Kontekst sinf yozing va strategiyani almashtiring.
- Bir strategiya ichida boshqasini ishlatib ko'ring.
- Xuddi shu masalani yopilmalar bilan yozing.
- Qachon sinf, qachon yopilma kerakligini jadval qiling.
enumvariantini yozing vacases()bilan forma ro'yxatini chiqaring.- Strategiyalar registrini yozing.
- Strategy kerak emas bo'lgan ikkita funksiyani kodingizdan toping.
Xulosa #
- Strategy - bir vazifaning bir necha usulini almashtirish naqshi.
- Naqshsiz kodda algoritmlar
if/elseifichida 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.
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.