برنامه نویسی شیء گرا PHP – ضمیمه 4: بررسی Trait ها

درسنامه درس 14 از سری شی گرایی در PHP
php-oop-trait-analytics

با سلام، در قسمت قبل با مفهوم کلی trait ها آشنا شدیم. در این قسمت قصد داریم به بررسی بیشتر آن ها، ذکر فواید و معایب، مثال واقعی و ... بپردازیم.

مزایای استفاده از trait ها چیست؟

استفاده از trait ها دو مزیت اصلی دارد:

  • کاهش مقدار کد های تکراری (code duplication)
  • در عین حال، دوری از ساختار های پیچیده و تو در توی وراثت چند گانه

معایب استفاده از trait ها چیست؟

trait ها به خاطر راحتی ای که در اختیار شما قرار می دهند باعث می شوند که به قول گفتنی به خودتان سخت نگیرید! و کلاس هایی بنویسید که کار های بسیار زیادی انجام می دهند.

این کار مثل وقتی است که مهمان دارید و به جای تمیز کردن خانه، وسایل اتاق را در کشوی کمدتان جا می کنید!

با خودتان فکر می کنید که چرا برای کار های متعدد کلاس های مختلفی تعریف کنم؟ می توانم همه را یکجا بنویسم! این کار در برنامه نویسی اصلا توصیه نمی شود و شما را از اصل مسئولیت واحد (single responsibility principle) دور می کند. این اصل برنامه نویسی می گوید:

The single responsibility principle is a computer programming principle that states that every module or class should have responsibility over a single part of the functionality provided by the software, and that responsibility should be entirely encapsulated by the class.

به زبان ساده تر می توان گفت: هر ماژول یا کلاس باید مسئولیت تنها یک بخش از عملکرد ارائه شده توسط نرم افزار را بر عهده بگیرد.

عیب دیگری که می توان برای trait ها نام برد این است که اگر قصد بازنگری سورس کد را داشته باشید نمی توانید تمام متدهای یک کلاس را یک جا ببینید و باید در trait ها نیز دنبال آن ها بگردید.

باید توجه کنید که trait ها مثل شمشیر دو لبه هستند! یعنی اگر به درستی استفاده شوند ابزار بسیار عالی هستند اما امان از روزی که برنامه نویس بخواهد برای راحت کردن کار خودش از آن ها استفاده کند.

موقعیت های مناسب برای استفاده از trait ها

یکی از بهترین موقعیت هایی که می توان از trait ها استفاده کرد زمانی است که چندین کلاس مرتبط به هم داریم اما این کلاس ها نباید از یک کلاس انتزاعی واحد ارث بری داشته باشند.

برگردیم به همان مثال شبکه ی اجتماعی از جلسه ی قبل؛ ما شیء های مختلفی برای کار های مختلف داشتیم و می توانیم به آن ها مواردی را نیز اضافه بکنیم، مثل post و message و link و photo و note.

این کلاس ها تا حد زیادی به هم مربوط و شبیه هستند اما post و photo و link و note اشیائی هستند که می توانند به طور عمومی بین کاربران به اشتراک گذاشته شوند در حالی که اشیاء message پیام های خصوصی هستند و نباید به صورت عمومی در بیایند. از طرفی post و photo و note و link همگی از اینترفیس shareable (به معنی "قابل اشتراک گذاری") استفاده می کنند:

interface Shareable {

public function share();

}

با توجه به حالتی که با آن مواجه هستیم:

  • آیا عقلانی است که متد ()share را در تمام کلاس هایی که از اینترفیس shareable استفاده می کنند دوباره نویسی کنیم؟ قطعا خیر!
  • آیا عقلانی است که کلاسی به اسم AbstractShare بسازیم تا کلاس هایی که از اینترفیس shareable استفاده می کنند آن را extend کنند؟ خیر!
  • آیا عقلانی است که متد ()share را به عنوان قسمتی از یک کلاس انتزاعی به نام AbstractEntity قرار دهیم ولی دسترسی آن را از شیء message قطع کنیم؟ خیر!
  • آیا عقلانی است که از یک trait به نام ShareableTrait استفاده کنیم که همان کار اینترفیس را می کند اما در عین حال می تواند تنها به کلاس هایی که به آن نیاز دارند اضافه شود؟ بله! راه درست همین است!

استفاده از trait در پروژه های واقعی

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

پیشنهاد من برای شما این است که هر پروژه را بررسی و آنالیز کنید، دقیقا مثل سوال هایی که در قسمت بالا از شما پرسیده شد. حالت های مختلف را در نظر بگیرید و از بین آن ها بهترین روش را انتخاب کنید، اما این ها کافی نیست. مسئله ی مهمی که به شما کمک می کند با این ابزار ها آشنا شوید، سر زدن به پروژه های open source است.

با نگاه کردن به سورس کد این پروژه ها می توانید از تجربه ی چندین ساله ی توسعه دهندگانشان استفاده کنید. منبع بسیار خوب برای این کار، پروژه ی open source گوگل است! حتما به این وب سایت سری بزنید و از ابزار های بسیار قوی این وب سایت استفاده کنید. همچنین می توانید پروژه هایی مثل Cashier مربوط به لاراول را در GitHub سرچ کنید و به کد های آن ها نگاهی بیندازید.

اولویت متدهای همنام در trait ها

گفتیم که trait ها در زبان ساده کارشان copy & paste کردن کد است. حال اگر دو متد مختلف با نام یکسان در یک trait یا کلاسی که از trait استفاده می کند قرار بگیرد، از کدام استفاده خواهد شد؟ اولویت متدها به شکل زیر خواهد بود:

  • متدهای trait، متدهای به ارث رسیده از کلاس پدر را override (باطل) می کنند.
  • متدهایی که در خود کلاس تعریف شده باشند، متدهای trait را override (باطل) می کنند.

به مثال زیر توجه کنید:

trait Hello
{
    function sayHello() {
        return "Hello";
    }

    function sayWorld() {
        return "Trait World";
    }

    function sayHelloWorld() {
        echo $this->sayHello() . " " . $this->sayWorld();
    }

    function sayBaseWorld() {
        echo $this->sayHello() . " " . parent::sayWorld();
    }
}

class Base
{
    function sayWorld(){
        return "Base World";
    }
}

class HelloWorld extends Base
{
    use Hello;
    function sayWorld() {
        return "World";
    }
}

$h =  new HelloWorld();
$h->sayHelloWorld(); // Hello World
$h->sayBaseWorld(); // Hello Base World

در این مثال یک کلاس به نام HelloWorld داشته که از کلاس Base منشعب شده است و هر دو کلاس هم متدی با نام ()sayWorld دارند اما پیاده سازی شان متفاوت است. از طرفی trait با نام Hello را در کلاس HelloWorld اضافه کرده ایم. در آخر کد خروجی های ما را می بینید و متوجه اولویت بندی ای که گفتیم می شوید.

امیدوارم از این قسمت استفاده ی کافی را برده باشید.

در پناه حق.

تمام فصل‌های سری ترتیبی که روکسو برای مطالعه‌ی دروس سری شی گرایی در PHP توصیه می‌کند:
نویسنده شوید
دیدگاه‌های شما (1 دیدگاه)

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

ع ن
28 آذر 1399
سلام. وقت بخیر. فرق $this->sayWorld() و parent::sayWorld() چیه و اصولا چرا از parent::sayWorld() استفاده کردید؟ اگه توضیح بیشتری بدید ممنون میشم.

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.