Skip to content

9. تریت‌ها

ترایت‌ها ساختارهایی مشابه کلاس‌ها هستند. با این حال، نمی‌توان آن‌ها را نمونه‌سازی کرد. هدف از آن‌ها این است که در داخل کلاس‌ها گنجانده شوند. گنجاندن یک ترایت در یک کلاس، همان اثری را دارد که گویی کد ترایت در داخل کلاس کپی شده است. کدی که احتمالاً در چندین کلاس دوباره استفاده می‌شود، در داخل یک ترایت قرار می‌گیرد.

9.1. درخت اسکریپت

Image

9.2. شامل کردن یک تریت در یک کلاس

اسکریپت [traits-01.php] یک کاربرد پایه‌ای از یک تریت در داخل یک کلاس را نشان می‌دهد:


<?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;
  }

}

// test --------------
$class04 = new Class04("Tim");
$class05 = new Class05("Burton");
print $class04->getName() . "\n";
print $class05->getName() . "\n";
// نمایش هر دو کلاس
print_r($class04);
print_r($class05);

توضیحات کد

  • خطوط ۳–۲۱: تعریف کلاس [Class04] با یک ویژگی، متدهای get/set آن و یک سازنده؛
  • خطوط ۲۳–۳۶: کد [Class04] به جز سازنده (constructor) آن، برداشته شده و بدون تغییر به تریت (trait) [Trait04] منتقل می‌شود. سازنده (constructor) گنجانده نشده است زیرا یک تریت (trait) را نمی‌توان نمونه‌سازی (instantiate) کرد؛
  • خط ۲۳: این کلمه کلیدی [trait] است که [Trait04] را به جای یک کلاس، یک تریت (trait) می‌سازد؛
  • خطوط ۳۸–۴۵: ما یک کلاس [Class05] تعریف می‌کنیم که کد را از تریت [Trait04] (خط ۴۰) می‌گیرد و یک سازنده (lines 43–45) به آن اضافه می‌کند، که با سازنده کلاس [Class04] یکسان است، تا کلاس قابل نمونه‌سازی شود؛
  • خط ۴۰: کلمه کلیدی [use] امکان درج یک ویژگی (trait) در یک کلاس را فراهم می‌کند؛
  • خطوط ۵۰–۵۶: آزمایش‌ها نشان می‌دهند که کلاس‌های [Class04] و [Class05] به یک شکل رفتار می‌کنند؛

نتایج

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

نتایج در خطوط ۳–۱۰ نشان می‌دهد که کلاس‌های [Class04] و [Class05] محتوای یکسانی دارند؛

نتیجه‌گیری

استفاده از عبارت [use Trait] در یک کلاس معادل درج کد [Trait] در آن کلاس است.

9.3. استفاده از یک ویژگی یکسان در کلاس‌های مختلف

یکی از مزایای اصلی یک تریت، استفاده مجدد از همان کد (ویژگی‌ها و متدها) در کلاس‌های مختلف است. با این حال خواهیم دید که می‌توان با استفاده از کلاس‌های ساده نیز به همین هدف دست یافت.

اشتراک‌گذاری یک trait بین کلاس‌ها با اسکریپت [trait-02.php] زیر نشان داده شده است:


<?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();
}

// test01
print "test01-----------------\n";
test01();

توضیحات

  • خطوط ۳–۱۰: یک ویژگی که یک صفت (خط ۵) و یک متد (خطوط ۸–۱۰) را تعریف می‌کند.
  • ویژگی [Trait01] به دو کلاس تزریق می‌شود: [Class02] (خطوط 14–32) و [Class03] (خطوط 34–46).
  • خطوط 16–20: تزریق [Trait01] به [Class02];
  • خط ۱۹ برای حل یک تداخل طراحی شده است: کلاس‌های [Trait01] و [Class02] هر دو متدی به نام [doSomething] دارند. دو سناریو برای در نظر گرفتن وجود دارد:
    • متد [Class02::doSomething] از خارج کلاس فراخوانی می‌شود. در این حالت، متد [Class02::doSomething] بر متد [Trait01::doSomething] اولویت دارد و همان متدی است که فراخوانی می‌شود؛
    • متد [Class02::doSomething] از داخل کلاس فراخوانی می‌شود. در این حالت، تعارضی وجود دارد: تفسیرگر PHP نمی‌داند کدام متد را فراخوانی کند؛

خط ۱۹ متد [Trait01::doSomething] را به [doSomethingInTrait] تغییر نام می‌دهد. بنابراین، در داخل [Class02 نشانه‌های زیر استفاده خواهند شد:

  • [doSomethingInTrait] برای فراخوانی متد [Trait01::doSomething
  • [doSomething] برای فراخوانی متد [Class02::doSomething
  • خطوط ۲۵ و ۲۷: کلاس [Class02] از ویژگی و متد کلاس [Trait01] استفاده می‌کند، گویی که متعلق به خود او هستند؛
  • خطوط ۳۴–۴۸: کلاس [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]. این در خطوط ۲ و ۴ نتایج نشان داده شده است. اگر ویژگی [Trait01::i] بین کلاس‌های [Class02] و [Class03] مشترک بود، مقدار در خط ۴ به جای ۱۰، ۲۰ می‌شد.

اسکریپت [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 {
  // کلاس گنجانش01
  private $class01;

  // setter
  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();
}

// test02 ----------------
function test02(): void {
  //نمونه مشترک [Class01]
  $class01 = new Class01();
  // دو شیء
  $class02 = new Class02();
  $class03 = new Class03();
  //به همان نمونه از [Class01] دسترسی خواهد داشت
  $class02->setClass01($class01);
  $class03->setClass01($class01);
  //تأیید
  $class02->doSomething();
  $class03->doSomethingElse();
}

// test01
print "test01-----------------\n";
test01();
// test02
print "test02-----------------\n";
test02();

توضیحات

  • سطور ۴–۲۳: کلاس [Class01] جایگزین کلاس [Trait01] می‌شود. کد [Trait01] در کد کلاس‌های [Class02] و [Class03] گنجانده شد. در اینجا اینطور نخواهد بود. در عوض، یک مرجع به کد کلاس [Class01] به کلاس‌های [Class02] و [Class03] تزریق خواهد شد. این بدان معناست که ویژگی‌های کلاس [Class01] خارج از کد کلاس‌های [Class02] و [Class03] قرار خواهند گرفت. از آنجا که ویژگی [id] خصوصی است (خط ۶)، باید یک گتر (خطوط ۱۴–۱۶) و یک ستر (خطوط ۹–۱۱) ارائه شود. این اولین تفاوت نسبت به یک تریت است: برای دسترسی به ویژگی‌های خصوصی کلاس باید کد نوشته شود. ما می‌توانستیم دید ویژگی [id] را به [public] تغییر دهیم، اما هرگز این کار توصیه نمی‌شود. استفاده از یک ستر برای تعیین مقدار یک ویژگی، امکان بررسی اعتبار آن را فراهم می‌کند؛
  • خط ۲۷: درج یک ارجاع به کلاس [Class01] در کد برای [Class02]. از آنجا که این ویژگی خصوصی است، باید یک متد تنظیم‌کننده (setter) (خطوط ۳۰–۳۲) برای مقداردهی اولیه آن ایجاد کنیم؛
  • خطوط ۳۷–۴۱: برای هرگونه استفاده از کد در [Class01]، باید از ویژگی [$this→class01] استفاده کنیم؛
  • خطوط ۴۸–۶۹: کلاس [Class03] یک کپی از کلاس [Classe02] است، با این تفاوت که متد آن نام متفاوتی دارد؛
  • خطوط ۷۲–۸۲: تست اول. این شامل تزریق دو نمونه متفاوت از کلاس [Class01] به کلاس‌های [Class02] و [Class03] (خطوط ۷۷ و ۷۸) است؛
  • خطوط ۸۵–۹۷: تست دوم، یک نمونه یکسان از کلاس [Class01] را به کلاس‌های [Class02] و [Class03] تزریق می‌کند (خطوط ۹۷، ۹۲، ۹۳);
  • خطوط 100–101: اجرای تست [test01];
  • خطوط ۱۰۳–۱۰۴: اجرای تست [test02];

نتایج

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

توضیحاتی در مورد نتایج

  • خطوط ۲–۵: همان نتایج مشابه با ویژگی [Trait01] به دست می‌آید. بنابراین می‌توان نتیجه گرفت که استفاده از تریت در اینجا ضروری نیست، اما باعث کاهش حجم کد می‌شود زیرا نیازی به متدهایی برای دسترسی به ویژگی‌های تریت نیست: این ویژگی‌ها بخشی جدایی‌ناپذیر از کدی هستند که تریت در آن گنجانده شده است؛
  • خطوط ۷–۱۰: زیرا همان ارجاع به [Class01] به کلاس‌های [Class02] و [Class03] تزریق شده است، ویژگی [Class01::id] بین این دو کلاس مشترک شده است. به همین دلیل، خط ۹ نتایج به جای ۱۰، عدد ۲۰ را با این ویژگی نشان می‌دهد. بنابراین می‌توان نتیجه گرفت که اگر ویژگی‌های یک تریت قرار است بین کلاس‌ها مشترک شوند، آنگاه نمی‌توان از تریت استفاده کرد و باید به جای آن از یک کلاس استفاده شود؛

9.4. گروه‌بندی متدها در یک تریت

در مثال قبلی، تریت هم دارای ویژگی‌ها و هم متدها بود. در اینجا، ما موردی را در نظر می‌گیریم که تریت تنها شامل متدها است. در این حالت، تریت شبیه به یک عامل‌گیری از متدها است که سپس می‌توان از آن در کلاس‌های مختلف استفاده کرد. از آنجایی که تریت در اینجا هیچ ویژگی‌ای ندارد، ما موردی را در نظر می‌گیریم که متدهای موجود در آن صرفاً بر روی پارامترهای ورودی به آنها عمل می‌کنند. در واقع، این امر اجباری نیست: یک تریت می‌تواند بر روی صفتی به نام [$this→attribut1] عمل کند، بدون اینکه خود آن صفت را داشته باشد. در این صورت، بر عهده کلاس‌هایی است که از این تریت استفاده می‌کنند تا صفت [$this→attribut1] را فراهم کنند.

در صورتی که یک تریت تنها متدهایی داشته باشد که صرفاً بر روی پارامترهای ورودی خود عمل می‌کنند، نشان خواهیم داد که آن تریت را می‌توان با کلاسی جایگزین کرد که متدهای یکسانی با تریت داشته و به صورت استاتیک (static) تعریف شده باشد.

استفاده از این تریت در اسکریپت [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();

توضیحات

  • خطوط ۳–۱۰: تریت [Trait01] دیگر هیچ ویژگی‌ای ندارد؛
  • خطوط ۱۴–۱۸: درج [Trait01] درون [Class02]. متد [Trait01] در خط ۲۲ استفاده می‌شود؛
  • خط ۳۱: درج [Trait01] در [Class03]. متد [Trait01] در خط ۳۶ استفاده شده است؛

نتایج

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();

توضیحات

  • خطوط ۳–۱۰: ویژگی [Trait01] با یک کلاس انتزاعی [Class01] جایگزین می‌شود که در آن تمام متدها به صورت استاتیک اعلام شده‌اند. این کلاس صرفاً برای جلوگیری از نمونه‌سازی آن به عنوان یک کلاس انتزاعی اعلام شده است. همچنین دوست داشتیم [final] را بنویسیم تا از ارث‌بری آن جلوگیری کنیم، اما PHP پیشوند [final abstract] را برای یک کلاس نمی‌پذیرد. یا یکی و یا دیگری، اما نه هر دو؛
  • خط ۱۶: به جای نوشتن [$this→doSomethingInTrait]، اکنون [Class01::doSomething] را می‌نویسیم، یعنی متد استاتیک [doSomething] از کلاس [Class01] را فراخوانی می‌کنیم؛
  • خط ۲۸: ما همین رویه را در [Class03] تکرار می‌کنیم؛

نتایج

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

ما در واقع به همان نتیجه‌ای می‌رسیم که با تریت [Trait01] به دست می‌آید، که نشان می‌دهد می‌توان از استفاده از آن اجتناب کرد. ما متوجه شده‌ایم که متدهای یک تریت می‌توانند بر روی یک ویژگی [$this→attribut1] عمل کنند، بدون اینکه خود تریت آن ویژگی را داشته باشد. بنابراین، این بر عهده کلاس‌هایی است که از این تریت استفاده می‌کنند که ویژگی [$this→attribut1] را فراهم کنند. این یک مورد غیرمعمول است: به همان اندازه منطقی است که ویژگی [$this→attribut1]—که کلاس‌های استفادهکننده از تریت باید آن را داشته باشند—را به خود تریت پاس کنیم. به این ترتیب، این ویژگی ناگزیر بخشی از ویژگی‌های کلاس استفادهکننده از تریت خواهد شد.

9.5. وراثت چندگانه با یک تریت

در متون مربوط به PHP معمولاً خوانده می‌شود که ترِیت‌ها چندمیراثی را ممکن می‌سازند: توانایی یک کلاس برای ارث بردن از چندین کلاس. زبان C++ از این قابلیت پشتیبانی می‌کند، اما جاوا و C# چنین نیستند، زیرا آن‌ها فقط از میراث تکی (single inheritance) پشتیبانی می‌کنند. نشان خواهیم داد که در حالی که استفاده از یک تریت در یک کلاس مشتق شده واقعاً امکان پیاده‌سازی چیزی شبیه به ارث‌بری چندگانه را فراهم می‌کند، این مورد استفاده را می‌توان بار دیگر با استفاده از کلاس‌های ساده پیاده‌سازی کرد.

اسکریپت [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";
  }

}

// test ----------------
(new Class02())->doSomethingInClass02();
(new Class03())->doSomethingInClass03();

نظرات

  • خطوط ۳–۱۵: ما به یک تریت [Trait01] با یک ویژگی و یک متدی که آن را دستکاری می‌کند، بازمی‌گردیم؛
  • خطوط 17–29: یک کلاس [Class02] که هیچ ارتباطی با ویژگی [Trait01] ندارد. از آن استفاده نمی‌کند؛
  • خط ۱۹: تنها ویژگی کلاس [Class02] با دید [protected] اعلام شده است تا در کلاس‌های مشتق قابل دسترسی باشد؛
  • خط ۳۲: کلاس [Class03] از کلاس [Class02] ارث می‌برد. علاوه بر این، آن ویژگی [Trait01] (خط ۳۵) را نیز در خود جای می‌دهد. در نهایت، این کلاس ویژگی‌ها و متدهای [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 {
  // شامل شدن کلاس 01
  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();

توضیحات

  • خطوط ۳–۲۴: کلاس [Class01] جایگزین ویژگی [Trait01] می‌شود. از آنجا که کلاس [Class01] قرار است از طریق یک مرجع در کلاس‌ها گنجانده شود، متدهای get/set برای ویژگی [$i] فراهم شده‌اند؛
  • خطوط 26–38: کلاس [Classe02] بدون تغییر باقی می‌ماند؛
  • خط ۴۰: کلاس [Class03] از کلاس [Classe02] ارث می‌برد؛
  • خط ۴۲: کلاس [Classe01] از طریق یک مرجع در [Classe03] گنجانده شده است؛
  • خطوط ۴۵–۴۷: یک setter تعریف شده است تا مرجع به کلاس [Classe01] را مقداردهی اولیه کند؛
  • خطوط ۵۰–۶۱: متد [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 که آن را وارد (import) کرده‌اند، پیاده‌سازی نشده‌اند؛

در اینجا مثالی آورده شده است که نشان می‌دهد این دو راه‌حل تا چه حد به یکدیگر شباهت دارند.

برنامهٔ کاربردی ۱، راه‌حل ۱ را که در بالا شرح داده شد، پیاده‌سازی می‌کند: [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;
}

// کلاس مشتق ۱
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";
  }

}

//کلاس مشتق ۲
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());

نظرات

  • خطوط ۳–۸: اینترفیس [Interface1] دارای دو متد است؛
  • خطوط ۱۰–۴۱: کلاس انتزاعی [AbstractClass] رابط [Interface1] را پیاده‌سازی می‌کند (خط ۱۰). این کلاس دارای دو ویژگی با گترها و سترها (خطوط ۱۲–۳۲) است و متد [doSomething] را از رابط [Interface1] پیاده‌سازی می‌کند (خطوط ۳۵–۳۷) اما متد [doSomethingElse] را پیاده‌سازی نمی‌کند. بنابراین این متد به عنوان انتزاعی (abstract) اعلام شده است (خط ۴۰). کلاس انتزاعی [AbstractClass] قابل نمونه‌سازی (instantiation) نیست و برای اینکه کاربردی باشد، باید از آن ارث بری شود؛
  • خطوط 44–63: کلاس Class1 از کلاس انتزاعی [AbstractClass] ارث می‌برد و بنابراین رابط [Interface1] (خط 14) را پیاده‌سازی می‌کند. این کلاس بدنه متد [doSomethingElse] را که کلاس والد آن تعریف نکرده بود، فراهم می‌کند (خطوط 59–61). همچنین یک ویژگی به ویژگی‌های کلاس والد خود اضافه می‌کند (خطوط 46–56)؛
  • خطوط ۶۶–۸۲: کلاس Class2 از کلاس انتزاعی [AbstractClass] ارث می‌برد و بنابراین رابط [Interface1] را پیاده‌سازی می‌کند (خط ۶۶). بدنه متد [doSomethingElse] را که کلاس والد آن تعریف نکرده بود، فراهم می‌کند (خطوط ۸۰–۸۲). همچنین یک ویژگی به ویژگی‌های کلاس والد خود اضافه می‌کند (خطوط ۶۸–۷۷)؛
  • خطوط ۸۷–۹۰: تابع [useInterfaceWith] یک نوع [Interface1] را به عنوان پارامتر می‌پذیرد و دو متد این رابط را فراخوانی می‌کند؛
  • خطوط ۹۳–۹۴: تابع [useInterfaceWith] بار اول با نوع [Class1] و بار دوم با نوع [Class2] فراخوانی می‌شود. این صحیح است، زیرا هر دو نوع رابط [Interface1] را پیاده‌سازی می‌کنند؛

نتایج


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

اکنون راه‌حل دوم را با استفاده از اسکریپت [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";
  }
}

//کلاس مشتق ۱
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";
  }

}

// کلاس مشتق ۲
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());

توضیحات

  • خطوط ۳–۸: رابط [Interface1] تغییر نکرده است؛
  • خطوط ۱۰–۴۱: تریت [Trait1] جایگزین کلاس انتزاعی [AbstractClass] از راه‌حل ۱ می‌شود. کد به جز جزئیات زیر یکسان است:
    • خط ۱۰: تریت [Trait1] رابط [Interface1] را پیاده‌سازی نمی‌کند. این از نظر نحوی غیرممکن است؛
    • خطوط ۱۲–۱۳: ویژگی دید [protected] برای ویژگی‌های کلاس انتزاعی [AbstractClass] در اینجا به [private] تغییر می‌یابد. این دو ویژگی برای این منظور در نظر گرفته شده‌اند که به کلاس‌های مشتق دسترسی مستقیم به ویژگی‌های کلاس والد (محافظت‌شده) یا تریت (خصوصی) بدهند، بدون اینکه مجبور باشند از طریق گترها و سترها عمل کنند؛
    • تریت [Trait1] متد انتزاعی [doSomethingElse] را اعلام نمی‌کند؛
  • خطوط ۴۱–۶۲: کلاس [Class1] در راه‌حل ۲ با کلاس [Class1] در راه‌حل ۱ یکسان است، به جز جزئیات زیر:
    • خط ۴۱: کلاس [Class1] رابط [Interface1] را پیاده‌سازی می‌کند، در حالی که در راه‌حل ۱ این کلاس از کلاس انتزاعی [AbstractClass] ارث می‌برد؛
    • خط ۴۳: از تریت [Trait1] برای پیاده‌سازی بخشی از اینفراساخت استفاده می‌کند؛
  • خطوط ۶۵–۸۵: همان ملاحظات برای [Class1] نیز صدق می‌کند؛
  • خطوط ۸۷–۹۵: بقیه کد بدون تغییر باقی می‌ماند؛

نتایج


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

ما در واقع به همان نتایج می‌رسیم.

9.7. Conclusion

از مثال‌های قبلی روشن است که موارد استفاده‌ای که در آن‌ها استفاده از داش منفعت خالصی به همراه دارد، نامشخص هستند. در مثال‌های ما همیشه می‌توانیم با جایگزینی آن با یک کلاس، از آن صرف‌نظر کنیم. با این حال، به نظر می‌رسد استفاده از آن برای استخراج کد مشترک بین کلاس‌های مشتق مختلف مفید است، گویی آن کد متعلق به یک کلاس والد است. این کاری است که در مثال بعدی انجام خواهیم داد.