Skip to content

9. السمات

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

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

Image

9.2. تضمين سمة في فئة

يوضح البرنامج النصي [traits-01.php] الاستخدام الأساسي لسمة في فئة:


<?php
 
class Class04 {
  // attribute
  private $name;
 
  // manufacturer
  public function __construct(string $name) {
    $this->name = $name;
  }
 
  // getters and setters
  public function getName(): string {
    return $this->name;
  }
 
  public function setName(string $name): void {
    $this->name = $name;
  }
 
}
 
trait Trait04 {
  // attribute
  private $name;
 
  // getters and setters
  public function getName(): string {
    return $this->name;
  }
 
  public function setName(string $name): void {
    $this->name = $name;
  }
 
}
 
class Class05 {
  // trait inclusion
  use Trait04;
 
  // manufacturer
  public function __construct(string $name) {
    $this->name = $name;
  }
 
}
 
// test --------------
$class04 = new Class04("Tim");
$class05 = new Class05("Burton");
print $class04->getName() . "\n";
print $class05->getName() . "\n";
// display of both classes
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] بتضمين سمة في فئة؛
  • الأسطر 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] التالي مشاركة السمة بين الفئات:


<?php
 
trait Trait01 {
  // attribute
  private $id = 0;
 
  // method
  public function doSomething() {
    print "Trait01::doSomething… ($this->id)\n";
  }
 
}
 
class Class02 {
  // inclusion Trait01
  use Trait01 {
    // method [Trait01::doSomething] is accessible
    // in the class under the name [doSomethingInTrait]
    Trait01::doSomething as doSomethingInTrait;
  }
 
  // class-specific method
  public function doSomething(): void {
    // id attribute
    $this->id += 10;
    // using the Trait01 method
    $this->doSomethingInTrait();
    // local display
    print "Class02->doSomething\n";
  }
 
}
 
class Class03 {
  // inclusion Trait01
  use Trait01;
 
  // method local to the class
  public function doSomethingElse(): void {
    // id attribute
    $this->id += 10;
    // using the Trait01 method
    $this->doSomething();
    // local display
    print "Class03->doSomethingElse\n";
  }
 
}
 
// test01 ----------------
function test01(): void {
  $class02 = new Class02();
  $class03 = new Class03();
  $class02->doSomething();
  $class03->doSomethingElse();
}
 
// test01
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] في الفئتين [Class02] و [Class03]، تصبح السمة [Trait01::i] سمتين منفصلتين: [Class02::i] و [Class03::i]. يظهر هذا في السطرين 2 و 4 من النتائج. لو كانت السمة [Trait01::i] مشتركة بين الفئتين [Class02] و [Class03 لكان الرقم 20 في السطر 4 بدلاً من 10.

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


<?php
 
// class that replaces the
class Class01 {
  // attribute
  private $id = 0;
 
  // setter
  public function setId(int $id) {
    $this->id = $id;
  }
 
  // getter
  public function getId(): int {
    return $this->id;
  }
 
  // method
  public function doSomething(): void {
    print "Class01::doSomething… ($this->id)\n";
  }
 
}
 
class Class02 {
  // class01 inclusion
  private $class01;
 
  // setter
  public function setClass01(Class01 $class01) {
    $this->class01 = $class01;
  }
 
  // class-specific method
  public function doSomething(): void {
    // chgt Class01 attribute
    $id = $this->class01->getId();
    $id += 10;
    $this->class01->setId($id);
    // using the Class01 method
    $this->class01->doSomething();
    // local display
    print "Class02->doSomething\n";
  }
 
}
 
class Class03 {
  // class01 inclusion
  private $class01;
 
  // setter
  public function setClass01(Class01 $class01) {
    $this->class01 = $class01;
  }
 
  // method local to the class
  public function doSomethingElse(): void {
    // chgt Class01 attribute
    $id = $this->class01->getId();
    $id += 10;
    $this->class01->setId($id);
    // using the Class01 method
    $this->class01->doSomething();
    // local display
    print "Class03->doSomethingElse\n";
  }
 
}
 
// test01 ----------------
function test01(): void {
  // two objects
  $class02 = new Class02();
  $class03 = new Class03();
  // will access two different instances of [Class01]
  $class02->setClass01(new Class01());
  $class03->setClass01(new Class01());
  // check
  $class02->doSomething();
  $class03->doSomethingElse();
}
 
// test02 ----------------
function test02(): void {
  // shared instance of [Class01]
  $class01 = new Class01();
  // two objects
  $class02 = new Class02();
  $class03 = new Class03();
  // will access the same instance of [Class01]
  $class02->setClass01($class01);
  $class03->setClass01($class01);
  // check
  $class02->doSomething();
  $class03->doSomethingElse();
}
 
// test01
print "test01-----------------\n";
test01();
// test02
print "test02-----------------\n";
test02();

تعليقات

  • الأسطر 4-23: تستبدل فئة [Class01] السمة [Trait01]. تم تضمين كود [Trait01] في كود فئتي [Class02] و[Class03]. هنا، لن يكون الأمر كذلك. بدلاً من ذلك، سيتم إدخال مرجع إلى الكود في فئة [Class01] في [Class02] و[Class03]. وهذا يعني أن سمات فئة [Class01] ستكون خارجية بالنسبة لكود [Class02] و[Class03]. نظرًا لأن السمة [id] خاصة هنا (السطر 6)، يجب توفير دالة الحصول (الأسطر 14–16) ودالة التعيين (الأسطر 9–11). هذا هو الاختلاف الأول عن السمة: يجب عليك إنشاء الكود للوصول إلى السمات الخاصة للفئة. كان بإمكاننا تغيير رؤية السمة [id] إلى [public]، ولكن لا يُنصح بذلك أبدًا. يتيح لنا استخدام دالة تعيين لتعيين قيمة السمة التحقق من صحتها؛
  • السطر 27: تضمين إشارة إلى الفئة [Class01] في كود [Class02]. نظرًا لأن هذه السمة خاصة، نحتاج إلى إنشاء دالة تعيين (الأسطر 30-32) لتهيئتها؛
  • الأسطر 37-41: لأي استخدام لرمز [Class01]، يجب علينا استخدام السمة [$this→class01]؛
  • الأسطر 48–69: الفئة [Class03] هي نسخة من الفئة [Class02]، باستثناء أن اسم أسلوبها مختلف؛
  • الأسطر 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→attribute1] دون أن تحتوي على تلك السمة نفسها. ومن ثم، فإن الأمر متروك للفئات التي تستخدم هذه السمة لتوفير السمة [$this→attribute1].

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

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


<?php
 
trait Trait01 {
 
  // method to share
  public function doSomething() {
    print "Trait01::doSomething ….\n";
  }
 
}
 
class Class02 {
  // inclusion Trait01
  use Trait01 {
    // method [Trait01::doSomething] is accessible
    // in the class under the name [doSomethingInTrait]
    Trait01::doSomething as doSomethingInTrait;
  }
 
  public function doSomething(): void {
    // trait01 method call
    $this->doSomethingInTrait();
    // local display
    print "Class02->doSomething\n";
  }
 
}
 
class Class03 {
  // inclusion Trait01
  use Trait01;
 
  // method local to the class
  public function doSomethingElse(): void {
    // trait01 method call
    $this->doSomething();
    // local display
    print "Class03->doSomethingElse\n";
  }
 
}
 
// test ----------------
(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 {
 
  // static method to share
  public static function doSomething() {
    print "Class01::doSomething ….\n";
  }
 
}
 
class Class02 {

  public function doSomething(): void {
    // calling the Class01 method
    Class01::doSomething();
    // local display
    print "Class02->doSomething\n";
  }
 
}
 
class Class03 {
 
  // method local to the class
  public function doSomethingElse(): void {
    // calling the Class01 method
    Class01::doSomething();
    // local display
    print "Class03->doSomethingElse\n";
  }
 
}
 
// test ----------------
(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 {
  // attribute
  private $i;
 
  // method to share
  public function doSomethingInTrait01() {
    // modification Trait01::$i
    $this->i++;
    // display
    print "Trait01::doSomethingInTrait01… i=$this->i\n";
  }
 
}
 
class Class02 {
  // attribute
  protected $j = 0;
 
  // method
  public function doSomethingInClass02(): void {
    // modification Class02::j
    $this->j += 10;
    // display
    print "Class02->doSomethingInClass02… j=$this->j\n";
  }
 
}
 
// derived class
class Class03 extends Class02 {
  // inherits Class02:j and Trait01::i
  // inclusion Trait01
  use Trait01;
 
  // method
  public function doSomethingInClass03(): void {
    // using the Trait01 method
    $this->doSomethingInTrait01();
    // modification Trait01::i
    $this->i += 100;
    // modification Class03::j (==Class02::j)
    $this->j += 1000;
    // display
    print "Class03->doSomethingInClass03… i=$this->i, j=$this->j\n";
  }
 
}
 
// test ----------------
(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 {
  // attribute
  protected $i;
 
  // getter and setter
  public function getI(): int {
    return $this->i;
  }
 
  public function setI(int $i): void {
    $this->i = $i;
  }
 
  // method
  public function doSomethingInClass01(): void {
    // modification Class01::$i
    $this->i++;
    // display
    print "Class01::doSomething in Class01… i=$this->i\n";
  }
 
}
 
class Class02 {
  // attribute
  protected $j = 0;
 
  // class-specific method
  public function doSomethingInClass02(): void {
    // modification Class02::j
    $this->j += 10;
    // display
    print "Class02->doSomethingInClass02… j=$this->j\n";
  }
 
}
 
class Class03 extends Class02 {
  // class01 inclusion
  private $class01;
 
  // setter
  public function setClass01(Class01 $class01) {
    $this->class01 = $class01;
  }
 
  // method local to the class
  public function doSomethingInClass03(): void {
    // using the Class01 method
    $this->class01->doSomethingInClass01();
    // modification Class01::i
    $i = $this->class01->getI();
    $i += 100;
    $this->class01->setI($i);
    // modification Class03::j
    $this->j += 1000;
    // display
    print "Class03->doSomethingInClass03… i=$i, j=$this->j\n";
  }
 
}
 
// test ----------------
$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: تظل الفئة [Class02] دون تغيير؛
  • السطر 40: الفئة [Class03] تمتد من الفئة [Class02]؛
  • السطر 42: يتم دمج الفئة [Class01] في [Class03] عبر مرجع؛
  • الأسطر 45–47: تم توفير أداة تعيين لتهيئة المرجع إلى الفئة [Class01]؛
  • الأسطر 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 {
  // attributes
  protected $attr1 = 11;
  protected $attr2 = 12;
 
  // getters and setters
  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;
  }
 
  // implemented method
  public function doSomething(): void {
    print "AbstractClass::doSomething [$this->attr1,$this->attr2]\n";
  }
 
  // method not implemented
  abstract public function doSomethingElse(): void;
}
 
// derived class 1
class Class1 extends AbstractClass {
  // attribute
  private $attr3 = 13;
 
  // getter and setter
  public function getAttr3() {
    return $this->attr3;
  }
 
  public function setAttr3($attr3) {
    $this->attr3 = $attr3;
    return $this;
  }
 
  // implementation doSomethingElse
  public function doSomethingElse(): void {
    print "Class1::doSomethingElse [$this->attr1,$this->attr2,$this->attr3]\n";
  }
 
}
 
// derived class 2
class Class2 extends AbstractClass {
  // attribute
  private $attr4 = 14;
 
  public function getAttr4() {
    return $this->attr4;
  }
 
  public function setAttr4($attr4) {
    $this->attr4 = $attr4;
    return $this;
  }
 
  // implementation doSomethingElse
  public function doSomethingElse(): void {
    print "Class2::doSomethingElse [$this->attr1,$this->attr2,$this->attr4]\n";
  }
 
}
 
// external function
function useInterfaceWith(Interface1 $interface):void{
  $interface->doSomething();
  $interface->doSomethingElse();
}
 
// tests
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 {
  // attributes
  private $attr1 = 11;
  private $attr2 = 12;
 
  // getters and setters
  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;
  }
 
  // implemented method
  public function doSomething(): void {
    print "Trait::doSomething [$this->attr1,$this->attr2]\n";
  }
}
 
// derived class 1
class Class1 implements Interface1 {
  // line usage
  use Trait1;
  // attribute
  private $attr3 = 13;
 
  // getter and setter
  public function getAttr3() {
    return $this->attr3;
  }
 
  public function setAttr3($attr3) {
    $this->attr3 = $attr3;
    return $this;
  }
 
  // implementation doSomethingElse
  public function doSomethingElse(): void {
    print "Class1::doSomethingElse [$this->attr1,$this->attr2,$this->attr3]\n";
  }
 
}
 
// derived class 2
class Class2 implements Interface1 {
  // line usage
  use Trait1;
  // attribute
  private $attr4 = 14;
 
  public function getAttr4() {
    return $this->attr4;
  }
 
  public function setAttr4($attr4) {
    $this->attr4 = $attr4;
    return $this;
  }

  // implementation doSomethingElse
  public function doSomethingElse(): void {
    print "Class2::doSomethingElse [$this->attr1,$this->attr2,$this->attr4]\n";
  }
 
}
 
// external function using the
function useInterfaceWith(Interface1 $interface): void {
  $interface->doSomething();
  $interface->doSomethingElse();
}
 
// tests
useInterfaceWith(new Class1());
useInterfaceWith(new Class2());

تعليقات

  • الأسطر 3–8: لم تتغير الواجهة [Interface1]؛
  • الأسطر 10–41: السمة [Trait1] تحل محل الفئة المجردة [AbstractClass] من الحل 1. الكود هو نفسه باستثناء التفاصيل التالية:
    • السطر 10: السمة [Trait1] لا تنفذ الواجهة [Interface1]. وهذا مستحيل من الناحية النحوية؛
    • الأسطر 12-13: تصبح سمة الرؤية [protected] لسمات الفئة المجردة [AbstractClass] هنا [private]. تهدف هاتان السمتان إلى منح الفئات المشتقة وصولاً مباشراً إلى سمات الفئة الأصلية (protected) أو السمة (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. الخلاصة

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