2-bo‘lim

SOLID tamoyillari

Deyarli har bir naqsh SOLID ning bir yoki ikki tamoyilidan kelib chiqadi.

🕑 15 daqiqa o‘qish 📄 910 so‘z 👁 1 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. S - bir mas'uliyat
  2. O - ochiq/yopiq
  3. L - Liskov almashuvi
  4. I - interfeysni bo'lish
  5. D - bog'liqlikni teskari qilish
  6. SOLID ni ortiqcha qo'llash
  7. Xulosa

Naqshlarni yodlashdan oldin ularning manbasini bilish kerak. SOLID - beshta tamoyilning bosh harflari. Ular naqsh emas, balki naqshlar javob beradigan savollar.

HarfTamoyilQisqacha
SSingle ResponsibilityBir sinf - bir sabab bilan o'zgaradi
OOpen/ClosedKengaytirishga ochiq, o'zgartirishga yopiq
LLiskov SubstitutionVoris ota-sinf o'rnini buzmasdan egallashi kerak
IInterface SegregationKichik interfeys katta interfeysdan yaxshi
DDependency InversionKonkret sinfga emas, abstraksiyaga tayaning
SOLID va u tug'dirgan naqshlar S Bir mas'uliyat Bir sabab bilan o'zgaradi O Ochiq / yopiq Yangi kod qo'shiladi, eskisi tegilmaydi L Liskov almashuvi Voris va'dani buzmaydi I Interfeys bo'linishi Keraksiz metodni majburlamang D Bog'liqlik teskarisi Abstraksiyaga tayaning Facade Command Strategy Decorator Composite Template Method Adapter Proxy Factory Method Observer Naqsh - tamoyilni amalga oshirishning nomlangan usuli. Tamoyil - nima uchun, naqsh - qanday.
Har bir tamoyil bir necha naqshni tug'dirgan

S - bir mas'uliyat #

Quyidagi sinf uch ish qiladi: hisoblaydi, saqlaydi va xabar yuboradi.

PHP
final class BuyurtmaHammasi
{
    /** @var list<array{nom: string, narx: int, soni: int}> */
    private array $mahsulotlar = [];

    public function qosh(string $nom, int $narx, int $soni): void
    {
        $this->mahsulotlar[] = ['nom' => $nom, 'narx' => $narx, 'soni' => $soni];
    }

    public function jami(): int
    {
        $jami = 0;
        foreach ($this->mahsulotlar as $m) {
            $jami += $m['narx'] * $m['soni'];
        }
        return $jami;
    }

    public function saqla(): string
    {
        return 'INSERT INTO buyurtmalar (jami) VALUES (' . $this->jami() . ')';
    }

    public function xabarYubor(string $email): string
    {
        return "{$email} ga xat: buyurtmangiz {$this->jami()} so'm";
    }
}

$b = new BuyurtmaHammasi();
$b->qosh('Klaviatura', 250000, 1);
$b->qosh('Sichqoncha', 120000, 2);
echo $b->jami(), PHP_EOL;
echo $b->saqla(), PHP_EOL;
echo $b->xabarYubor('[email protected]'), PHP_EOL;
Natija
490000
INSERT INTO buyurtmalar (jami) VALUES (490000)
[email protected] ga xat: buyurtmangiz 490000 so'm
Bu sinf uch xil sabab bilan o'zgaradi
O'zgarishKim so'raydi
Chegirma qo'shildiBiznes
Baza MariaDB dan PostgreSQL ga o'tdiTexnik jamoa
Xat matni yangilandiMarketing

Uchala guruh bitta faylni tahrirlaydi. Bu konfliktlar, tasodifiy buzilishlar va "men buni tegmagandim" degan suhbatlarni keltirib chiqaradi.

"Bir mas'uliyat" degani "bir metod" degani emas. U - sinfning bir manfaatdor tomoni bo'lishi kerak degani.

Bo'lib tashlaymiz:

PHP
final class Buyurtma
{
    /** @var list<array{nom: string, narx: int, soni: int}> */
    private array $mahsulotlar = [];

    public function qosh(string $nom, int $narx, int $soni): void
    {
        $this->mahsulotlar[] = ['nom' => $nom, 'narx' => $narx, 'soni' => $soni];
    }

    public function jami(): int
    {
        $jami = 0;
        foreach ($this->mahsulotlar as $m) {
            $jami += $m['narx'] * $m['soni'];
        }
        return $jami;
    }
}

final class BuyurtmaOmbori
{
    public function saqla(Buyurtma $b): string
    {
        return 'INSERT INTO buyurtmalar (jami) VALUES (' . $b->jami() . ')';
    }
}

final class BuyurtmaXabarnomasi
{
    public function yubor(Buyurtma $b, string $email): string
    {
        return "{$email} ga xat: buyurtmangiz {$b->jami()} so'm";
    }
}
PHP
$buyurtma = new Buyurtma();
$buyurtma->qosh('Klaviatura', 250000, 1);
$buyurtma->qosh('Sichqoncha', 120000, 2);

echo (new BuyurtmaOmbori())->saqla($buyurtma), PHP_EOL;
echo (new BuyurtmaXabarnomasi())->yubor($buyurtma, '[email protected]'), PHP_EOL;
Natija
INSERT INTO buyurtmalar (jami) VALUES (490000)
[email protected] ga xat: buyurtmangiz 490000 so'm

Endi marketing BuyurtmaXabarnomasi ni, texnik jamoa BuyurtmaOmbori ni tahrirlaydi. Ular bir-biriga tegmaydi.

O - ochiq/yopiq #

Bu tamoyilni 1-bo'limda ko'rdik. Yana bir misol - chegirma qoidalari.

PHP
interface Chegirma
{
    public function nomi(): string;
    public function qollash(int $summa): int;
}

final class Yoq implements Chegirma
{
    public function nomi(): string { return 'chegirmasiz'; }
    public function qollash(int $summa): int { return $summa; }
}

final class FoizChegirma implements Chegirma
{
    public function __construct(private int $foiz) {}

    public function nomi(): string { return "{$this->foiz}% chegirma"; }

    public function qollash(int $summa): int
    {
        return $summa - intdiv($summa * $this->foiz, 100);
    }
}

final class BayramChegirma implements Chegirma
{
    public function nomi(): string { return 'bayram: 50 ming'; }

    public function qollash(int $summa): int
    {
        return max(0, $summa - 50000);
    }
}

$chegirmalar = [new Yoq(), new FoizChegirma(15), new BayramChegirma()];
foreach ($chegirmalar as $ch) {
    printf("%-18s %8d -> %d%s", $ch->nomi(), 490000,
        $ch->qollash(490000), PHP_EOL);
}
Natija
chegirmasiz          490000 -> 490000
15% chegirma         490000 -> 416500
bayram: 50 ming      490000 -> 440000
Yoq sinfiga e'tibor bering

"Chegirma yo'q" holatini null bilan emas, alohida sinf bilan ifodaladik. Bu Null Object naqshi.

Foydasi - chaqiruvchi tomonda if ($chegirma !== null) tekshiruvi kerak emas. Yoq obyekti interfeysni to'liq bajaradi, faqat hech narsa qilmaydi.

Bu kichik naqsh, lekin if larni ajoyib kamaytiradi.

L - Liskov almashuvi #

Eng tez-tez buziladigan tamoyil. Klassik misol - kvadrat va to'rtburchak.

PHP
class Toqrtburchak
{
    protected int $eni = 0;
    protected int $boyi = 0;

    public function eniQoy(int $eni): void { $this->eni = $eni; }
    public function boyiQoy(int $boyi): void { $this->boyi = $boyi; }
    public function yuza(): int { return $this->eni * $this->boyi; }
}

// Matematikada kvadrat - to'rtburchakning turi. Kodda esa?
class Kvadrat extends Toqrtburchak
{
    public function eniQoy(int $eni): void
    {
        $this->eni = $eni;
        $this->boyi = $eni;
    }

    public function boyiQoy(int $boyi): void
    {
        $this->eni = $boyi;
        $this->boyi = $boyi;
    }
}

function yuzaniTekshir(Toqrtburchak $shakl): string
{
    $shakl->eniQoy(5);
    $shakl->boyiQoy(4);
    $yuza = $shakl->yuza();
    $holat = $yuza === 20 ? 'TOGRI' : 'XATO';
    return sprintf('%-12s yuza=%2d  %s', $shakl::class, $yuza, $holat);
}

echo yuzaniTekshir(new Toqrtburchak()), PHP_EOL;
echo yuzaniTekshir(new Kvadrat()), PHP_EOL;
Natija
Toqrtburchak yuza=20  TOGRI
Kvadrat      yuza=16  XATO
Bu kod kompilyatsiyadan o'tadi, lekin noto'g'ri

yuzaniTekshir funksiyasi Toqrtburchak ni kutadi va uning va'dasiga tayanadi: "eni va boyini alohida qo'ysang, ko'paytmasini olasan".

Kvadrat bu va'dani buzdi. U tur jihatidan mos, lekin xulq-atvor jihatidan mos emas.

Liskov almashuvi shuni talab qiladi: agar B extends A bo'lsa, A ni kutayotgan har qanday kod B bilan ham to'g'ri ishlashi kerak.

Buzilish belgilari:

BelgiMisol
Voris metodni bo'sh qoldiradipublic function uch(): void {}
Voris istisno tashlaydithrow new BadMethodCallException()
Voris shartlarni kuchaytiradiOta manfiy sonni qabul qilardi, voris qilmaydi
Chaqiruvchida instanceof paydo bo'ladiif ($x instanceof Kvadrat)

Yechim odatda oddiy: meros o'rniga alohida turlar.

PHP
interface Shakl
{
    public function yuza(): int;
}

final readonly class Toqrtburchak2 implements Shakl
{
    public function __construct(private int $eni, private int $boyi) {}
    public function yuza(): int { return $this->eni * $this->boyi; }
}

final readonly class Kvadrat2 implements Shakl
{
    public function __construct(private int $tomon) {}
    public function yuza(): int { return $this->tomon ** 2; }
}

$shakllar = [new Toqrtburchak2(5, 4), new Kvadrat2(5)];
foreach ($shakllar as $sh) {
    printf("%-14s %d%s", $sh::class, $sh->yuza(), PHP_EOL);
}
Natija
Toqrtburchak2  20
Kvadrat2       25

readonly obyektni yaratilgandan keyin o'zgartirib bo'lmaydi - shu sababli "eni qo'ysam boyi ham o'zgaradi" muammosi umuman paydo bo'lmaydi.

I - interfeysni bo'lish #

PHP
// Juda "semiz" interfeys
interface Xodim
{
    public function kodYoz(): string;
    public function dizaynQil(): string;
    public function hisobotTayyorla(): string;
}

final class Dasturchi implements Xodim
{
    public function kodYoz(): string { return 'PHP kodi yozildi'; }

    public function dizaynQil(): string
    {
        throw new BadMethodCallException('Dasturchi dizayn qilmaydi');
    }

    public function hisobotTayyorla(): string
    {
        throw new BadMethodCallException('Dasturchi hisobot tayyorlamaydi');
    }
}

$d = new Dasturchi();
echo $d->kodYoz(), PHP_EOL;
try {
    echo $d->dizaynQil(), PHP_EOL;
} catch (BadMethodCallException $x) {
    echo 'Istisno: ', $x->getMessage(), PHP_EOL;
}
Natija
PHP kodi yozildi
Istisno: Dasturchi dizayn qilmaydi

Har bir vazifa uchun alohida interfeys:

PHP
interface Kodlovchi { public function kodYoz(): string; }
interface Dizayner { public function dizaynQil(): string; }
interface Hisobotchi { public function hisobotTayyorla(): string; }

final class Dasturchi2 implements Kodlovchi
{
    public function kodYoz(): string { return 'PHP kodi yozildi'; }
}

final class Rahbar implements Kodlovchi, Hisobotchi
{
    public function kodYoz(): string { return 'Kod ko\'rib chiqildi'; }
    public function hisobotTayyorla(): string { return 'Oylik hisobot tayyor'; }
}

/** @param list<Kodlovchi> $jamoa */
function kodJarayoni(array $jamoa): void
{
    foreach ($jamoa as $a) {
        printf("%-11s %s%s", $a::class, $a->kodYoz(), PHP_EOL);
    }
}

kodJarayoni([new Dasturchi2(), new Rahbar()]);
echo (new Rahbar())->hisobotTayyorla(), PHP_EOL;
Natija
Dasturchi2  PHP kodi yozildi
Rahbar      Kod ko'rib chiqildi
Oylik hisobot tayyor
Sinf bir nechta interfeysni bajarishi mumkin

Rahbar implements Kodlovchi, Hisobotchi - PHP bir nechta interfeysga ruxsat beradi (ko'p merosga esa yo'q).

Shuning uchun kichik interfeyslar yozish deyarli har doim foydali: ularni kerakligicha birlashtirish mumkin, katta interfeysni esa bo'lish qiyin.

Yaxshi interfeys ko'pincha bitta metod ga ega bo'ladi.

D - bog'liqlikni teskari qilish #

PHP
final class SmsYuboruvchi
{
    public function yubor(string $raqam, string $matn): string
    {
        return "SMS -> {$raqam}: {$matn}";
    }
}

final class QattiqBoglangan
{
    private SmsYuboruvchi $sms;

    public function __construct()
    {
        // sinf o'zi konkret bog'liqlikni yaratadi
        $this->sms = new SmsYuboruvchi();
    }

    public function xush(string $raqam): string
    {
        return $this->sms->yubor($raqam, 'Xush kelibsiz!');
    }
}

echo (new QattiqBoglangan())->xush('+998901112233'), PHP_EOL;
Natija
SMS -> +998901112233: Xush kelibsiz!
Bu sinfni sinab bo'lmaydi

QattiqBoglangan ni testda ishga tushirsangiz, u haqiqiy SMS yuborishga urinadi. Uni almashtirishning iloji yo'q, chunki new SmsYuboruvchi() konstruktor ichida qotib qolgan.

Muammo bitta so'zda: kim yaratadi. Sinf o'z bog'liqligini o'zi yaratsa, tashqaridan boshqarib bo'lmaydi.

PHP
interface Yuboruvchi
{
    public function yubor(string $manzil, string $matn): string;
}

final class Sms implements Yuboruvchi
{
    public function yubor(string $manzil, string $matn): string
    {
        return "SMS -> {$manzil}: {$matn}";
    }
}

final class Telegram implements Yuboruvchi
{
    public function yubor(string $manzil, string $matn): string
    {
        return "Telegram -> {$manzil}: {$matn}";
    }
}

// test uchun: hech qayerga yubormaydi, faqat yozib boradi
final class SoxtaYuboruvchi implements Yuboruvchi
{
    /** @var list<string> */
    public array $yuborilganlar = [];

    public function yubor(string $manzil, string $matn): string
    {
        $this->yuborilganlar[] = "{$manzil}|{$matn}";
        return 'soxta';
    }
}

final readonly class Xabarnoma
{
    // konkret sinf emas, interfeys
    public function __construct(private Yuboruvchi $yuboruvchi) {}

    public function xush(string $manzil): string
    {
        return $this->yuboruvchi->yubor($manzil, 'Xush kelibsiz!');
    }
}

echo (new Xabarnoma(new Sms()))->xush('+998901112233'), PHP_EOL;
echo (new Xabarnoma(new Telegram()))->xush('@husanboy'), PHP_EOL;

$soxta = new SoxtaYuboruvchi();
(new Xabarnoma($soxta))->xush('@nodira');
echo 'testda yozilgan: ', $soxta->yuborilganlar[0], PHP_EOL;
echo 'jami yuborildi: ', count($soxta->yuborilganlar), PHP_EOL;
Natija
SMS -> +998901112233: Xush kelibsiz!
Telegram -> @husanboy: Xush kelibsiz!
testda yozilgan: @nodira|Xush kelibsiz!
jami yuborildi: 1
"Teskari qilish" nima teskari bo'ldi

Odatdagi oqim: yuqori qatlam (Xabarnoma) pastki qatlamga (Sms) bog'liq.

Teskarisi: ikkalasi ham interfeysga bog'liq. Interfeys esa yuqori qatlamga tegishli - u nima kerakligini aytadi, pastki qatlam esa moslashadi.

Kim kimga bog'liqNatija
XabarnomaSmsSms o'zgarsa, Xabarnoma buziladi
XabarnomaYuboruvchiSmsSms almashadi, Xabarnoma tegilmaydi

Bog'liqlikni konstruktordan berish dependency injection deyiladi. Bu naqsh emas, oddiy usul - lekin naqshlarning yarmisi shunga tayanadi.

SOLID ni ortiqcha qo'llash #

PHP
// Faqat foydalanuvchi ismini formatlash kerak edi
function ismFormat(string $ism, string $familiya): string
{
    return trim($familiya . ' ' . mb_substr($ism, 0, 1) . '.');
}

echo ismFormat('Husanboy', 'Qodirov'), PHP_EOL;
echo ismFormat('Malika', 'Ergasheva'), PHP_EOL;
Natija
Qodirov H.
Ergasheva M.
Bu funksiyaga SOLID qo'llanmaydi

Uni IsmFormatlovchiInterfeysi, OddiyIsmFormatlovchi, IsmFormatlovchiFabrikasi ga bo'lish - bu SOLID emas, kargo-kult.

Tamoyillar o'zgarish joylari uchun. Agar kod bir yildan beri o'zgarmagan bo'lsa va o'zgarishi kutilmasa - u yerda abstraksiya faqat zarar keltiradi.

Qachon qo'llash kerakligini aniqlash uchun savol bering:

Bu kodning qaysi qismi kelasi olti oyda o'zgaradi?

Javob "hech qaysi" bo'lsa - qo'ymang.

Amaliy topshiriq
  1. Uch ish qiladigan sinf yozing va uni uchga bo'ling.
  2. Bo'lgandan keyin har bir sinfni kim tahrirlashini yozing.
  3. Chegirma interfeysiga to'rtinchi qoida qo'shing.
  4. Yoq (Null Object) sinfi qanday if ni yo'q qilganini tushuntiring.
  5. Kvadrat extends Toqrtburchak misolini ishga tushiring.
  6. Nima uchun readonly versiyada muammo yo'qligini yozing.
  7. Semiz interfeysni uchta kichik interfeysga bo'ling.
  8. SoxtaYuboruvchi ga o'xshash test dublyorini o'zingiz yozing.
  9. Konstruktor ichida new yozilgan joyni kodingizdan toping.
  10. SOLID kerak bo'lmagan uchta funksiyani sanab bering.

Xulosa #

  • S: sinf bir manfaatdor tomon uchun o'zgarishi kerak.
  • O: yangi imkoniyat yangi fayl bilan qo'shilsin.
  • L: voris ota-sinfning va'dasini buzmasin.
  • Liskov buzilishining belgilari: bo'sh metod, istisno, instanceof.
  • Meros muammo tug'dirsa - alohida turlar va readonly ko'pincha yechadi.
  • I: kichik interfeys yaxshi, chunki ularni birlashtirish oson.
  • D: konkret sinfga emas, interfeysga tayaning.
  • Bog'liqlikni konstruktordan berish testni mumkin qiladi.
  • Null Object - null tekshiruvlarini yo'qotadigan kichik naqsh.
  • SOLID o'zgarish kutilgan joylarda qo'llanadi, hamma yerda emas.

Keyingi bo'limda birinchi naqsh - Factory Method 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.