PHP / Interface ба abstract класс

Interface ба abstract класс

Өвлөлт нь нэг эх классаас л өвлөж болдог хязгаартай. Гэвч бодит дэлхийд нэг объект олон үүрэг гүйцэтгэдэг — жишээлбэл, Нохой нь "Амьтан" мөн, "Гэрийн тэжээвэр" мөн. Interface нь яг энэ асуудлыг шийддэг: нэг класс хэдэн ч interface-г хэрэгжүүлж болно. Abstract класс нь interface болон жирийн классын дундах зүйл — зарим аргыг хэрэгжүүлж, зарим аргыг дэд классд үлдээдэг.

Interface — гэрээ тодорхойлох

interface нь аргуудын гарын үсэг (signature) тодорхойлдог — биелэлт агуулдаггүй. Классыг implements гэж хэрэгжүүлэхдээ бүх аргыг заавал бодитоор тодорхойлох ёстой:

php
<?php
interface Дуугарагч {
    public function дуугарах(): string;
}

interface Хөдөлгөөнт {
    public function хөдөлгөөн(): string;
    public function зогсох(): void;
}

// Нэг класс хэдэн ч interface хэрэгжүүлж болно
class Нохой implements Дуугарагч, Хөдөлгөөнт {
    public function дуугарах(): string {
        return "Хав хав!";
    }

    public function хөдөлгөөн(): string {
        return "Гүйж байна";
    }

    public function зогсох(): void {
        echo "Нохой зогслоо.\n";
    }
}

$нохой = new Нохой();
echo $нохой->дуугарах();  // Хав хав!
echo $нохой->хөдөлгөөн(); // Гүйж байна
$нохой->зогсох();          // Нохой зогслоо.

// instanceof шалгалт interface-тэй ч ажилладаг
echo ($нохой instanceof Дуугарагч) ? "Дуугарагч мөн\n" : "";
echo ($нохой instanceof Хөдөлгөөнт) ? "Хөдөлгөөнт мөн\n" : "";
?>

Interface-г хэрэгжүүлэхдээ нэг ч аргыг орхивол PHP алдаа шиддэг. Энэ нь "гэрээ" шиг ажилладаг — хэрэгжүүлсэн класс бүх үүргээ заавал биелүүлнэ.

Interface-г тип болгон ашиглах

Interface-ийн хамгийн том давуу тал нь функцийн параметр болгон ашиглах явдал юм. Ямар ч класс тэр interface-г хэрэгжүүлсэн л бол дамжуулж болно:

php
<?php
interface Төлбөр_Систем {
    public function төлбөр_хийх(float $дүн): bool;
    public function нэр(): string;
}

class QPay implements Төлбөр_Систем {
    public function төлбөр_хийх(float $дүн): bool {
        echo "QPay-р {$дүн}₮ төлбөр хийлээ.\n";
        return true;
    }

    public function нэр(): string {
        return "QPay";
    }
}

class Карт implements Төлбөр_Систем {
    public function төлбөр_хийх(float $дүн): bool {
        echo "Картаар {$дүн}₮ төлбөр хийлээ.\n";
        return true;
    }

    public function нэр(): string {
        return "Банкны карт";
    }
}

// Функц нь interface-г параметр болгон авна — аль ч хэрэгжүүлэлт ажиллана
function гүйлгээ_хийх(Төлбөр_Систем $систем, float $дүн): void {
    if ($систем->төлбөр_хийх($дүн)) {
        echo $систем->нэр() . " амжилттай.\n";
    }
}

гүйлгээ_хийх(new QPay(), 15_000);  // QPay-р 15000₮ төлбөр хийлээ.
гүйлгээ_хийх(new Карт(), 50_000);  // Картаар 50000₮ төлбөр хийлээ.
?>

Шинэ төлбөрийн систем нэмэхэд гүйлгээ_хийх() функцийг огт өөрчлөхгүйгээр шинэ класс нэмэхэд хангалттай — энэ бол мэргэжлийн програмчлалын гол зарчим.

Abstract класс — дунд зам

abstract класс нь interface болон жирийн классын хооронд оршдог. Зарим аргыг бодитоор хэрэгжүүлж, зарим аргыг abstract гэж тэмдэглэн дэд классд үлдээж болно. abstract классаас шууд объект үүсгэж болохгүй:

php
<?php
abstract class Тайлан {
    // Бодит арга — бүх дэд класс ашиглана
    public function гарчиг(): void {
        echo "=== " . $this->гарчиг_текст() . " ===\n";
    }

    public function хэвлэх(): void {
        $this->гарчиг();
        $this->агуулга(); // дэд класс хэрэгжүүлнэ
        echo "\n--- Тайлан дуусав ---\n";
    }

    // Abstract арга — дэд класс заавал хэрэгжүүлэх ёстой
    abstract protected function гарчиг_текст(): string;
    abstract protected function агуулга(): void;
}

class Борлуулалтын_тайлан extends Тайлан {
    public function __construct(private float $нийт) {}

    protected function гарчиг_текст(): string {
        return "Борлуулалтын тайлан";
    }

    protected function агуулга(): void {
        echo "Нийт борлуулалт: " . number_format($this->нийт) . "₮\n";
    }
}

class Хэрэглэгчийн_тайлан extends Тайлан {
    public function __construct(private int $тоо) {}

    protected function гарчиг_текст(): string {
        return "Хэрэглэгчийн тайлан";
    }

    protected function агуулга(): void {
        echo "Нийт хэрэглэгч: {$this->тоо}\n";
    }
}

(new Борлуулалтын_тайлан(5_800_000))->хэвлэх();
(new Хэрэглэгчийн_тайлан(142))->хэвлэх();

// new Тайлан(); // АЛДАА — abstract класс шууд үүсгэж болохгүй
?>

Interface ба abstract — хэзээ алийг ашиглах вэ?

| | Interface | Abstract класс | |---|---|---| | Биелэлт агуулах | Үгүй | Тийм (зарим арга) | | Хэдийг хэрэгжүүлэх | Хэдийг ч | Зөвхөн нэгийг | | Шинж чанар (property) | Зөвхөн constant | Тийм | | Ашиглах тохиолдол | Үүрэг тодорхойлох | Нийтлэг суурь бүтээх |

php
<?php
// Interface — "юу хийх чадвартай" гэдгийг тодорхойлно
interface Экспортлох_Боломжтой {
    public function csv_экспорт(): string;
    public function json_экспорт(): string;
}

// Abstract класс — нийтлэг логикийг агуулна
abstract class Тооцоолол_Суурь {
    abstract public function тооцоолох(): float;

    // Нийтлэг форматлах логик
    public function форматлах(): string {
        return number_format($this->тооцоолох(), 2) . "₮";
    }
}

// Хоёуланг хамт хэрэглэж болно
class НӨАТ_Тооцоолол extends Тооцоолол_Суурь implements Экспортлох_Боломжтой {
    public function __construct(private float $үнэ) {}

    public function тооцоолох(): float {
        return $this->үнэ * 0.1; // 10% НӨАТ
    }

    public function csv_экспорт(): string {
        return "үнэ,нөат\n{$this->үнэ}," . $this->тооцоолох();
    }

    public function json_экспорт(): string {
        return json_encode(['үнэ' => $this->үнэ, 'нөат' => $this->тооцоолох()]);
    }
}

$нөат = new НӨАТ_Тооцоолол(100_000);
echo $нөат->форматлах();     // 10,000.00₮
echo $нөат->csv_экспорт();
?>

Дараагийн хичээлд:

PHP-н өвөрмөц боломж болох trait-ийг судална. Trait нь нэг классаас л өвлөж болдог хязгаарыг тойрч, олон классд нийтлэг код хуваалцах боломж олгодог. "Хуулж оруулах" гэсэн утгаараа interface-ээс яагаад ялгаатай болохыг жишээгээр үзнэ.