16-bo‘lim
Template Method
Umumiy skeletni ota-sinfda saqlab, ayrim qadamlarni vorislarga qoldirish.
Ushbu bo‘lim mundarijasi
Template Method - algoritmning skeletini ota-sinfda yozib, ayrim qadamlarini vorislarga qoldirish naqshi.
Strategy butun algoritmni almashtiradi. Template Method esa faqat ayrim qadamlarni.
Muammo: uch marta takrorlangan skelet #
Uch xil formatdagi faylni import qilamiz. Har birida jarayon bir xil: o'qish, tahlil, tekshirish, saqlash, hisobot.
final class CsvImport
{
public function import(string $manba): string
{
echo ' 1. manba ochildi', PHP_EOL;
$xom = "nom,narx\nKlaviatura,250000\nSichqoncha,120000";
echo ' 2. CSV tahlil qilinmoqda', PHP_EOL;
$qatorlar = [];
foreach (array_slice(explode("\n", $xom), 1) as $q) {
[$nom, $narx] = explode(',', $q);
$qatorlar[] = ['nom' => $nom, 'narx' => (int) $narx];
}
echo ' 3. tekshirilmoqda', PHP_EOL;
$qatorlar = array_filter($qatorlar,
static fn(array $q): bool => $q['narx'] > 0);
echo ' 4. saqlanmoqda', PHP_EOL;
return sprintf('%s: %d qator saqlandi', $manba, count($qatorlar));
}
}
final class JsonImport
{
public function import(string $manba): string
{
echo ' 1. manba ochildi', PHP_EOL;
$xom = '[{"nom":"Monitor","narx":1800000},{"nom":"Kabel","narx":0}]';
echo ' 2. JSON tahlil qilinmoqda', PHP_EOL;
$qatorlar = json_decode($xom, true, 512, JSON_THROW_ON_ERROR);
echo ' 3. tekshirilmoqda', PHP_EOL;
$qatorlar = array_filter($qatorlar,
static fn(array $q): bool => $q['narx'] > 0);
echo ' 4. saqlanmoqda', PHP_EOL;
return sprintf('%s: %d qator saqlandi', $manba, count($qatorlar));
}
}
echo (new CsvImport())->import('mahsulot.csv'), PHP_EOL;
echo (new JsonImport())->import('mahsulot.json'), PHP_EOL;
1. manba ochildi
2. CSV tahlil qilinmoqda
3. tekshirilmoqda
4. saqlanmoqda
mahsulot.csv: 2 qator saqlandi
1. manba ochildi
2. JSON tahlil qilinmoqda
3. tekshirilmoqda
4. saqlanmoqda
mahsulot.json: 1 qator saqlandi
Faqat 2-qadam farq qiladi. Qolgani so'zma-so'z takrorlangan.
| Qadam | CSV | JSON | Bir xilmi |
|---|---|---|---|
| 1. Ochish | ✓ | ✓ | Ha |
| 2. Tahlil | CSV | JSON | Yo'q |
| 3. Tekshirish | ✓ | ✓ | Ha |
| 4. Saqlash | ✓ | ✓ | Ha |
Uchinchi format qo'shilsa, skelet yana ko'chiriladi.
Va eng yomoni: tekshirish qoidasi o'zgarsa, uni hamma
importerda o'zgartirish kerak. Bittasi eskicha qoladi -
13-bo'limda ko'rgan tezkor xatosining aynan o'zi.
Yechim: skelet ota-sinfda #
abstract class Importer
{
// ANDOZA METOD - skelet shu yerda, o'zgarmaydi
final public function import(string $manba): string
{
echo ' 1. manba ochildi', PHP_EOL;
$xom = $this->xomOqi();
echo " 2. {$this->format()} tahlil qilinmoqda", PHP_EOL;
$qatorlar = $this->tahlil($xom);
echo ' 3. tekshirilmoqda', PHP_EOL;
$qatorlar = array_values(array_filter($qatorlar,
fn(array $q): bool => $this->togrimi($q)));
echo ' 4. saqlanmoqda', PHP_EOL;
return sprintf('%s: %d qator saqlandi', $manba, count($qatorlar));
}
// Vorislar bajarishi SHART
abstract protected function format(): string;
abstract protected function xomOqi(): string;
/** @return list<array{nom: string, narx: int}> */
abstract protected function tahlil(string $xom): array;
// ILGAK (hook) - ixtiyoriy, standart xulqi bor
protected function togrimi(array $qator): bool
{
return $qator['narx'] > 0;
}
}
final class CsvImporter extends Importer
{
protected function format(): string { return 'CSV'; }
protected function xomOqi(): string
{
return "nom,narx\nKlaviatura,250000\nSichqoncha,120000";
}
protected function tahlil(string $xom): array
{
$natija = [];
foreach (array_slice(explode("\n", $xom), 1) as $q) {
[$nom, $narx] = explode(',', $q);
$natija[] = ['nom' => $nom, 'narx' => (int) $narx];
}
return $natija;
}
}
final class JsonImporter extends Importer
{
protected function format(): string { return 'JSON'; }
protected function xomOqi(): string
{
return '[{"nom":"Monitor","narx":1800000},{"nom":"Kabel","narx":0}]';
}
protected function tahlil(string $xom): array
{
return json_decode($xom, true, 512, JSON_THROW_ON_ERROR);
}
}
echo (new CsvImporter())->import('mahsulot.csv'), PHP_EOL;
echo (new JsonImporter())->import('mahsulot.json'), PHP_EOL;
1. manba ochildi
2. CSV tahlil qilinmoqda
3. tekshirilmoqda
4. saqlanmoqda
mahsulot.csv: 2 qator saqlandi
1. manba ochildi
2. JSON tahlil qilinmoqda
3. tekshirilmoqda
4. saqlanmoqda
mahsulot.json: 1 qator saqlandi
Natija bir xil, lekin skelet bir marta yozilgan.
Ilgak (hook) - ixtiyoriy qadam #
togrimi() ning standart xulqi bor, shuning uchun vorislar
uni majburiy emas. Lekin xohlasa - o'zgartiradi:
final class QattiqImporter extends Importer
{
protected function format(): string { return 'CSV'; }
protected function xomOqi(): string
{
return "nom,narx\nKlaviatura,250000\nSichqoncha,120000\n"
. "Kabel,5000\nSalfetka,1000";
}
protected function tahlil(string $xom): array
{
$natija = [];
foreach (array_slice(explode("\n", $xom), 1) as $q) {
[$nom, $narx] = explode(',', $q);
$natija[] = ['nom' => $nom, 'narx' => (int) $narx];
}
return $natija;
}
// ilgakni qayta yozamiz: arzon mahsulotlar ham chetlanadi
protected function togrimi(array $qator): bool
{
return $qator['narx'] >= 100000;
}
}
echo (new QattiqImporter())->import('katalog.csv'), PHP_EOL;
1. manba ochildi
2. CSV tahlil qilinmoqda
3. tekshirilmoqda
4. saqlanmoqda
katalog.csv: 2 qator saqlandi
To'rt qatordan ikkitasi qoldi - Kabel (5000) va Salfetka
(1000) chetlandi.
Andoza metod nima uchun final #
abstract class YomonImporter
{
// final EMAS - voris uni butunlay o'zgartirishi mumkin
public function import(): string
{
return 'ota: ' . $this->tahlil();
}
abstract protected function tahlil(): string;
}
final class BuzuqVoris extends YomonImporter
{
// skeletni butunlay chetlab o'tdi
public function import(): string
{
return 'voris: tekshiruvsiz, jurnalsiz, tartibsiz';
}
protected function tahlil(): string { return 'hech qachon chaqirilmaydi'; }
}
echo (new BuzuqVoris())->import(), PHP_EOL;
voris: tekshiruvsiz, jurnalsiz, tartibsiz
BuzuqVoris tekshiruvni ham, jurnalni ham chetlab o'tdi.
Bu naqshning butun ma'nosini yo'q qiladi.
Shuning uchun andoza metod final bo'lishi kerak, ichki
qadamlar esa protected:
| Element | Ko'rinuvchanlik | Nima uchun |
|---|---|---|
| Andoza metod | final public | Skelet o'zgarmasin |
| Majburiy qadam | abstract protected | Faqat voris bajarsin |
| Ilgak | protected (tanasi bor) | Ixtiyoriy |
| Yordamchi | private | Umuman tegilmasin |
protected muhim: qadamlar public bo'lsa, ularni
tashqaridan tartibsiz chaqirish mumkin bo'ladi.
Naqshning asosiy cheklovi: meros #
// Bitta voris - bitta ota. Ikkinchi "o'lchov" qo'shsak?
$variantlar = [
['CSV', 'oddiy tekshiruv'],
['CSV', 'qattiq tekshiruv'],
['JSON', 'oddiy tekshiruv'],
['JSON', 'qattiq tekshiruv'],
['XML', 'oddiy tekshiruv'],
['XML', 'qattiq tekshiruv'],
];
foreach ($variantlar as [$format, $qoida]) {
printf("%-6s + %-18s -> %s%s%s", $format, $qoida,
$format, str_replace(' ', '', ucwords($qoida)), PHP_EOL);
}
printf('jami %d ta sinf kerak%s', count($variantlar), PHP_EOL);
CSV + oddiy tekshiruv -> CSVOddiyTekshiruv
CSV + qattiq tekshiruv -> CSVQattiqTekshiruv
JSON + oddiy tekshiruv -> JSONOddiyTekshiruv
JSON + qattiq tekshiruv -> JSONQattiqTekshiruv
XML + oddiy tekshiruv -> XMLOddiyTekshiruv
XML + qattiq tekshiruv -> XMLQattiqTekshiruv
jami 6 ta sinf kerak
PHP da ko'p meros yo'q. Format ham, tekshiruv qoidasi ham o'zgaradigan bo'lsa - ko'paytma hosil bo'ladi.
Bu 9-bo'limdagi "sinflar portlashi" ning aynan o'zi.
Yechim - o'zgaradigan qismni kompozitsiyaga o'tkazish, ya'ni Strategy ga. Quyida shuni qilamiz.
Template Method dan Strategy ga o'tish #
// Tekshiruv qoidasi endi alohida obyekt
interface Qoida
{
public function nomi(): string;
public function togrimi(array $qator): bool;
}
final class NarxMusbat implements Qoida
{
public function nomi(): string { return 'narx > 0'; }
public function togrimi(array $qator): bool
{
return $qator['narx'] > 0;
}
}
final class NarxChegara implements Qoida
{
public function __construct(private readonly int $eng) {}
public function nomi(): string { return "narx >= {$this->eng}"; }
public function togrimi(array $qator): bool
{
return $qator['narx'] >= $this->eng;
}
}
// Skelet endi oddiy sinfda, meros yo'q
final class MoslashuvchanImporter
{
public function __construct(
private readonly Importer $format,
private readonly Qoida $qoida,
) {}
public function nomi(): string
{
return $this->format::class . ' + ' . $this->qoida->nomi();
}
}
// Format x Qoida - endi sinf emas, kombinatsiya
$formatlar = [new CsvImporter(), new JsonImporter()];
$qoidalar = [new NarxMusbat(), new NarxChegara(100000)];
foreach ($formatlar as $f) {
foreach ($qoidalar as $q) {
echo ' ', (new MoslashuvchanImporter($f, $q))->nomi(), PHP_EOL;
}
}
printf('%d format x %d qoida = %d kombinatsiya, %d ta sinf%s',
count($formatlar), count($qoidalar),
count($formatlar) * count($qoidalar),
count($formatlar) + count($qoidalar), PHP_EOL);
CsvImporter + narx > 0
CsvImporter + narx >= 100000
JsonImporter + narx > 0
JsonImporter + narx >= 100000
2 format x 2 qoida = 4 kombinatsiya, 4 ta sinf
Ikki o'lchov ko'paytma emas, yig'indi bo'lib qoldi: 4 kombinatsiya uchun 4 sinf (2 format + 2 qoida), meros bilan esa 4 sinf plus ota-sinf kerak edi - va uchinchi qoida qo'shilsa farq keskin o'sadi.
| O'lchovlar | Meros bilan | Kompozitsiya bilan |
|---|---|---|
| 2 x 2 | 4 sinf | 4 sinf |
| 3 x 3 | 9 sinf | 6 sinf |
| 4 x 4 | 16 sinf | 8 sinf |
| 5 x 5 | 25 sinf | 10 sinf |
Template Method va Strategy #
| Template Method | Strategy | |
|---|---|---|
| Mexanizm | Meros | Kompozitsiya |
| Nima almashadi | Ayrim qadamlar | Butun algoritm |
| Qachon aniqlanadi | Sinf yozilganda | Ish vaqtida |
| Ota-sinf o'zgarsa | Barcha vorislar ta'sirlanadi | Ta'sir yo'q |
| Nechta o'lchov | Bitta | Ko'p |
| Kod miqdori | Kamroq | Ko'proq |
Template Method mos keladi:
- O'zgaradigan bitta o'lchov bor
- Qadamlar soni 2-4 ta
- Skelet haqiqatan barqaror
- Vorislar soni kam va oldindan ma'lum
Strategy mos keladi:
- Ikki va undan ko'p o'lchov o'zgaradi
- Tanlov ish vaqtida bo'ladi
- Har variantni alohida sinash kerak
- Kombinatsiyalar oldindan noma'lum
Amaliy tavsiya: Template Method dan boshlang, u soddaroq. Ikkinchi o'lchov paydo bo'lgan kunda Strategy ga o'tkazing.
Bu 1-bo'limdagi "uch marta qoidasi" ning davomi - abstraksiyani ehtiyoj ko'ringandan keyin kiriting.
Meros bilan bog'liq yana bir xavf #
abstract class Asos
{
final public function ishla(): string
{
return implode(' -> ', [$this->birinchi(), $this->ikkinchi()]);
}
protected function birinchi(): string { return 'A'; }
abstract protected function ikkinchi(): string;
}
final class Voris extends Asos
{
// ota-sinfning himoyalangan metodiga TAYANADI
protected function ikkinchi(): string
{
return strtolower($this->birinchi()) . '2';
}
}
echo (new Voris())->ishla(), PHP_EOL;
A -> a2
Voris::ikkinchi() ota-sinfning birinchi() metodiga
tayanadi.
Ertaga kimdir Asos::birinchi() ni 'A' dan 'A1' ga
o'zgartirsa, Voris jimgina boshqacha ishlay boshlaydi -
a12.
Ota-sinf mualliflari buni bilmaydi, chunki ular faqat
ishla() natijasini sinaydi.
| Belgi | Xavf darajasi |
|---|---|
Voris faqat abstract metodni yozadi | Past |
Voris protected metodlarni chaqiradi | O'rta |
Voris protected metodni qayta yozadi | Yuqori |
Voris parent:: ni chaqiradi | Yuqori |
Merosda ota va voris orasidagi shartnoma yozilmagan bo'ladi. Kompozitsiyada esa u interfeysda aniq ko'rinadi.
Mana shu sabab "kompozitsiyani merosdan ustun qo'ying" degan maslahatning asosidir.
- Uchta importer yozing va bir xil qadamlarni sanang.
- Skeletni abstrakt sinfga chiqaring.
- Andoza metodni
finalqiling va nima uchunligini yozing. finalni olib tashlang va skeletni chetlab o'tuvchi voris yozing.- Ilgak (hook) qo'shing va uni bitta vorisda qayta yozing.
- Ikkinchi o'lchov qo'shing va nechta sinf kerakligini sanang.
- O'zgaruvchan qismni Strategy ga ko'chiring.
- Meros va kompozitsiya uchun sinflar sonini jadval qiling.
- Voris ota-sinfning
protectedmetodiga tayanadigan misol yozing. - Ota-sinfni o'zgartiring va vorisning natijasi qanday buzilganini ko'ring.
Xulosa #
- Template Method - skelet ota-sinfda, ayrim qadamlar vorislarda.
- Naqshsiz kodda bir xil skelet har sinfda so'zma-so'z takrorlanadi.
- Andoza metod
finalbo'lishi kerak - aks holda voris skeletni chetlab o'tadi. - Majburiy qadamlar
abstract protected, ilgaklar esa standart tanaga ega. - Ilgak vorisga tanlov beradi: qayta yozish yoki qoldirish.
- Gollivud tamoyili: ota-sinf vorisni chaqiradi, teskarisi emas.
- Asosiy cheklov - meros: ikkinchi o'lchov paydo bo'lsa sinflar ko'payadi.
- Kompozitsiyada o'lchovlar ko'paytma emas, yig'indi bo'ladi.
- Merosda ota va voris shartnomasi yozilmagan - mo'rt asosiy sinf muammosi.
- Template Method dan boshlang, ikkinchi o'lchov kelganda Strategy ga o'ting.
Keyingi bo'limda obyektning holatini alohida sinflarga ajratadigan State 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.