Skip to content

9. السمات

السمات (Traits) هي هياكل مشابهة للفئات. ومع ذلك، لا يمكن إنشاء مثيلات منها. وهي مصممة ليتم تضمينها في الفئات. إن تضمين سمة في فئة له نفس تأثير نسخ كود السمة إلى الفئة. نضع في السمة كودًا يمكن إعادة استخدامه في عدة فئات.

9.1. شجرة البرامج النصية

Image

9.2. إدراج سمة في فئة

يُظهر البرنامج النصي [traits-01.php] استخدامًا أساسيًا لـ Trait في فئة:


<?php

class Class04 {
  // السمة
  private $name;

  // منشئ
  public function __construct(string $name) {
    $this->name = $name;
  }

  // مُستردات ومُعيّنات
  public function getName(): string {
    return $this->name;
  }

  public function setName(string $name): void {
    $this->name = $name;
  }

}

trait Trait04 {
  // السمة
  private $name;

  // مُستردات ومُعيّنات
  public function getName(): string {
    return $this->name;
  }

  public function setName(string $name): void {
    $this->name = $name;
  }

}

class Class05 {
  // تضمين السمة
  use Trait04;

  // منشئ
  public function __construct(string $name) {
    $this->name = $name;
  }

}

// اختبار --------------
$class04 = new Class04("Tim");
$class05 = new Class05("Burton");
print $class04->getName() . "\n";
print $class05->getName() . "\n";
// عرض الفئتين
print_r($class04);
print_r($class05);

تعليقات على الكود

  • الأسطر 3-21: تعريف الفئة [Class04] مع سمة واحدة، ووظائف get / set الخاصة بها، ومنشئ؛
  • الأسطر 23-36: نأخذ كود [Class04] بدون منشئه وننقله إلى السمة [Trait04] كما هو. لا نأخذ المنشئ لأن السمة لا يمكن إنشاء مثيل لها؛
  • السطر 23: الكلمة المفتاحية [trait] هي التي تجعل من [Trait04] سمةً بدلاً من فئة؛
  • الأسطر 38-45: يتم تعريف فئة [Class05] التي تستعير كود السمة [Trait04] (السطر 40) وتضيف إليها منشئًا (الأسطر 43-45)، مطابقًا لمنشئ فئة [Class04] لجعل الفئة قابلة للتجسيد؛
  • السطر 40: الكلمة الرئيسية [use] هي التي تسمح بتضمين سمة (Trait) في فئة؛
  • الأسطر 50-56: تُظهر الاختبارات أن الفئتين [Class04] و [Class05] تعملان بنفس الطريقة؛

النتائج

Tim
Burton
Class04 Object
(
    [name:Class04:private] => Tim
)
Class05 Object
(
    [name:Class05:private] => Burton
)

تُظهر نتائج الأسطر 3-10 أن الفئتين [Class04] و [Class05] لهما نفس المحتوى؛

الاستنتاج

استخدام الأمر [use Trait] في فئة ما يعادل تضمين كود [Trait] في تلك الفئة.

9.3. استخدام نفس السمة في فئات مختلفة

يبدو أن الفائدة الأولى للسمة هي إعادة استخدام نفس الكود (السمات + الطرق) بين فئات مختلفة. لكننا سنرى أنه يمكننا الوصول إلى نفس الهدف باستخدام فئات بسيطة.

يوضح البرنامج النصي [trait-02.php] التالي مشاركة trait بين الفئات:


<?php

trait Trait01 {
  // السمة
  private $id = 0;

  // طريقة
  public function doSomething() {
    print "Trait01::doSomething… ($this->id)\n";
  }

}

class Class02 {
  // تضمين Trait01
  use Trait01 {
    // يمكن الوصول إلى الطريقة [Trait01::doSomething]
    // في الفئة تحت الاسم [doSomethingInTrait]
    Trait01::doSomething as doSomethingInTrait;
  }

  // طريقة خاصة بالفئة
  public function doSomething(): void {
    // السمة id
    $this->id += 10;
    // استخدام طريقة Trait01
    $this->doSomethingInTrait();
    // العرض المحلي
    print "Class02->doSomething\n";
  }

}

class Class03 {
  // تضمين Trait01
  use Trait01;

  // طريقة محلية خاصة بالفئة
  public function doSomethingElse(): void {
    // السمة id
    $this->id += 10;
    // استخدام طريقة Trait01
    $this->doSomething();
    // العرض المحلي
    print "Class03->doSomethingElse\n";
  }

}

// test01 ----------------
function test01(): void {
  $class02 = new Class02();
  $class03 = new Class03();
  $class02->doSomething();
  $class03->doSomethingElse();
}

// اختبار01
print "test01-----------------\n";
test01();

تعليقات

  • الأسطر 3-10: سمة تُعرّف خاصية (السطر 5) وطريقة (الأسطر 8-10).
  • يتم حقن السمة [Trait01] في فئتين هما [Class02] (الأسطر 14-32) و [Class03] (الأسطر 34-46).
  • الأسطر 16-20: إدراج [Trait01] في [Class02]؛
  • يهدف السطر 19 إلى حل تعارض: تحتوي كل من [Trait01] و [Class02] على طريقة تسمى [doSomething]. وهناك حالتان يجب توقعهما:
    • يتم استدعاء الطريقة [Class02::doSomething] من خارج الفئة. في هذه الحالة، تكون الطريقة [Class02::doSomething] لها الأولوية على الطريقة [Trait01::doSomething] وهي التي يتم استدعاؤها؛
    • يتم استدعاء الأسلوب [Class02::doSomething] من داخل الفئة. في هذه الحالة، يحدث تعارض: لا يعرف المُفسر PHP أي أسلوب يجب استدعاؤه؛

يسمح السطر 19 بإعادة تسمية الطريقة [Trait01::doSomething] لتصبح [doSomethingInTrait]. وبالتالي، داخل [Class02 سيتم استخدام الترميزات التالية:

  • [doSomethingInTrait] لاستدعاء الطريقة [Trait01::doSomething
  • [doSomething] لاستدعاء الطريقة [Class02::doSomething
  • السطران 25 و27: تستخدم الفئة [Class02] السمة والطريقة الخاصتين بـ [Trait01] كما لو كانتا خاصتين بها؛
  • الأسطر 34-48: الفئة [Class03] مطابقة للفئة [Class02]. يعد تضمين [Trait01] هنا أسهل لأنه لا يوجد تضارب بين طرق [Trait01] و[Class03]؛

النتائج

1
2
3
4
5
test01-----------------
Trait01::doSomething… (10)
Class02->doSomething
Trait01::doSomething… (10)
Class03->doSomethingElse

تجدر الإشارة إلى أنه لا يوجد تقاسم للسمة [Trait01] بين الفئتين [Class02] و [Class03]. وبالتالي، فإنالسمة [Trait01::i] تصبح، من خلال تضمين [Trait01] في الفئتين [Class02] و [Class03 سمتين مختلفتين هما [Class02::i] و [Class03::i]. وهذا ما توضحه السطران 2 و4 من النتائج. لو كانت السمة [Trait01::i] مشتركة بين الفئتين [Class02] و [Class03 لكان الرقم 20 هو الذي ظهر في السطر 4 بدلاً من 10.

يُظهر البرنامج النصي [trait-03.php] أنه يمكن الوصول إلى نفس النتيجة باستخدام فئة بدلاً من السمة:


<?php

// الفئة التي تحل محل السمة
class Class01 {
  // السمة
  private $id = 0;

  // مُعيّن
  public function setId(int $id) {
    $this->id = $id;
  }

  // مُسترد
  public function getId(): int {
    return $this->id;
  }

  // طريقة
  public function doSomething(): void {
    print "Class01::doSomething… ($this->id)\n";
  }

}

class Class02 {
  // تضمين Class01
  private $class01;

  // مُعيّن
  public function setClass01(Class01 $class01) {
    $this->class01 = $class01;
  }

  // طريقة خاصة بالفئة
  public function doSomething(): void {
    // تغيير سمة Class01
    $id = $this->class01->getId();
    $id += 10;
    $this->class01->setId($id);
    // استخدام طريقة Class01
    $this->class01->doSomething();
    // عرض محلي
    print "Class02->doSomething\n";
  }

}

class Class03 {
  // تضمين Class01
  private $class01;

  // مُعيّن
  public function setClass01(Class01 $class01) {
    $this->class01 = $class01;
  }

  // طريقة محلية للفئة
  public function doSomethingElse(): void {
    // تغيير سمة Class01
    $id = $this->class01->getId();
    $id += 10;
    $this->class01->setId($id);
    // استخدام طريقة Class01
    $this->class01->doSomething();
    // عرض محلي
    print "Class03->doSomethingElse\n";
  }

}

// test01 ----------------
function test01(): void {
  // كائنان
  $class02 = new Class02();
  $class03 = new Class03();
  // سيصلان إلى مثيلين مختلفين من [Class01]
  $class02->setClass01(new Class01());
  $class03->setClass01(new Class01());
  // التحقق
  $class02->doSomething();
  $class03->doSomethingElse();
}

// اختبار02 ----------------
function test02(): void {
  // مثيل مشترك لـ [Class01]
  $class01 = new Class01();
  // كائنان
  $class02 = new Class02();
  $class03 = new Class03();
  // سيصلان إلى نفس مثيل [Class01]
  $class02->setClass01($class01);
  $class03->setClass01($class01);
  // التحقق
  $class02->doSomething();
  $class03->doSomethingElse();
}

// اختبار01
print "test01-----------------\n";
test01();
// اختبار02
print "test02-----------------\n";
test02();

تعليقات

  • الأسطر 4-23: تحل الفئة [Class01] محل السمة [Trait01]. كان كود [Trait01] مدمجًا في كود الفئتين [Class02] و [Class03]. ولن يكون هذا هو الحال هنا. بل ستكون هناك إشارة إلى رمز الفئة [Class01] الذي سيتم إدراجه في الفئتين [Class02] و [Class03]. مما يعني أن سمات الفئة [Class01] ستكون خارج نطاق كود الفئتين [Class02] و [Class03]. ونظرًا لأن السمة [id] هنا خاصة (السطر 6)، فيجب توفير دالة getter (الأسطر 14-16) ودالة setter (الأسطر 9-11). وهذا هو الاختلاف الأول عن السمة (trait): حيث يتعين إنشاء كود للوصول إلى السمات الخاصة للفئة. كان من الممكن تغيير رؤية السمة [id] إلى [publicولكن لا يُنصح أبدًا بالقيام بذلك. يتيح استخدام دالة تعيين لتعيين قيمة السمة التحقق من صحتها؛
  • السطر 27: تضمين مرجع إلى الفئة [Class01] في كود [Class02]. ونظرًا لأن هذا السمة خاصة، يتعين علينا إنشاء مُعيّن (السطور 30-32) لتهيئتها؛
  • الأسطر 37-41: لأي استخدام لرمز [Class01]، يتعين علينا المرور عبر السمة [$this→class01]؛
  • الأسطر 48-69: الفئة [Class03] هي نسخة من الفئة [Classe02] باستثناء أن اسم أسلوبها مختلف؛
  • الأسطر 72-82: الاختبار الأول. يتكون هذا الاختبار من إدخال مثيلين مختلفين من الفئة [Class01] في الفئتين [Class02] و [Class03] (السطران 77 و78)؛
  • الأسطر 85-97: الاختبار الثاني يقوم بإدخال نفس المثيل من الفئة [Class01] في الفئتين [Class02] و [Class03] (الأسطر 97 و92 و93)؛
  • الأسطر 100-101: تنفيذ الاختبار [test01]؛
  • السطران 103-104: تنفيذ الاختبار [test02]؛

النتائج

test01-----------------
Class01::doSomething… (10)
Class02->doSomething
Class01::doSomething… (10)
Class03->doSomethingElse
test02-----------------
Class01::doSomething… (10)
Class02->doSomething
Class01::doSomething… (20)
Class03->doSomethingElse

تعليقات على النتائج

  • الأسطر 2-5: نحصل على نفس النتائج التي نحصل عليها باستخدام السمة [Trait01]. ونستنتج من ذلك أن استخدام السمة ليس ضروريًا هنا، ولكنه يؤدي إلى تقليل حجم الكود نظرًا لعدم الحاجة إلى طرق للوصول إلى سمات السمة: فهذه السمات جزء لا يتجزأ من الكود الذي تم دمج السمة فيه؛
  • الأسطر 7-10: نظرًا لأننا قمنا بإدراج نفس مرجع [Class01] في الفئتين [Class02] و [Class03]، تمت مشاركة السمة [Class01::id] بين الفئتين. ولهذا السبب، يعرض السطر 9 من النتائج 20 بدلاً من 10 مع السمة. ونستنتج من ذلك أنه إذا كان يجب مشاركة سمات السمة بين الفئات، فإن السمة لا يمكن استخدامها ويجب عندئذٍ استخدام فئة؛

9.4. تجميع الطرق في سمة

في المثال السابق، كان السمة تحتوي على سمات وطرق. ننظر هنا في الحالة التي لا تحتوي إلا على طرق. في هذه الحالة، تشبه السمة عملية تحليل طرق يمكن استخدامها بعد ذلك في فئات مختلفة. وبما أن السمة لا تحتوي هنا على أي سمة، سننظر في الحالة التي تعمل فيها الطرق التي تجمعها فقط على المعلمات التي يتم تمريرها إليها. في الواقع، هذا ليس إلزاميًا: يمكن للسمة أن تعمل على سمة [$this→attribut1] دون أن تحتوي على هذه السمة. وعندئذ يقع على عاتق الفئات التي تستخدم هذه السمة توفير السمة [$this→attribut1].

في الحالة التي لا تحتوي فيها السمة إلا على طرق تعمل حصريًّا على المعلمات التي يتم تمريرها إليها، سنُبيّن أن السمة يمكن عندئذٍ استبدالها بفئة تحتوي على نفس الطرق الموجودة في السمة، على أن تكون هذه الطرق مُعلنة على أنها ثابتة.

يتم توضيح استخدام السمة من خلال البرنامج النصي [trait-04.php] الذي يستخدم السمة من المثال السابق مع إزالة جميع السمات منها:


<?php

trait Trait01 {

  // طريقة للمشاركة
  public function doSomething() {
    print "Trait01::doSomething ….\n";
  }

}

class Class02 {
  // تضمين السمة 01
  use Trait01 {
    // الطريقة [Trait01::doSomething] متاحة
    // في الفئة تحت الاسم [doSomethingInTrait]
    Trait01::doSomething as doSomethingInTrait;
  }

  public function doSomething(): void {
    // استدعاء طريقة Trait01
    $this->doSomethingInTrait();
    // عرض محلي
    print "Class02->doSomething\n";
  }

}

class Class03 {
  // تضمين Trait01
  use Trait01;

  // طريقة محلية تابعة للفئة
  public function doSomethingElse(): void {
    // استدعاء طريقة Trait01
    $this->doSomething();
    // عرض محلي
    print "Class03->doSomethingElse\n";
  }

}

// اختبار ----------------
(new Class02())->doSomething();
(new Class03())->doSomethingElse();

تعليقات

  • الأسطر 3-10: لم يعد للسمة [Trait01] أي سمات؛
  • الأسطر 14-18: تضمين [Trait01] في [Class02]. يتم استخدام طريقة [Trait01] في السطر 22؛
  • السطر 31: تضمين [Trait01] في [Class03]. تُستخدم طريقة [Trait01] في السطر 36؛

النتائج

1
2
3
4
Trait01::doSomething ….
Class02->doSomething
Trait01::doSomething ….
Class03->doSomethingElse

في حالة الاستخدام هذه، يمكن بسهولة استبدال السمة [Trait01] بفئة. يوضح ذلك البرنامج النصي [trait-05.php] التالي:


<?php

abstract class Class01 {

  // طريقة ثابتة مشتركة
  public static function doSomething() {
    print "Class01::doSomething ….\n";
  }

}

class Class02 {

  public function doSomething(): void {
    // استدعاء طريقة Class01
    Class01::doSomething();
    // عرض محلي
    print "Class02->doSomething\n";
  }

}

class Class03 {

  // طريقة محلية للفئة
  public function doSomethingElse(): void {
    // استدعاء طريقة من Class01
    Class01::doSomething();
    // عرض محلي
    print "Class03->doSomethingElse\n";
  }

}

// اختبار ----------------
(new Class02())->doSomething();
(new Class03())->doSomethingElse();

تعليقات

  • الأسطر 3-10: يتم استبدال السمة [Trait01] بفئة مجردة [Class01] يتم إعلان جميع أساليبها على أنها ثابتة. يتم إعلان الفئة على أنها مجردة فقط لمنع إنشاء مثيل لها. كنا نرغب أيضًا في كتابة [final] لمنع اشتقاقها، لكن PHP 7 لا تقبل البادئة [final abstract] لفئة ما. إما هذا أو ذاك، ولكن ليس كلاهما؛
  • السطر 16: بدلاً من كتابة [$this→doSomethingInTraitنكتب الآن [Class01::doSomething]، أي أننا نستدعي الطريقة الثابتة [doSomething] للفئة [Class01]؛
  • السطر 28: نكرر نفس الإجراء في [Class03]؛

النتائج

1
2
3
4
Class01::doSomething ….
Class02->doSomething
Class01::doSomething ….
Class03->doSomethingElse

لقد حصلنا على نفس النتيجة تمامًا كما في حالة السمة [Trait01]، مما يدل على أنه يمكن تجنب استخدام هذه السمة. وقد ذكرنا سابقًا أن طرق السمة يمكنها العمل على السمة [$this→attribut1] دون أن تحتوي السمة نفسها على هذه السمة. وبالتالي، يقع على عاتق الفئات التي تستخدم هذا السمة توفير السمة [$this→attribut1]. وهذه حالة استثنائية: فمن الأفضل «ترقية» السمة [$this→attribut1] التي يجب أن تمتلكها الفئات التي تستخدم السمة، إلى داخل السمة نفسها. وبذلك ستصبح بالضرورة جزءًا من سمات الفئة التي تستخدم السمة.

9.5. الوراثة المتعددة باستخدام سمة

من الشائع قراءة في أدبيات PHP أن السمة تسمح بالوراثة المتعددة: أي إمكانية أن ترث فئة ما من عدة فئات. تتمتع لغة C++ بهذه الإمكانية، على عكس لغتي Java وC# اللتين لا تعرفان سوى الوراثة البسيطة. سنوضح أنه إذا كان استخدام السمة في فئة مشتقة يسمح بالفعل بتنفيذ شيء يشبه التوريث المتعدد، فإن حالة الاستخدام هذه يمكن تنفيذها أيضًا باستخدام فئات بسيطة.

يستخدم البرنامج النصي [trait-06.php] فئة مشتقة وسمة:


<?php

trait Trait01 {
  // السمة
  private $i;

  // طريقة للمشاركة
  public function doSomethingInTrait01() {
    // تعديل Trait01::$i
    $this->i++;
    // عرض
    print "Trait01::doSomethingInTrait01… i=$this->i\n";
  }

}

class Class02 {
  // السمة
  protected $j = 0;

  // طريقة
  public function doSomethingInClass02(): void {
    // تعديل Class02::j
    $this->j += 10;
    // العرض
    print "Class02->doSomethingInClass02… j=$this->j\n";
  }

}

// فئة مشتقة
class Class03 extends Class02 {
  // يرث من Class02:j و Trait01::i
  // تضمين Trait01
  use Trait01;

  // طريقة
  public function doSomethingInClass03(): void {
    // استخدام طريقة من Trait01
    $this->doSomethingInTrait01();
    // تعديل Trait01::i
    $this->i += 100;
    // تعديل Class03::j (==Class02::j)
    $this->j += 1000;
    // عرض
    print "Class03->doSomethingInClass03… i=$this->i, j=$this->j\n";
  }

}

// اختبار ----------------
(new Class02())->doSomethingInClass02();
(new Class03())->doSomethingInClass03();

تعليقات

  • الأسطر 3-15: نعود إلى سمة [Trait01] مع سمة وأسلوب يتعامل معها؛
  • الأسطر 17-29: فئة [Class02] التي لا علاقة لها بالسمة [Trait01]. فهي لا تستخدمها؛
  • السطر 19: تم إعلان السمة الوحيدة لـ [Class02] مع نطاق رؤية [protected] حتى تكون متاحة في الفئات المشتقة؛
  • السطر 32: الفئة [Class03] تمتد من الفئة [Class02]. بالإضافة إلى ذلك، فإنها تدمج السمة [Trait01] (السطر 35). وأخيرًا، ترث هذه الفئة سمات وأساليب [Class02] وتدمج سمات وأساليب [Trait01]. وبالتالي، فإننا أمام حالة مشابهة للوراثة المتعددة؛

النتائج

1
2
3
Class02->doSomethingInClass02… j=10
Trait01::doSomethingInTrait01… i=1
Class03->doSomethingInClass03… i=101, j=1000

بالطريقة نفسها التي اتبعناها في المثال السابق، سنوضح أن:

  • يمكن استبدال السمة بفئة؛
  • بدلاً من دمج السمة في الفئة المشتقة، يتم دمج مرجع مثيل من الفئة؛

النص البرمجي [trait-07.php] هو كما يلي:


<?php

class Class01 {
  // السمة
  protected $i;

  // مُسترد ومُعيّن
  public function getI(): int {
    return $this->i;
  }

  public function setI(int $i): void {
    $this->i = $i;
  }

  // طريقة
  public function doSomethingInClass01(): void {
    // تعديل Class01::$i
    $this->i++;
    // العرض
    print "Class01::doSomething in Class01… i=$this->i\n";
  }

}

class Class02 {
  // السمة
  protected $j = 0;

  // طريقة خاصة بالفئة
  public function doSomethingInClass02(): void {
    // تعديل Class02::j
    $this->j += 10;
    // عرض
    print "Class02->doSomethingInClass02… j=$this->j\n";
  }

}

class Class03 extends Class02 {
  // تضمين Class01
  private $class01;

  // مُعيّن
  public function setClass01(Class01 $class01) {
    $this->class01 = $class01;
  }

  // طريقة محلية للفئة
  public function doSomethingInClass03(): void {
    // استخدام طريقة من Class01
    $this->class01->doSomethingInClass01();
    // تعديل Class01::i
    $i = $this->class01->getI();
    $i += 100;
    $this->class01->setI($i);
    // تعديل Class03::j
    $this->j += 1000;
    // العرض
    print "Class03->doSomethingInClass03… i=$i, j=$this->j\n";
  }

}

// اختبار ----------------
$class01 = new Class01();
$class02 = new Class02();
$class03 = new Class03();
$class03->setClass01($class01);
$class02->doSomethingInClass02();
$class03->doSomethingInClass03();

تعليقات

  • الأسطر 3-24: تحل الفئة [Class01] محل السمة [Trait01]. نظرًا لأن الفئة [Class01] سيتم دمجها في الفئات عبر مرجع، فقد تم توفير دالات get / set للسمة [$i]؛
  • الأسطر 26-38: لم تتغير الفئة [Classe02]؛
  • السطر 40: الفئة [Class03] تمتد من الفئة [Classe02]؛
  • السطر 42: يتم دمج الفئة [Classe01] في [Classe03] عبر مرجع؛
  • الأسطر 45-47: يتم توفير setter لتهيئة الإشارة إلى الفئة [Classe01]؛
  • الأسطر 50-61: تقوم الطريقة [doSomethingInClass03] بنفس العملية المذكورة سابقًا، ولكن برمز أكثر تعقيدًا؛

النتائج

1
2
3
Class02->doSomethingInClass02… j=10
Class01::doSomething in Class01… i=1
Class03->doSomethingInClass03… i=101, j=1000

من هذا المثال، يمكننا أن نستنتج أن السمة ليست ضرورية هنا أيضًا، ولكن يجب الاعتراف بأنها تسمح بكتابة كود أقصر في الفئة المشتقة.

9.6. استخدام السمة بدلاً من الفئة المجردة

غالبًا ما نواجه الحالة التالية: ننشئ واجهة I عامة إلى حد ما يمكن أن تنشأ عنها عدة تطبيقات. تشترك هذه التطبيقات في كود مشترك، لكنها تختلف في طرق أخرى. يمكن تنفيذ هذه الحالة بطريقتين:

  1. نقوم بإنشاء فئة مجردة C تضم الكود المشترك بين الفئات المشتقة. تقوم الفئة C بتنفيذ الواجهة I، لكن بعض الطرق التي يجب إعلانها في الفئات المشتقة تُعلن في الفئة C على أنها مجردة، وبالتالي فإن الفئة C نفسها مجردة. ثم ننشئ فئتين C1 و C2 مشتقتين من C، كل منهما تنفذ بطريقتها الخاصة الطرق غير المحددة (المجردة) لفئتها الأم C؛
  2. نقوم بإنشاء سمة T شبه مطابقة للفئة المجردة C من الحل السابق. لا تنفذ هذه السمة الواجهة I لأنها لا تستطيع ذلك من الناحية النحوية. ثم ننشئ فئتين C1 و C2 اللتين تنفذان الواجهة I وتستخدمان السمة T. ولم يتبق لهذه الفئات سوى تنفيذ أساليب الواجهة I التي لم تنفذها السمة T التي تستوردها؛

فيما يلي مثال يوضح مدى التقارب الكبير بين هذين الحلين.

تنفذ التطبيق 1 الحل 1 الموصوف سابقًا [trait-08.php]:


<?php

interface Interface1 {

  public function doSomething(): void;

  public function doSomethingElse(): void;
}

abstract class AbstractClass implements Interface1 {
  // السمات
  protected $attr1 = 11;
  protected $attr2 = 12;

  // مُستردات وقيم
  public function getAttr1() {
    return $this->attr1;
  }

  public function getAttr2() {
    return $this->attr2;
  }

  public function setAttr1($attr1) {
    $this->attr1 = $attr1;
    return $this;
  }

  public function setAttr2($attr2) {
    $this->attr2 = $attr2;
    return $this;
  }

  // طريقة مُنفَّذة
  public function doSomething(): void {
    print "AbstractClass::doSomething [$this->attr1,$this->attr2]\n";
  }

  // طريقة غير منفذة
  abstract public function doSomethingElse(): void;
}

// فئة مشتقة 1
class Class1 extends AbstractClass {
  // السمة
  private $attr3 = 13;

  // مُسترد ومُعيّن
  public function getAttr3() {
    return $this->attr3;
  }

  public function setAttr3($attr3) {
    $this->attr3 = $attr3;
    return $this;
  }

  // التنفيذ doSomethingElse
  public function doSomethingElse(): void {
    print "Class1::doSomethingElse [$this->attr1,$this->attr2,$this->attr3]\n";
  }

}

// فئة مشتقة 2
class Class2 extends AbstractClass {
  // السمة
  private $attr4 = 14;

  public function getAttr4() {
    return $this->attr4;
  }

  public function setAttr4($attr4) {
    $this->attr4 = $attr4;
    return $this;
  }

  // تنفيذ doSomethingElse
  public function doSomethingElse(): void {
    print "Class2::doSomethingElse [$this->attr1,$this->attr2,$this->attr4]\n";
  }

}

// وظيفة خارجية
function useInterfaceWith(Interface1 $interface):void{
  $interface->doSomething();
  $interface->doSomethingElse();
}

// اختبارات
useInterfaceWith(new Class1());
useInterfaceWith(new Class2());

تعليقات

  • الأسطر 3-8: تحتوي الواجهة [Interface1] على طريقتين؛
  • الأسطر 10-41: الفئة المجردة [AbstractClass] تُنفِّذ الواجهة [Interface1] (السطر 10). ولديها خاصيتان مع متغيرات الحصول والتعيين الخاصة بهما (الأسطر 12-32)، وتنفذ الطريقة [doSomething] للواجهة [Interface1] (الأسطر 35-37) ولكنها لا تستطيع تنفيذ الطريقة [doSomethingElse]. ولذلك تم إعلان هذه الطريقة على أنها مجردة (السطر 40). لا يمكن إنشاء مثيل للفئة المجردة [AbstractClass]، ولأن تكون لها فائدة ما، يجب بالضرورة أن تكون مشتقة؛
  • الأسطر 44-63: الفئة Class1 تمتد من الفئة المجردة [AbstractClass]، وبالتالي تُنفذ الواجهة [Interface1] (السطر 14). وهي تضفي مضمونًا على الطريقة [doSomethingElse] التي لم تحددها فئتها الأم (الأسطر 59-61). كما تضيف سمة إلى سمات فئتها الأم (الأسطر 46-56)؛
  • الأسطر 66-82: الفئة Class2 توسع الفئة المجردة [AbstractClass] وبالتالي تنفذ الواجهة [Interface1] (السطر 66). وهي توفر نصًا لطريقة [doSomethingElse] التي لم تحددها فئتها الأم (الأسطر 80-82). كما تضيف سمة إلى سمات فئتها الأم (الأسطر 68-77)؛
  • الأسطر 87-90: تتلقى الدالة [useInterfaceWith] كمعلمة نوع [Interface1] وتستدعي طريقتي هذه الواجهة؛
  • الأسطر 93-94: يتم استدعاء الدالة [useInterfaceWith] للمرة الأولى بنوع [Class1] وللمرة الثانية بنوع [Class2]. وهذا صحيح لأن هذين النوعين ينفذان واجهة [Interface1]؛

النتائج


AbstractClass::doSomething [11,12]
Class1::doSomethingElse [11,12,13]
AbstractClass::doSomething [11,12]
Class2::doSomethingElse [11,12,14]

الآن نقوم بتنفيذ الحل 2 باستخدام البرنامج النصي [trait-09.php]. ويتمثل ذلك في استبدال الفئة المجردة بخاصية:


<?php

interface Interface1 {

  public function doSomething(): void;

  public function doSomethingElse(): void;
}

trait Trait1 {
  // السمات
  private $attr1 = 11;
  private $attr2 = 12;

  // دالات الاسترجاع والتعيين
  public function getAttr1() {
    return $this->attr1;
  }

  public function getAttr2() {
    return $this->attr2;
  }

  public function setAttr1($attr1) {
    $this->attr1 = $attr1;
    return $this;
  }

  public function setAttr2($attr2) {
    $this->attr2 = $attr2;
    return $this;
  }

  // طريقة مُنفَّذة
  public function doSomething(): void {
    print "Trait::doSomething [$this->attr1,$this->attr2]\n";
  }
}

// فئة مشتقة 1
class Class1 implements Interface1 {
  // استخدام السمة
  use Trait1;
  // السمة
  private $attr3 = 13;

  // الوصول والضبط
  public function getAttr3() {
    return $this->attr3;
  }

  public function setAttr3($attr3) {
    $this->attr3 = $attr3;
    return $this;
  }

  // التنفيذ doSomethingElse
  public function doSomethingElse(): void {
    print "Class1::doSomethingElse [$this->attr1,$this->attr2,$this->attr3]\n";
  }

}

// الفئة المشتقة 2
class Class2 implements Interface1 {
  // استخدام السمة
  use Trait1;
  // السمة
  private $attr4 = 14;

  public function getAttr4() {
    return $this->attr4;
  }

  public function setAttr4($attr4) {
    $this->attr4 = $attr4;
    return $this;
  }

  // التنفيذ doSomethingElse
  public function doSomethingElse(): void {
    print "Class2::doSomethingElse [$this->attr1,$this->attr2,$this->attr4]\n";
  }

}

// وظيفة خارجية تستخدم الواجهة
function useInterfaceWith(Interface1 $interface): void {
  $interface->doSomething();
  $interface->doSomethingElse();
}

// الاختبارات
useInterfaceWith(new Class1());
useInterfaceWith(new Class2());

تعليقات

  • الأسطر 3-8: لم تتغير واجهة [Interface1]؛
  • الأسطر 10-41: السمة [Trait1] تحل محل الفئة المجردة [AbstractClass] من الحل 1. الكود هو نفسه باستثناء التفاصيل التالية:
    • السطر 10: السمة [Trait1] لا تنفذ الواجهة [Interface1]. وهذا مستحيل من الناحية النحوية؛
    • السطران 12-13: تصبح سمة الرؤية [protected] الخاصة بسمات الفئة المجردة [AbstractClass] هنا [private]. يهدف هذان السمتان إلى منح الفئات المشتقة وصولاً مباشراً إلى سمات الفئة الأم (محمية) أو السمة (خاصة) دون الحاجة إلى المرور عبر دوال الحصول والتعيين؛
    • السمة [Trait1] لا تعلن عن الطريقة المجردة [doSomethingElse]؛
  • الأسطر 41-62: الفئة [Class1] في الحل 2 مطابقة للفئة [Class1] في الحل 1 باستثناء التفاصيل التالية:
    • السطر 41: تُنفذ الفئة [Class1] الواجهة [Interface1]، بينما في الحل 1 كانت تمتد من الفئة المجردة [AbstractClass]؛
    • السطر 43: تستخدم السمة [Trait1] لتنفيذ جزء من الواجهة؛
  • الأسطر 65-85: يمكن إبداء نفس الملاحظات التي تم إبداؤها بشأن [Class1]؛
  • الأسطر 87-95: لم يتغير باقي الكود؛

النتائج


Trait::doSomething [11,12]
Class1::doSomethingElse [11,12,13]
Trait::doSomething [11,12]
Class2::doSomethingElse [11,12,14]

نحصل بالفعل على نفس النتائج.

9.7. الخلاصة

يتضح من الأمثلة السابقة أن حالات الاستخدام التي قد يجلب فيها استخدام الخط فائدة واضحة ليست واضحة. في أمثلةنا، يمكننا دائمًا الاستغناء عنه واستبداله بفئة. ومع ذلك، يبدو أن استخدامه عملي لتقسيم الكود بين فئات مشتقة مختلفة، كما لو كان هذا الكود ينتمي إلى فئة أصلية. وهذا ما سنفعله في المثال التالي.