4-bo‘lim

Abstract Factory

Bitta emas, bir-biriga mos keladigan obyektlar oilasini yaratish.

🕑 12 daqiqa o‘qish 📄 804 so‘z 👁 1 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Muammo: aralashib ketgan oila
  2. Yechim: oila fabrikasi
  3. Uchinchi oila qo'shish
  4. Naqshning narxi
  5. Qachon ishlatiladi
  6. Fabrikani tanlash
  7. Xulosa

Factory Method bitta obyekt yaratadi. Abstract Factory esa bir-biriga mos keladigan obyektlar oilasini yaratadi.

Asosiy g'oya: oiladagi qismlarni aralashtirib bo'lmasin.

Muammo: aralashib ketgan oila #

Interfeys elementlarini matn ko'rinishida chizamiz. Ikkita mavzu bor: yorug' va qorong'i.

PHP
interface Tugma
{
    public function chiz(string $matn): string;
}

interface Panel
{
    public function chiz(string $sarlavha, string $ichi): string;
}

final class YorugTugma implements Tugma
{
    public function chiz(string $matn): string
    {
        return "[ {$matn} ]";
    }
}

final class QorongTugma implements Tugma
{
    public function chiz(string $matn): string
    {
        return "< {$matn} >";
    }
}

final class YorugPanel implements Panel
{
    public function chiz(string $sarlavha, string $ichi): string
    {
        $chiziq = str_repeat('-', 28);
        return "+{$chiziq}+\n| " . str_pad($sarlavha, 26) . " |\n"
             . "| " . str_pad($ichi, 26) . " |\n+{$chiziq}+";
    }
}

final class QorongPanel implements Panel
{
    public function chiz(string $sarlavha, string $ichi): string
    {
        $chiziq = str_repeat('=', 28);
        return "#{$chiziq}#\n# " . str_pad($sarlavha, 26) . " #\n"
             . "# " . str_pad($ichi, 26) . " #\n#{$chiziq}#";
    }
}

Endi ularni qo'lda yig'amiz - va xato qilamiz:

PHP
// Diqqat: panel qorong'i, tugma yorug'
$panel = new QorongPanel();
$tugma = new YorugTugma();

echo $panel->chiz('Sozlamalar', $tugma->chiz('Saqlash')), PHP_EOL;
Natija
#============================#
# Sozlamalar                 #
# [ Saqlash ]                #
#============================#
Kod ishladi, natija noto'g'ri

Panel # va = bilan chizildi (qorong'i uslub), tugma esa [ ] bilan (yorug' uslub). Ikki oila aralashib ketdi.

Hech qanday xato yo'q, hech qanday istisno yo'q. Faqat foydalanuvchi buzuq interfeysni ko'radi.

Bunday xatolar eng yomoni, chunki:

SababOqibati
Turlar tekshiruvi o'tadiIkkalasi ham to'g'ri interfeysni bajaradi
Test odatda tutmaydiHar biri alohida to'g'ri ishlaydi
Faqat ko'z bilan ko'rinadiDizayner shikoyat qilgach topiladi

Muammoning ildizi: oila haqidagi bilim kodda yo'q. Hech kim "qorong'i panel qorong'i tugma bilan yuradi" degan qoidani yozib qo'ymagan.

Yechim: oila fabrikasi #

PHP
interface InterfeysFabrikasi
{
    public function nomi(): string;
    public function tugmaYarat(): Tugma;
    public function panelYarat(): Panel;
}

final class YorugFabrika implements InterfeysFabrikasi
{
    public function nomi(): string { return 'yorug'; }
    public function tugmaYarat(): Tugma { return new YorugTugma(); }
    public function panelYarat(): Panel { return new YorugPanel(); }
}

final class QorongFabrika implements InterfeysFabrikasi
{
    public function nomi(): string { return 'qorong'; }
    public function tugmaYarat(): Tugma { return new QorongTugma(); }
    public function panelYarat(): Panel { return new QorongPanel(); }
}

// Mijoz kod faqat fabrikani biladi, konkret sinflarni emas
function sozlamaOynasi(InterfeysFabrikasi $fabrika): string
{
    $panel = $fabrika->panelYarat();
    $tugma = $fabrika->tugmaYarat();

    return $panel->chiz('Sozlamalar', $tugma->chiz('Saqlash'));
}
PHP
foreach ([new YorugFabrika(), new QorongFabrika()] as $f) {
    echo "--- mavzu: {$f->nomi()}", PHP_EOL;
    echo sozlamaOynasi($f), PHP_EOL;
}
Natija
--- mavzu: yorug
+----------------------------+
| Sozlamalar                 |
| [ Saqlash ]                |
+----------------------------+
--- mavzu: qorong
#============================#
# Sozlamalar                 #
# < Saqlash >                #
#============================#

Endi har ikkala mavzu butun: yorug'da +, - va [ ]; qorong'ida #, = va < >. Aralashish imkonsiz, chunki mijoz kod konkret sinf nomini umuman ko'rmaydi.

Buni tekshirib ham ko'rish mumkin:

PHP
$oyna = sozlamaOynasi(new QorongFabrika());

printf("panel qorong'imi: %s%s",
    str_contains($oyna, '#====') ? 'ha' : 'yoq', PHP_EOL);
printf("tugma qorong'imi: %s%s",
    str_contains($oyna, '< Saqlash >') ? 'ha' : 'yoq', PHP_EOL);
printf("yorug' iz qoldimi: %s%s",
    str_contains($oyna, '[ Saqlash ]') ? 'ha' : 'yoq', PHP_EOL);
Natija
panel qorong'imi: ha
tugma qorong'imi: ha
yorug' iz qoldimi: yoq
Abstract Factory - oila butunligini saqlaydi InterfeysFabrikasi tugmaYarat() · panelYarat() YorugFabrika YorugTugma YorugPanel [ Saqlash ] +---- panel ----+ QorongFabrika QorongTugma QorongPanel < Saqlash > #==== panel ====# × Oilalarni aralashtirib bo'lmaydi Mijoz kod faqat InterfeysFabrikasi ni biladi - konkret sinf nomi hech qayerda yozilmaydi.
Bitta fabrika - butun oila; aralashish imkonsiz

Uchinchi oila qo'shish #

PHP
final class QavsTugma implements Tugma
{
    public function chiz(string $matn): string
    {
        return "(( {$matn} ))";
    }
}

final class QavsPanel implements Panel
{
    public function chiz(string $sarlavha, string $ichi): string
    {
        $chiziq = str_repeat('~', 28);
        return "({$chiziq})\n( " . str_pad($sarlavha, 26) . " )\n"
             . "( " . str_pad($ichi, 26) . " )\n({$chiziq})";
    }
}

final class QavsFabrika implements InterfeysFabrikasi
{
    public function nomi(): string { return 'qavs'; }
    public function tugmaYarat(): Tugma { return new QavsTugma(); }
    public function panelYarat(): Panel { return new QavsPanel(); }
}

// sozlamaOynasi() umuman o'zgarmadi
echo sozlamaOynasi(new QavsFabrika()), PHP_EOL;
Natija
(~~~~~~~~~~~~~~~~~~~~~~~~~~~~)
( Sozlamalar                 )
( (( Saqlash ))              )
(~~~~~~~~~~~~~~~~~~~~~~~~~~~~)

Uchta yangi sinf yozildi, bitta ham mavjud fayl o'zgarmadi.

Naqshning narxi #

Abstract Factory ikki o'lchovda o'sadi: oilalar va mahsulotlar.

PHP
$oilalar = ['Yorug', 'Qorong', 'Qavs'];
$mahsulotlar = ['Tugma', 'Panel'];

printf("%-10s %-14s %-14s%s", 'oila', $mahsulotlar[0], $mahsulotlar[1], PHP_EOL);
echo str_repeat('-', 40), PHP_EOL;

foreach ($oilalar as $oila) {
    printf("%-10s %-14s %-14s%s", $oila,
        $oila . $mahsulotlar[0], $oila . $mahsulotlar[1], PHP_EOL);
}

printf("%s%d mahsulot sinfi + %d fabrika sinfi + %d interfeys = %d fayl%s",
    PHP_EOL,
    count($oilalar) * count($mahsulotlar),
    count($oilalar),
    count($mahsulotlar) + 1,
    count($oilalar) * count($mahsulotlar) + count($oilalar)
        + count($mahsulotlar) + 1,
    PHP_EOL);
Natija
oila       Tugma          Panel         
----------------------------------------
Yorug      YorugTugma     YorugPanel    
Qorong     QorongTugma    QorongPanel   
Qavs       QavsTugma      QavsPanel     

6 mahsulot sinfi + 3 fabrika sinfi + 3 interfeys = 12 fayl
Yangi mahsulot turi qo'shish - og'riqli

Yangi oila qo'shish oson: bitta fabrika + ikkita mahsulot sinfi, mavjud kod tegilmaydi.

Lekin yangi mahsulot turi (masalan Maydon) qo'shish uchun:

Nima o'zgaradiNechta fayl
Maydon interfeysi1 (yangi)
InterfeysFabrikasi interfeysi1 (o'zgaradi)
Har bir fabrika sinfi3 (o'zgaradi)
Har oila uchun Maydon sinfi3 (yangi)

Jami 8 fayl, ulardan 4 tasi mavjud. Interfeysga metod qo'shilgani uchun uni bajaruvchi barcha sinflar buziladi

  • to hammasi yozilmaguncha kod ishlamaydi.

Bu naqshning asosiy cheklovi: oila qo'shish arzon, mahsulot qo'shish qimmat.

Shuning uchun Abstract Factory ni faqat mahsulotlar ro'yxati barqaror bo'lganda ishlating.

Qachon ishlatiladi #

HolatMos keladimi
UI mavzulari (yorug'/qorong'i)Ha
Baza drayverlari (ulanish + so'rov quruvchi + migratsiya)Ha
Test va ishlab chiqarish muhitlariHa
Operatsion tizimga qarab fayl va yo'l ishlovchilariHa
Bir-biriga bog'liq bo'lmagan obyektlarYo'q
Faqat bitta oila borYo'q
PHP
// Bir-biriga bog'liq bo'lmagan obyektlar - fabrika kerak emas
final readonly class Sozlama
{
    public function __construct(
        public string $kalit,
        public string $qiymat,
    ) {}
}

$sozlamalar = [
    new Sozlama('til', 'uz'),
    new Sozlama('mavzu', 'qorong'),
];

foreach ($sozlamalar as $s) {
    printf("%-6s = %s%s", $s->kalit, $s->qiymat, PHP_EOL);
}
Natija
til    = uz
mavzu  = qorong
Bitta oila bo'lsa - naqsh ortiqcha

Ko'p loyihada mavzu bitta bo'ladi va hech qachon ikkinchisi qo'shilmaydi.

Shunday holatda InterfeysFabrikasi interfeysi va YorugFabrika sinfi hech narsa qo'shmaydi - ular faqat new YorugTugma() ni o'raydi.

Ikkinchi oila paydo bo'lgandan keyin naqshni kiritish oson: mavjud sinflar allaqachon interfeyslarni bajaradi, faqat fabrika qo'shiladi.

Bu 1-bo'limdagi uch marta qoidasi ning aynan o'zi.

Fabrikani tanlash #

Amalda fabrika odatda sozlamadan tanlanadi:

PHP
function fabrikaTanla(string $mavzu): InterfeysFabrikasi
{
    return match ($mavzu) {
        'yorug'  => new YorugFabrika(),
        'qorong' => new QorongFabrika(),
        'qavs'   => new QavsFabrika(),
        default  => new YorugFabrika(),
    };
}

$sozlama = ['mavzu' => 'qorong'];
$fabrika = fabrikaTanla($sozlama['mavzu']);

echo 'tanlangan: ', $fabrika->nomi(), PHP_EOL;
echo $fabrika->tugmaYarat()->chiz('Kirish'), PHP_EOL;

// noma'lum qiymat - zaxira variant
echo fabrikaTanla('mavjud-emas')->nomi(), PHP_EOL;
Natija
tanlangan: qorong
< Kirish >
yorug

Bu match - butun dasturdagi yagona joy bo'lib, u konkret fabrika sinflarini biladi. Qolgan hamma kod faqat interfeys bilan ishlaydi.

Abstract Factory va Factory Method farqi
Factory MethodAbstract Factory
Nechta mahsulotBittaBir necha (oila)
Asosiy mexanizmMeros - voris metodni qayta yozadiKompozitsiya - fabrika uzatiladi
KengaytirishYangi voris sinfYangi fabrika sinfi
Asosiy maqsadYaratishni ajratishOila butunligi

Amalda Abstract Factory ning har bir metodi - bu bitta Factory Method. Ya'ni ikkinchisi birinchisining to'plami.

Amaliy topshiriq
  1. Ikkita mavzu uchun Tugma va Panel sinflarini yozing.
  2. Ularni ataylab aralashtiring va natijani ko'ring.
  3. InterfeysFabrikasi interfeysini yozing va mijoz funksiyasini unga o'tkazing.
  4. Uchinchi mavzu qo'shing - mijoz kodi o'zgardimi?
  5. Maydon degan yangi mahsulot turi qo'shing va nechta mavjud fayl o'zgarganini sanang.
  6. Nima uchun oila qo'shish arzon, mahsulot qo'shish qimmatligini tushuntiring.
  7. fabrikaTanla funksiyasini enum bilan qayta yozing.
  8. Mijoz kodida konkret sinf nomi qolib ketmaganini grep bilan tekshiring.
  9. Factory Method va Abstract Factory farqini uch qatorda yozing.
  10. Bu naqsh kerak emas bo'lgan ikkita holat yozing.

Xulosa #

  • Abstract Factory - bir-biriga mos obyektlar oilasini yaratadi.
  • Asosiy maqsad - oila qismlarini aralashtirishning oldini olish.
  • Aralashish xatosi turlar tekshiruvida ham, testda ham ko'rinmaydi.
  • Mijoz kod faqat fabrika interfeysini biladi, konkret sinflarni emas.
  • Yangi oila qo'shish arzon: mavjud fayllar tegilmaydi.
  • Yangi mahsulot turi qo'shish qimmat: interfeys va barcha fabrikalar o'zgaradi.
  • Shuning uchun mahsulotlar ro'yxati barqaror bo'lishi kerak.
  • Konkret fabrikani tanlash dasturdagi yagona match da qoladi.
  • Factory Method merosga, Abstract Factory kompozitsiyaga tayanadi.
  • Oila bitta bo'lsa yoki obyektlar bog'liq bo'lmasa - naqsh kerak emas.

Keyingi bo'limda ko'p parametrli obyektlarni bosqichma-bosqich quradigan Builder 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.