Kodokon kodokon.com

الوراثة، الواجهات، السمات والأصناف المجرّدة

اختر أداة التصميم المناسبة - عقد، أو هيكل خوارزمية، أو إعادة استخدام أفقية - بمعرفة مفاضلات كل منها.

9 دقيقة · 3 أسئلة

افتح هذا الدرس في Kodokon

تمنحك PHP أربع أدوات تصميم تتداخل جزئيًا: الوراثة الملموسة، والأصناف المجرّدة، والواجهات، والسمات. ويتلخّص الاختيار في سؤال واحد: ما الذي تريد مشاركته؟ عقد: واجهة. عقد مع هيكل تنفيذ: صنف مجرّد. كود مشترك بين أصناف لا رابط هرمي بينها: سمة. أما الوراثة الملموسة، فيصعب تبريرها أكثر فأكثر: فهي تخلق اقترانًا وثيقًا، ولا تسمح PHP إلا بأب واحد.

PHP
<?php

declare(strict_types=1);

abstract class Exporter
{
    public function export(array $rows): string
    {
        return $this->head() . $this->body($rows);
    }

    abstract protected function head(): string;

    abstract protected function body(
        array $rows
    ): string;
}

final class CsvExporter extends Exporter
{
    protected function head(): string
    {
        return "id;name\n";
    }

    protected function body(array $rows): string
    {
        $lines = array_map(
            fn (array $r): string => implode(';', $r),
            $rows,
        );

        return implode("\n", $lines);
    }
}

$csv = new CsvExporter();
echo $csv->export([[1, 'Ada'], [2, 'Linus']]);
دالة القالب (template method): الأب يثبّت الخوارزمية، والأبناء يوفّرون التفاصيل.

الواجهة هي العقد الصِّرف: تواقيع وثوابت، دون حالة ودون تنفيذ. وميزتها الحاسمة على الصنف المجرّد: أن الصنف يمكنه تنفيذ عدة واجهات، وأن أيّ صنف قائم يمكنه تبنّي العقد لاحقًا. أما الصنف المجرّد، في المقابل، فيحمل حالة وكودًا مشتركًا، لكنه يستهلك خانة الوراثة الوحيدة لديك. وعمليًا: حدِّد أنواع وسائطك بواجهات (Cache، لا ArrayCache) لتتمكن من استبدال التنفيذات، في الاختبارات كما في الإنتاج.

PHP
<?php

declare(strict_types=1);

interface Cache
{
    public function get(string $key): ?string;

    public function set(
        string $key,
        string $value
    ): void;
}

final class ArrayCache implements Cache
{
    private array $items = [];

    public function get(string $key): ?string
    {
        return $this->items[$key] ?? null;
    }

    public function set(
        string $key,
        string $value
    ): void {
        $this->items[$key] = $value;
    }
}

$cache = new ArrayCache();
$cache->set('lang', 'PHP');
echo $cache->get('lang');
الكود العميل يعتمد على Cache: يبقى التنفيذ قابلًا للاستبدال.

تحلّ السمات المشاركة الأفقية: أي حقن الدوال نفسها في أصناف لا سلف مشترك بينها. وعندما توفّر سمتان دالة بالاسم نفسه، ترفض PHP أن تختار عنك وتطالب بحل صريح: تحدّد insteadof النسخة التي تُبقيها، وتنشئ as اسمًا بديلًا (alias) لإبقاء الأخرى في المتناول. وهذا إسهاب مقصود - إذ يجب أن يبقى التعارض ظاهرًا في الكود.

PHP
<?php

declare(strict_types=1);

trait FileLogger
{
    public function log(string $message): void
    {
        echo "[file] $message\n";
    }
}

trait AuditLogger
{
    public function log(string $message): void
    {
        echo "[audit] $message\n";
    }
}

final class PaymentService
{
    use FileLogger, AuditLogger {
        FileLogger::log insteadof AuditLogger;
        AuditLogger::log as audit;
    }
}

$service = new PaymentService();
$service->log('Payment received');
$service->audit('Payment received');
تعارض السمات: insteadof يحسم، وas يُبقي اسمًا بديلًا.

اختبار المعرفة

تأكّد من أنك تذكّرت النقاط الأساسية في هذا الدرس.

  1. ما الميزة الحاسمة التي تتمتع بها الواجهة على الصنف المجرّد؟
    • يمكنها الاحتفاظ بخصائص مشتركة
    • يمكن للصنف أن ينفّذ عدة واجهات، لكن أن يرث من صنف واحد فقط
    • تعمل دوالها بسرعة أكبر
  2. سمتان يستخدمهما الصنف نفسه، تصرّح كل منهما بدالة log(). ماذا يحدث؟
    • تستخدم PHP دالة السمة الأولى المذكورة
    • خطأ فادح (fatal error)، ما لم يُحلّ التعارض صراحةً بـ insteadof
    • تدمج PHP الدالتين في دالة واحدة
  3. ما الذي يمكن أن يحتويه الصنف المجرّد ولا يمكن للواجهة احتواؤه؟
    • ثوابت
    • خصائص ودوال منفَّذة
    • تواقيع دوال عامة (public)