13-bo‘lim

Vorislik, interfeys va trait

Abstrakt sinflar, interfeyslar, trait lar va nima uchun kompozitsiya vorislikdan ko'ra afzal.

🕑 9 daqiqa o‘qish 📄 623 so‘z 👁 1 marta ko‘rilgan
Ushbu bo‘lim mundarijasi
  1. Interfeys - kelishuv
  2. Abstrakt sinf - umumiy kod bilan
  3. trait - kodni bo'lishish
  4. Kompozitsiya - ko'pincha yaxshiroq
  5. Xulosa

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 #

PHP
// Har bir misol qat'iy turlar rejimida ishlaydi.
PHP
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";
Natija
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)
Interfeysga bog'laning, sinfga emas

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:

InterfeysAbstrakt sinf
Xossa saqlaydimiYo'q (faqat doimiy)Ha
Usul tanasiYo'qBo'lishi mumkin
Nechtasini olish mumkinCheksizFaqat 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 #

PHP
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";
Natija
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
Shablon usuli naqshi

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:

VaziyatTanlov
Faqat kelishuv kerakInterfeys
Umumiy kod ham borAbstrakt sinf
Bir necha kelishuv kerakInterfeys
Umumiy holat (xossa) borAbstrakt sinf

Ko'p holatda ikkalasi birga ishlatiladi: interfeys kelishuvni belgilaydi, abstrakt sinf esa uning umumiy qismini beradi.

Vorislik kerakmi - ikki savol "B - bu A ning bir turimi?" (is-a) yo'q ha KOMPOZITSIYA ishlating "Menda bor" munosabati - a'zo qiling. class Mashina { private Dvigatel $d; } Aksariyat holatda TO'G'RI tanlov Ikkinchi savol "Vorisni asos o'rniga qo'yganda kod BUZILMAYDIMI?" (Liskov) Buzilsa - bu vorislik emas Klassik xato: Kvadrat extends Turtburchak Matematikada kvadrat - to'rtburchak. Kodda esa setKenglik() kvadratning balandligini ham buzadi. Uchta vosita, uchta maqsad interface - kelishuv abstract - umumiy kod trait - qayta ishlatish Ularni chalkashtirmang - har birining o'z o'rni bor.
Vorislik - interfeys uchun, kod qayta ishlatish uchun emas

trait - kodni bo'lishish #

PHP
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";
Natija
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 qilinadi

trait kodni nusxa-ko'chirish mexanizmi: PHP uni kompilyatsiya paytida sinf ichiga ko'chiradi.

Bu bir necha muammo tug'diradi:

MuammoIzoh
Yashirin bog'liqliktrait sinf xossalarini talab qilishi mumkin
Nom to'qnashuviIkki trait bir xil usul bersa - xato
Turga aylanmayditrait ni tur sifatida ishlatib bo'lmaydi
Sinov qiyinAlohida sinab bo'lmaydi

Uchinchi qator ayniqsa muhim. Buni yozib bo'lmaydi:

PHP
function f(VaqtBelgisi $x) { ... }     // XATO

Shuning uchun keng tarqalgan naqsh - interfeys + trait birgalikda:

PHP
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 #

PHP
// 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";
Natija
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.
Amaliy topshiriq
  1. Tolov interfeysini yozing va uchta sinfda amalga oshiring.
  2. Interfeysdagi usulni sinfda yozmang - qanday xato chiqadi?
  3. Interfeysni qabul qiluvchi funksiya yozing va uchala sinfni bering.
  4. Abstrakt sinf bilan shablon usuli naqshini qo'llang.
  5. final usulni vorisda qayta aniqlashga urinib ko'ring.
  6. Abstrakt sinfdan obyekt yaratishga urining.
  7. Ikkita trait yozing va bitta sinfda ikkalasini ishlating.
  8. trait ni funksiya parametri turi sifatida yozib ko'ring.
  9. Interfeys + trait birikmasini qo'llang.
  10. ArrayObject dan voris Stek yozing va get_class_methods bilan 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 qadamlar abstract.
  • trait - kompilyatsiya paytidagi nusxa-ko'chirish; u tur yaratmaydi.
  • trait ni 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.

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.