16-bo‘lim

Template Method

Umumiy skeletni ota-sinfda saqlab, ayrim qadamlarni vorislarga qoldirish.

🕑 12 daqiqa o‘qish 📄 953 so‘z 👁 1 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Muammo: uch marta takrorlangan skelet
  2. Yechim: skelet ota-sinfda
  3. Ilgak (hook) - ixtiyoriy qadam
  4. Andoza metod nima uchun final
  5. Naqshning asosiy cheklovi: meros
  6. Template Method dan Strategy ga o'tish
  7. Template Method va Strategy
  8. Meros bilan bog'liq yana bir xavf
  9. Xulosa

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.

PHP
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;
Natija
  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
To'rtta qadamdan uchtasi bir xil

Faqat 2-qadam farq qiladi. Qolgani so'zma-so'z takrorlangan.

QadamCSVJSONBir xilmi
1. OchishHa
2. TahlilCSVJSONYo'q
3. TekshirishHa
4. SaqlashHa

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 #

PHP
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);
    }
}
PHP
echo (new CsvImporter())->import('mahsulot.csv'), PHP_EOL;
echo (new JsonImporter())->import('mahsulot.json'), PHP_EOL;
Natija
  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:

PHP
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;
Natija
  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.

Template Method - skelet qotgan, qadamlar ochiq abstract class Importer final import() 1. ochish ota-sinfda 2. tahlil() abstract 3. togrimi() ilgak 4. saqlash ota-sinfda Tartib va oqim ota-sinfda qotgan - voris uni o'zgartira olmaydi sariq = majburiy · yashil = ixtiyoriy · oq = yopiq CsvImporter tahlil() yozadi JsonImporter tahlil() yozadi QattiqImporter + togrimi() ham Gollivud tamoyili "Bizni chaqirmang - biz sizni chaqiramiz." Voris ota-sinfni emas, ota-sinf vorisni chaqiradi.
Nazorat ota-sinfda qoladi - voris faqat bo'shliqlarni to'ldiradi

Andoza metod nima uchun final #

PHP
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;
Natija
voris: tekshiruvsiz, jurnalsiz, tartibsiz
Skeletni ochiq qoldirmang

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:

ElementKo'rinuvchanlikNima uchun
Andoza metodfinal publicSkelet o'zgarmasin
Majburiy qadamabstract protectedFaqat voris bajarsin
Ilgakprotected (tanasi bor)Ixtiyoriy
YordamchiprivateUmuman tegilmasin

protected muhim: qadamlar public bo'lsa, ularni tashqaridan tartibsiz chaqirish mumkin bo'ladi.

Naqshning asosiy cheklovi: meros #

PHP
// 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);
Natija
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
Ikki o'lchov - meros yiqiladi

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 #

PHP
// 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);
Natija
  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'lchovlarMeros bilanKompozitsiya bilan
2 x 24 sinf4 sinf
3 x 39 sinf6 sinf
4 x 416 sinf8 sinf
5 x 525 sinf10 sinf

Template Method va Strategy #

Template MethodStrategy
MexanizmMerosKompozitsiya
Nima almashadiAyrim qadamlarButun algoritm
Qachon aniqlanadiSinf yozilgandaIsh vaqtida
Ota-sinf o'zgarsaBarcha vorislar ta'sirlanadiTa'sir yo'q
Nechta o'lchovBittaKo'p
Kod miqdoriKamroqKo'proq
Qaysi birini tanlash

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 #

PHP
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;
Natija
A -> a2
"Mo'rt asosiy sinf" muammosi

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.

BelgiXavf darajasi
Voris faqat abstract metodni yozadiPast
Voris protected metodlarni chaqiradiO'rta
Voris protected metodni qayta yozadiYuqori
Voris parent:: ni chaqiradiYuqori

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.

Amaliy topshiriq
  1. Uchta importer yozing va bir xil qadamlarni sanang.
  2. Skeletni abstrakt sinfga chiqaring.
  3. Andoza metodni final qiling va nima uchunligini yozing.
  4. final ni olib tashlang va skeletni chetlab o'tuvchi voris yozing.
  5. Ilgak (hook) qo'shing va uni bitta vorisda qayta yozing.
  6. Ikkinchi o'lchov qo'shing va nechta sinf kerakligini sanang.
  7. O'zgaruvchan qismni Strategy ga ko'chiring.
  8. Meros va kompozitsiya uchun sinflar sonini jadval qiling.
  9. Voris ota-sinfning protected metodiga tayanadigan misol yozing.
  10. 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 final bo'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.

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.