2-bo‘lim
SOLID tamoyillari
Deyarli har bir naqsh SOLID ning bir yoki ikki tamoyilidan kelib chiqadi.
Ushbu bo‘lim mundarijasi
Naqshlarni yodlashdan oldin ularning manbasini bilish kerak. SOLID - beshta tamoyilning bosh harflari. Ular naqsh emas, balki naqshlar javob beradigan savollar.
| Harf | Tamoyil | Qisqacha |
|---|---|---|
| S | Single Responsibility | Bir sinf - bir sabab bilan o'zgaradi |
| O | Open/Closed | Kengaytirishga ochiq, o'zgartirishga yopiq |
| L | Liskov Substitution | Voris ota-sinf o'rnini buzmasdan egallashi kerak |
| I | Interface Segregation | Kichik interfeys katta interfeysdan yaxshi |
| D | Dependency Inversion | Konkret sinfga emas, abstraksiyaga tayaning |
S - bir mas'uliyat #
Quyidagi sinf uch ish qiladi: hisoblaydi, saqlaydi va xabar yuboradi.
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;
490000
INSERT INTO buyurtmalar (jami) VALUES (490000)
[email protected] ga xat: buyurtmangiz 490000 so'm
| O'zgarish | Kim so'raydi |
|---|---|
| Chegirma qo'shildi | Biznes |
| Baza MariaDB dan PostgreSQL ga o'tdi | Texnik jamoa |
| Xat matni yangilandi | Marketing |
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:
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";
}
}
$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;
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.
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);
}
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.
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;
Toqrtburchak yuza=20 TOGRI
Kvadrat yuza=16 XATO
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:
| Belgi | Misol |
|---|---|
| Voris metodni bo'sh qoldiradi | public function uch(): void {} |
| Voris istisno tashlaydi | throw new BadMethodCallException() |
| Voris shartlarni kuchaytiradi | Ota manfiy sonni qabul qilardi, voris qilmaydi |
Chaqiruvchida instanceof paydo bo'ladi | if ($x instanceof Kvadrat) |
Yechim odatda oddiy: meros o'rniga alohida turlar.
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);
}
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 #
// 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;
}
PHP kodi yozildi
Istisno: Dasturchi dizayn qilmaydi
Har bir vazifa uchun alohida interfeys:
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;
Dasturchi2 PHP kodi yozildi
Rahbar Kod ko'rib chiqildi
Oylik hisobot tayyor
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 #
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;
SMS -> +998901112233: Xush kelibsiz!
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.
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;
SMS -> +998901112233: Xush kelibsiz!
Telegram -> @husanboy: Xush kelibsiz!
testda yozilgan: @nodira|Xush kelibsiz!
jami yuborildi: 1
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'liq | Natija |
|---|---|
Xabarnoma → Sms | Sms o'zgarsa, Xabarnoma buziladi |
Xabarnoma → Yuboruvchi ← Sms | Sms 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 #
// 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;
Qodirov H.
Ergasheva M.
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.
- Uch ish qiladigan sinf yozing va uni uchga bo'ling.
- Bo'lgandan keyin har bir sinfni kim tahrirlashini yozing.
Chegirmainterfeysiga to'rtinchi qoida qo'shing.Yoq(Null Object) sinfi qandayifni yo'q qilganini tushuntiring.Kvadrat extends Toqrtburchakmisolini ishga tushiring.- Nima uchun
readonlyversiyada muammo yo'qligini yozing. - Semiz interfeysni uchta kichik interfeysga bo'ling.
SoxtaYuboruvchiga o'xshash test dublyorini o'zingiz yozing.- Konstruktor ichida
newyozilgan joyni kodingizdan toping. - 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
readonlyko'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-nulltekshiruvlarini yo'qotadigan kichik naqsh.- SOLID o'zgarish kutilgan joylarda qo'llanadi, hamma yerda emas.
Keyingi bo'limda birinchi naqsh - Factory Method 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.