13-bo‘lim
Vorislik, interfeys va trait
Abstrakt sinflar, interfeyslar, trait lar va nima uchun kompozitsiya vorislikdan ko'ra afzal.
Ushbu bo‘lim mundarijasi
Vorislik - OOP ning eng ko'p suiiste'mol qilinadigan vositasi. Bu bo'limda uni qachon ishlatish va - eng muhimi - qachon ishlatmaslik kerakligini ko'ramiz.
Interfeys - kelishuv #
// Har bir misol qat'iy turlar rejimida ishlaydi.
interface Tolov
{
public function tolash(int $tiyin): bool;
public function nomi(): string;
public function komissiya(int $tiyin): int;
}
final class Karta implements Tolov
{
private int $balans;
public function __construct(int $boshlangich)
{
$this->balans = $boshlangich;
}
public function tolash(int $tiyin): bool
{
$jami = $tiyin + $this->komissiya($tiyin);
if ($jami > $this->balans) {
return false;
}
$this->balans -= $jami;
return true;
}
public function nomi(): string { return "karta"; }
public function komissiya(int $tiyin): int { return (int) round($tiyin * 0.01); }
public function balans(): int { return $this->balans; }
}
final class Naqd implements Tolov
{
public function tolash(int $tiyin): bool { return $tiyin > 0; }
public function nomi(): string { return "naqd"; }
public function komissiya(int $tiyin): int { return 0; }
}
final class Payme implements Tolov
{
public function tolash(int $tiyin): bool { return $tiyin >= 100000; }
public function nomi(): string { return "payme"; }
public function komissiya(int $tiyin): int { return (int) round($tiyin * 0.005); }
}
// Funksiya INTERFEYSNI biladi, aniq sinfni emas
function xaridQil(Tolov $usul, int $tiyin): string
{
$ok = $usul->tolash($tiyin);
return sprintf(
"%-7s %12s komissiya %10s %s",
$usul->nomi(),
number_format($tiyin / 100, 2, ",", " "),
number_format($usul->komissiya($tiyin) / 100, 2, ",", " "),
$ok ? "qabul qilindi" : "RAD ETILDI"
);
}
$usullar = [new Karta(100000000), new Naqd(), new Payme()];
foreach ($usullar as $u) {
echo xaridQil($u, 30000000) . "\n";
}
echo "\n";
echo xaridQil(new Payme(), 50000) . " (chegaradan kam)\n";
karta 300 000,00 komissiya 3 000,00 qabul qilindi
naqd 300 000,00 komissiya 0,00 qabul qilindi
payme 300 000,00 komissiya 1 500,00 qabul qilindi
payme 500,00 komissiya 2,50 RAD ETILDI (chegaradan kam)
xaridQil funksiyasi Karta, Naqd yoki Payme
haqida hech narsa bilmaydi. U faqat Tolov
kelishuvini biladi.
Natijada ertaga Click yoki Uzum qo'shsangiz, bu
funksiyaga hech qanday o'zgartirish kerak emas.
Bu tamoyil shunday ataladi:
Amalga oshirishga emas, abstraksiyaga bog'laning.
Interfeys sinfdan nimasi bilan farq qiladi:
| Interfeys | Abstrakt sinf | |
|---|---|---|
| Xossa saqlaydimi | Yo'q (faqat doimiy) | Ha |
| Usul tanasi | Yo'q | Bo'lishi mumkin |
| Nechtasini olish mumkin | Cheksiz | Faqat bitta |
| Ma'nosi | "Shu ishni qila oladi" | "Shu turdagi narsa" |
Uchinchi qator eng muhimi: PHP da bitta sinf faqat bitta sinfdan voris bo'ladi, lekin istalgancha interfeysni amalga oshiradi.
Shuning uchun yangi kodda birinchi tanlov - interfeys.
Abstrakt sinf - umumiy kod bilan #
abstract class Hisobot
{
public function __construct(protected readonly array $qatorlar) {}
// Voris SINFLAR MAJBURIY amalga oshiradi
abstract protected function sarlavha(): string;
abstract protected function qatorniChiz(array $qator): string;
// Umumiy mantiq - bir marta yozilgan
final public function chiqar(): string
{
$natija = [$this->sarlavha(), str_repeat("-", 44)];
foreach ($this->qatorlar as $q) {
$natija[] = $this->qatorniChiz($q);
}
$natija[] = str_repeat("-", 44);
$natija[] = sprintf("Jami: %d qator", count($this->qatorlar));
return implode("\n", $natija);
}
}
final class SotuvHisoboti extends Hisobot
{
protected function sarlavha(): string
{
return sprintf("%-16s %10s %14s", "MAHSULOT", "SONI", "SUMMA");
}
protected function qatorniChiz(array $q): string
{
return sprintf(
"%-16s %10d %14s",
$q["nom"],
$q["soni"],
number_format($q["summa"], 0, ",", " ")
);
}
}
final class QisqaHisobot extends Hisobot
{
protected function sarlavha(): string
{
return "QISQA RO'YXAT";
}
protected function qatorniChiz(array $q): string
{
return " - {$q['nom']} ({$q['soni']} dona)";
}
}
$malumot = [
["nom" => "Klaviatura", "soni" => 12, "summa" => 4200000],
["nom" => "Sichqoncha", "soni" => 40, "summa" => 4800000],
["nom" => "Monitor", "soni" => 5, "summa" => 12000000],
];
echo (new SotuvHisoboti($malumot))->chiqar() . "\n\n";
echo (new QisqaHisobot($malumot))->chiqar() . "\n";
MAHSULOT SONI SUMMA
--------------------------------------------
Klaviatura 12 4 200 000
Sichqoncha 40 4 800 000
Monitor 5 12 000 000
--------------------------------------------
Jami: 3 qator
QISQA RO'YXAT
--------------------------------------------
- Klaviatura (12 dona)
- Sichqoncha (40 dona)
- Monitor (5 dona)
--------------------------------------------
Jami: 3 qator
Hisobot sinfi algoritm skeletini belgilaydi
(chiqar), lekin ayrim qadamlarni vorislarga
qoldiradi.
Bu shablon usuli (template method) naqshi va u abstrakt sinfning eng to'g'ri qo'llanishi.
E'tibor bering, chiqar() final deb belgilangan.
Bu ataylab: vorislar alohida qadamlarni o'zgartirishi
mumkin, lekin umumiy tartibni buza olmaydi.
Qachon abstrakt sinf, qachon interfeys:
| Vaziyat | Tanlov |
|---|---|
| Faqat kelishuv kerak | Interfeys |
| Umumiy kod ham bor | Abstrakt sinf |
| Bir necha kelishuv kerak | Interfeys |
| Umumiy holat (xossa) bor | Abstrakt sinf |
Ko'p holatda ikkalasi birga ishlatiladi: interfeys kelishuvni belgilaydi, abstrakt sinf esa uning umumiy qismini beradi.
trait - kodni bo'lishish #
trait VaqtBelgisi
{
private ?string $yaratilgan = null;
private ?string $ozgartirilgan = null;
public function vaqtBelgila(string $sana): void
{
$this->yaratilgan ??= $sana;
$this->ozgartirilgan = $sana;
}
public function vaqtMatni(): string
{
return sprintf(
"yaratilgan: %s, o'zgartirilgan: %s",
$this->yaratilgan ?? "-",
$this->ozgartirilgan ?? "-"
);
}
}
trait Jurnal
{
private array $yozuvlar = [];
public function jurnalgaYoz(string $xabar): void
{
$this->yozuvlar[] = $xabar;
}
public function jurnal(): array
{
return $this->yozuvlar;
}
}
final class Maqola
{
use VaqtBelgisi;
use Jurnal;
public function __construct(public readonly string $sarlavha) {}
public function nashrQil(string $sana): void
{
$this->vaqtBelgila($sana);
$this->jurnalgaYoz("nashr qilindi: {$sana}");
}
}
final class Mahsulot
{
use VaqtBelgisi; // faqat bittasi kerak
public function __construct(public readonly string $nom) {}
}
$m = new Maqola("Zamonaviy PHP");
$m->nashrQil("2024-09-01");
$m->vaqtBelgila("2024-09-05");
$m->jurnalgaYoz("tahrirlandi");
echo "Maqola: {$m->sarlavha}\n";
echo " {$m->vaqtMatni()}\n";
echo " jurnal:\n";
foreach ($m->jurnal() as $y) {
echo " - {$y}\n";
}
echo "\n";
$p = new Mahsulot("Klaviatura");
$p->vaqtBelgila("2024-08-15");
echo "Mahsulot: {$p->nom}\n";
echo " {$p->vaqtMatni()}\n";
echo "\n";
echo "Maqola VaqtBelgisi dan foydalanadimi: "
. (in_array(VaqtBelgisi::class, class_uses(Maqola::class), true) ? "ha" : "yo'q") . "\n";
echo "Mahsulot Jurnal dan foydalanadimi: "
. (in_array(Jurnal::class, class_uses(Mahsulot::class), true) ? "ha" : "yo'q") . "\n";
Maqola: Zamonaviy PHP
yaratilgan: 2024-09-01, o'zgartirilgan: 2024-09-05
jurnal:
- nashr qilindi: 2024-09-01
- tahrirlandi
Mahsulot: Klaviatura
yaratilgan: 2024-08-15, o'zgartirilgan: 2024-08-15
Maqola VaqtBelgisi dan foydalanadimi: ha
Mahsulot Jurnal dan foydalanadimi: yo'q
trait - kuchli, lekin oson suiiste'mol qilinaditrait kodni nusxa-ko'chirish mexanizmi: PHP uni
kompilyatsiya paytida sinf ichiga ko'chiradi.
Bu bir necha muammo tug'diradi:
| Muammo | Izoh |
|---|---|
| Yashirin bog'liqlik | trait sinf xossalarini talab qilishi mumkin |
| Nom to'qnashuvi | Ikki trait bir xil usul bersa - xato |
| Turga aylanmaydi | trait ni tur sifatida ishlatib bo'lmaydi |
| Sinov qiyin | Alohida sinab bo'lmaydi |
Uchinchi qator ayniqsa muhim. Buni yozib bo'lmaydi:
function f(VaqtBelgisi $x) { ... } // XATO
Shuning uchun keng tarqalgan naqsh - interfeys + trait birgalikda:
interface VaqtliMi { public function vaqtMatni(): string; }
trait VaqtBelgisi { /* amalga oshirish */ }
final class Maqola implements VaqtliMi {
use VaqtBelgisi;
}
Endi tur ham bor, kod takrori ham yo'q.
Nom to'qnashuvini insteadof va as bilan hal qilish
mumkin, lekin bunga ehtiyoj paydo bo'lsa - odatda
dizaynni qayta ko'rib chiqish kerak.
Kompozitsiya - ko'pincha yaxshiroq #
// YOMON: Stek vector dan voris qilinsa, hamma amal ochiladi
// final class Stek extends ArrayObject {} // push, offsetSet, ...
// YAXSHI: kompozitsiya - faqat kerakli amal ochiladi
final class Stek
{
private array $elementlar = [];
public function qoy(mixed $q): void
{
$this->elementlar[] = $q;
}
public function ol(): mixed
{
if ($this->elementlar === []) {
throw new UnderflowException("Stek bo'sh");
}
return array_pop($this->elementlar);
}
public function tepa(): mixed
{
return $this->elementlar[array_key_last($this->elementlar)] ?? null;
}
public function boshmi(): bool { return $this->elementlar === []; }
public function soni(): int { return count($this->elementlar); }
}
$s = new Stek();
foreach ([10, 20, 30] as $q) {
$s->qoy($q);
}
echo "Elementlar: {$s->soni()}\n";
echo "Tepasi: {$s->tepa()}\n";
echo "Chiqarish: ";
$chiqqan = [];
while (!$s->boshmi()) {
$chiqqan[] = $s->ol();
}
echo implode(" ", $chiqqan) . "\n";
try {
$s->ol();
} catch (UnderflowException $x) {
echo "Bo'sh stekdan olish: " . $x->getMessage() . "\n";
}
echo "\n";
$usullar = get_class_methods(Stek::class);
sort($usullar);
echo "Stek ochadigan amallar (" . count($usullar) . " ta): "
. implode(", ", $usullar) . "\n";
echo "ArrayObject dan voris bo'lganda 25 dan ortiq amal ochilardi.\n";
Elementlar: 3
Tepasi: 30
Chiqarish: 30 20 10
Bo'sh stekdan olish: Stek bo'sh
Stek ochadigan amallar (5 ta): boshmi, ol, qoy, soni, tepa
ArrayObject dan voris bo'lganda 25 dan ortiq amal ochilardi.
Tolovinterfeysini yozing va uchta sinfda amalga oshiring.- Interfeysdagi usulni sinfda yozmang - qanday xato chiqadi?
- Interfeysni qabul qiluvchi funksiya yozing va uchala sinfni bering.
- Abstrakt sinf bilan shablon usuli naqshini qo'llang.
finalusulni vorisda qayta aniqlashga urinib ko'ring.- Abstrakt sinfdan obyekt yaratishga urining.
- Ikkita
traityozing va bitta sinfda ikkalasini ishlating. traitni funksiya parametri turi sifatida yozib ko'ring.- Interfeys +
traitbirikmasini qo'llang. ArrayObjectdan vorisStekyozing vaget_class_methodsbilan solishtiring.
Xulosa #
- Interfeys - kelishuv: "shu ishni qila oladi"; sinf istalgancha interfeysni oladi.
- PHP da sinf faqat bitta sinfdan voris bo'ladi - shuning uchun birinchi tanlov interfeys.
- Amalga oshirishga emas, abstraksiyaga bog'laning - yangi sinf qo'shish mavjud kodni buzmasin.
- Abstrakt sinf umumiy kod va holat ham berishi kerak bo'lganda ishlatiladi.
- Shablon usuli: algoritm skeleti
final, alohida qadamlarabstract. trait- kompilyatsiya paytidagi nusxa-ko'chirish; u tur yaratmaydi.traitni funksiya parametri turi sifatida ishlatib bo'lmaydi.- Keng tarqalgan naqsh - interfeys + trait: tur ham, umumiy kod ham bo'ladi.
- Vorislikdan oldin savol bering: "B - bu A ning turimi?" Yo'q bo'lsa - kompozitsiya.
- Kompozitsiya faqat kerakli amallarni ochadi; vorislik esa hammasini.
Keyingi bo'limda nomlar fazosi va avtoyuklashni - loyihani fayllarga to'g'ri taqsimlashni o'rganamiz.
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.