3-bo‘lim

Factory Method

Obyektni kim yaratishini vorislarga topshirish - va PHP 8 da buning soddaroq yo'li.

🕑 13 daqiqa o‘qish 📄 773 so‘z 👁 1 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Muammo: tarqoq new
  2. Oddiy fabrika
  3. Factory Method naqshi
  4. Farqni ko'rish
  5. PHP 8 da soddaroq yo'l
  6. Fabrika qachon kerak emas
  7. Nomlangan konstruktorlar
  8. Xulosa

Factory Method - obyekt yaratishni alohida metodga chiqarish va uni vorislarga qoldirish naqshi.

Muammo bitta so'zda: kodda new qayerda yozilgan.

Muammo: tarqoq new #

PHP
interface Formatlovchi
{
    public function kengaytma(): string;
    public function format(array $qatorlar): string;
}

final class CsvFormat implements Formatlovchi
{
    public function kengaytma(): string { return 'csv'; }

    public function format(array $qatorlar): string
    {
        $natija = [];
        foreach ($qatorlar as $q) {
            $natija[] = implode(',', $q);
        }
        return implode("\n", $natija);
    }
}

final class JsonFormat implements Formatlovchi
{
    public function kengaytma(): string { return 'json'; }

    public function format(array $qatorlar): string
    {
        return json_encode($qatorlar, JSON_UNESCAPED_UNICODE);
    }
}

final class MatnFormat implements Formatlovchi
{
    public function kengaytma(): string { return 'txt'; }

    public function format(array $qatorlar): string
    {
        $natija = [];
        foreach ($qatorlar as $q) {
            $natija[] = sprintf('%-12s %8s', $q[0], $q[1]);
        }
        return implode("\n", $natija);
    }
}

Uchta format tayyor. Endi ularni ishlatadigan kod:

PHP
$malumot = [['Klaviatura', '250000'], ['Sichqoncha', '120000']];

function eskiEksport(string $tur, array $malumot): string
{
    if ($tur === 'csv') {
        $f = new CsvFormat();
    } elseif ($tur === 'json') {
        $f = new JsonFormat();
    } else {
        $f = new MatnFormat();
    }
    return "hisobot.{$f->kengaytma()}\n" . $f->format($malumot);
}

echo eskiEksport('csv', $malumot), PHP_EOL;
echo eskiEksport('json', $malumot), PHP_EOL;
Natija
hisobot.csv
Klaviatura,250000
Sichqoncha,120000
hisobot.json
[["Klaviatura","250000"],["Sichqoncha","120000"]]
new bitta joyda emas

Real loyihada bunday if zanjiri bir necha faylda takrorlanadi: kontrollerda, cron skriptida, konsol buyrug'ida.

Yangi format qo'shganda ularning hammasini topib o'zgartirish kerak. Bittasini unutsangiz - xato faqat ishlab chiqarishda ko'rinadi.

Naqshning maqsadi - new ni bitta boshqariladigan joyga yig'ish.

Oddiy fabrika #

Eng sodda yechim - yaratishni bitta sinfga yig'ish.

PHP
final class FormatFabrikasi
{
    public static function yarat(string $tur): Formatlovchi
    {
        return match ($tur) {
            'csv'  => new CsvFormat(),
            'json' => new JsonFormat(),
            'txt'  => new MatnFormat(),
            default => throw new InvalidArgumentException(
                "Noma'lum format: {$tur}"
            ),
        };
    }
}
PHP
$malumot = [['Klaviatura', '250000'], ['Sichqoncha', '120000']];

foreach (['txt', 'csv'] as $tur) {
    $f = FormatFabrikasi::yarat($tur);
    echo "--- hisobot.{$f->kengaytma()}", PHP_EOL;
    echo $f->format($malumot), PHP_EOL;
}

try {
    FormatFabrikasi::yarat('pdf');
} catch (InvalidArgumentException $x) {
    echo 'Xato: ', $x->getMessage(), PHP_EOL;
}
Natija
--- hisobot.txt
Klaviatura     250000
Sichqoncha     120000
--- hisobot.csv
Klaviatura,250000
Sichqoncha,120000
Xato: Noma'lum format: pdf
Bu Simple Factory - GoF kitobida yo'q

"Oddiy fabrika" rasmiy naqsh emas, shunchaki foydali usul. Lekin amalda u eng ko'p ishlatiladigan variant.

Uning kuchli tomoni: match bitta joyda, hamma uni chaqiradi. Yangi format qo'shish - shu bitta match ga bir qator.

Zaif tomoni: FormatFabrikasi hamma formatlarni biladi. Ya'ni ochiq/yopiq tamoyili to'liq bajarilmaydi - fabrika fayli har safar o'zgaradi.

Ko'p loyihada bu yetarlicha yaxshi. Haqiqiy Factory Method esa bundan bir qadam narida.

Factory Method naqshi #

Endi haqiqiy naqsh. Farqi: yaratish statik funksiya emas, sinfning metodi, va uni voris aniqlaydi.

PHP
abstract class HisobotChiqaruvchi
{
    // Factory Method: qaysi formatlovchi kerakligini voris hal qiladi
    abstract protected function formatlovchiYarat(): Formatlovchi;

    // Umumiy jarayon - vorislarda takrorlanmaydi
    public function chiqar(string $nom, array $malumot): string
    {
        $f = $this->formatlovchiYarat();
        $tana = $f->format($malumot);
        $qator = substr_count($tana, "\n") + 1;

        return sprintf(
            "%s.%s (%d qator)\n%s",
            $nom, $f->kengaytma(), $qator, $tana
        );
    }
}

final class CsvChiqaruvchi extends HisobotChiqaruvchi
{
    protected function formatlovchiYarat(): Formatlovchi
    {
        return new CsvFormat();
    }
}

final class JsonChiqaruvchi extends HisobotChiqaruvchi
{
    protected function formatlovchiYarat(): Formatlovchi
    {
        return new JsonFormat();
    }
}
PHP
$malumot = [['Klaviatura', '250000'], ['Sichqoncha', '120000']];

/** @var list<HisobotChiqaruvchi> $chiqaruvchilar */
$chiqaruvchilar = [new CsvChiqaruvchi(), new JsonChiqaruvchi()];

foreach ($chiqaruvchilar as $ch) {
    echo $ch->chiqar('oylik', $malumot), PHP_EOL;
}
Natija
oylik.csv (2 qator)
Klaviatura,250000
Sichqoncha,120000
oylik.json (1 qator)
[["Klaviatura","250000"],["Sichqoncha","120000"]]

Yangi format qo'shish - hech qanday mavjud faylni ochmasdan:

PHP
final class XmlFormat implements Formatlovchi
{
    public function kengaytma(): string { return 'xml'; }

    public function format(array $qatorlar): string
    {
        $natija = ['<mahsulotlar>'];
        foreach ($qatorlar as $q) {
            $natija[] = "  <mahsulot nom=\"{$q[0]}\" narx=\"{$q[1]}\"/>";
        }
        $natija[] = '</mahsulotlar>';
        return implode("\n", $natija);
    }
}

final class XmlChiqaruvchi extends HisobotChiqaruvchi
{
    protected function formatlovchiYarat(): Formatlovchi
    {
        return new XmlFormat();
    }
}

$malumot = [['Klaviatura', '250000'], ['Sichqoncha', '120000']];
echo (new XmlChiqaruvchi())->chiqar('oylik', $malumot), PHP_EOL;
Natija
oylik.xml (4 qator)
<mahsulotlar>
  <mahsulot nom="Klaviatura" narx="250000"/>
  <mahsulot nom="Sichqoncha" narx="120000"/>
</mahsulotlar>
Factory Method - ikki parallel ierarxiya Yaratuvchi Mahsulot HisobotChiqaruvchi + chiqar() # formatlovchiYarat() ← abstrakt Formatlovchi + kengaytma() + format() qaytaradi CsvChiqaruvchi new CsvFormat() JsonChiqaruvchi new JsonFormat() CsvFormat JsonFormat har voris o'z juftini yaratadi Nima uchun shunday chiqar() umumiy jarayonni biladi, lekin qaysi formatlovchi kerakligini bilmaydi. Bu qarorni voris qabul qiladi - shuning uchun yangi format eski kodni o'zgartirmaydi.
Yaratuvchi va mahsulot ierarxiyalari yonma-yon o'sadi

Farqni ko'rish #

Simple FactoryFactory Method
Yaratish qayerdaBitta statik metodHar vorisda alohida metod
Yangi tur qo'shishmatch ni tahrirlashYangi voris sinf
Sinflar soniKamKo'p
Umumiy jarayonYo'qchiqar() da
QachonFaqat tanlash kerakTanlash + umumiy jarayon
Qaysi birini tanlash

Savol bitta: yaratishdan tashqari umumiy ish bormi?

Bizning misolda bor edi - chiqar() fayl nomini yasaydi, qatorlarni sanaydi, sarlavha qo'shadi. Bu kod har formatda bir xil.

Agar shunday umumiy ish bo'lmasa - Factory Method ortiqcha. match bilan cheklaning.

PHP 8 da soddaroq yo'l #

Ko'p holatda enum fabrikaning o'rnini bosadi:

PHP
enum Format: string
{
    case Csv = 'csv';
    case Json = 'json';
    case Matn = 'txt';

    public function formatlovchi(): Formatlovchi
    {
        return match ($this) {
            Format::Csv  => new CsvFormat(),
            Format::Json => new JsonFormat(),
            Format::Matn => new MatnFormat(),
        };
    }
}

$malumot = [['Klaviatura', '250000']];

foreach (Format::cases() as $format) {
    $f = $format->formatlovchi();
    printf("%-5s -> %s%s", $format->value, $f->format($malumot), PHP_EOL);
}

// satrdan enum ga - noto'g'ri qiymat null qaytaradi
var_dump(Format::tryFrom('pdf'));
echo Format::from('json')->name, PHP_EOL;
Natija
csv   -> Klaviatura,250000
json  -> [["Klaviatura","250000"]]
txt   -> Klaviatura     250000
NULL
Json
enum ning uchta ustunligi
UstunlikIzoh
match to'liqligi tekshiriladiYangi case qo'shsangiz, PHP xato beradi
tryFrom tayyor validatsiyanull qaytaradi, istisno kerak emas
cases() ro'yxatni beradiFormani to'ldirish uchun qulay

Birinchi punkt eng qimmatli: enum ga case Pdf qo'shsangiz va match ga qator qo'shishni unutsangiz, PHP UnhandledMatchError tashlaydi.

Fabrika sinfida esa default bo'lgani uchun xato ishlab chiqarishgacha yashirin qoladi.

Kamchiligi: enum ichida barcha turlar sanab o'tiladi - plagin kabi tashqaridan kengaytirib bo'lmaydi.

Fabrika qachon kerak emas #

PHP
final readonly class Nuqta
{
    public function __construct(public int $x, public int $y) {}

    public function __toString(): string
    {
        return "({$this->x}, {$this->y})";
    }
}

// Bitta tur, murakkab qaror yo'q - fabrika ortiqcha
echo new Nuqta(3, 4), PHP_EOL;
Natija
(3, 4)
Fabrika qo'shmang, agar...
HolatNima qilish
Faqat bitta amalga oshirish borTo'g'ridan-to'g'ri new
Yaratish tanlovi yo'qnew
Konstruktor soddanew
Fabrika faqat new ni o'raydiFoydasiz qatlam

Eng ko'p uchraydigan xato - shunday sinf:

KOD
final class NuqtaFabrikasi {
    public static function yarat(int $x, int $y): Nuqta {
        return new Nuqta($x, $y);
    }
}

Bu hech narsa qo'shmaydi. new Nuqta(3, 4) qisqaroq va aniqroq.

Nomlangan konstruktorlar #

PHP da fabrikaning eng qulay shakli - sinfning o'z statik metodlari:

PHP
final readonly class Pul
{
    private function __construct(
        public int $tiyin,
        public string $valyuta,
    ) {}

    public static function somdan(float $som): self
    {
        return new self((int) round($som * 100), 'UZS');
    }

    public static function tiyindan(int $tiyin): self
    {
        return new self($tiyin, 'UZS');
    }

    public static function nol(): self
    {
        return new self(0, 'UZS');
    }

    public function qosh(self $boshqa): self
    {
        return new self($this->tiyin + $boshqa->tiyin, $this->valyuta);
    }

    public function __toString(): string
    {
        return number_format($this->tiyin / 100, 2, '.', ' ') . ' ' . $this->valyuta;
    }
}

echo Pul::somdan(12500.75), PHP_EOL;
echo Pul::tiyindan(990), PHP_EOL;
echo Pul::nol()->qosh(Pul::somdan(1500))->qosh(Pul::somdan(2.50)), PHP_EOL;
Natija
12 500.75 UZS
9.90 UZS
1 502.50 UZS
Konstruktor private

__construct yopiq - obyektni faqat nomlangan metodlar orqali yaratish mumkin.

Foydasi: new Pul(1250075, 'UZS') degan chalkash chaqiruv o'rniga Pul::somdan(12500.75) yoziladi. Nom o'zi tushuntiradi.

Bu ham fabrika, lekin alohida sinf kerak emas. PHP kodida juda ko'p uchraydi.

Amaliy topshiriq
  1. if zanjirli eksport funksiyasini yozing va uni sanang.
  2. Uni match li oddiy fabrikaga aylantiring.
  3. HisobotChiqaruvchi ga to'rtinchi vorisni qo'shing.
  4. Qo'shishda nechta mavjud faylni ochganingizni yozing.
  5. Simple Factory va Factory Method farqini uch qatorda ayting.
  6. Format enum iga case Pdf qo'shing va match ni tahrirlamang - xatoni ko'ring.
  7. tryFrom va from farqini sinang.
  8. Pul sinfiga ayir() metodini qo'shing.
  9. Konstruktor private bo'lishining foydasini yozing.
  10. Kodingizdan fabrika kerak bo'lmagan ikkita joyni toping.

Xulosa #

  • Factory Method - obyekt yaratishni ajratish naqshi.
  • Muammo: new bir necha faylda tarqalib ketadi.
  • Simple Factory - bitta match, GoF naqshi emas, lekin eng ko'p ishlatiladi.
  • Factory Method - yaratishni voris hal qiladi, umumiy jarayon ota-sinfda qoladi.
  • Tanlov mezoni: yaratishdan tashqari umumiy ish bormi.
  • PHP 8 da enum + match ko'p holatda fabrikani almashtiradi.
  • enum ustunligi: match to'liqligi tekshiriladi, tryFrom va cases() tayyor.
  • enum kamchiligi: tashqaridan kengaytirib bo'lmaydi.
  • Bitta tur uchun fabrika - ortiqcha qatlam.
  • Nomlangan konstruktorlar (Pul::somdan()) - PHP dagi eng qulay fabrika shakli.

Keyingi bo'limda bir-biriga bog'liq obyektlar oilasini yaratadigan Abstract Factory 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.