آموزش trait در شی گرایی

تاریخ: 05 اردیبهشت 1400

در php روشی وجود دارد که با استفاده از آن می توانیم کدها رو reuse کنیم. این روش استفاده از trait ها می باشد.

Trait ها در شی گرایی چه کار می کنند؟

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

در php فقط می توانیم از یک کلاس extend کنیم لذا کدهای مشترک باید در یک والد قرار داده شود و این قضیه نوعی محدودیت محسوب می شود. trait ها این مشکل را رفع می کنند.

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

ما می توانیم دستورات عمومی و مشترک را در یک trait وارد نماییم و سپس آن trait را در کلاسی که می خواهیم ایمپورت کنیم.

 

تعریف trait در php

برای ایجاد یک trait از کلمه کلیدی trait استفاده می کنیم و سپس نام trait را می آوریم:

trait Footer {
    function copy_right() {
        return 'Biawp.ir';
    }
}

 

چون trait یک کلاس است، از قوانین نام گذاری کلاس ها پیروی می کند.

 

استفاده از trait در کلاس

پس از اینکه trait مدنظر را ایجاد کردیم، می توانیم در هر کلاسی که نیاز داریم آن را استفاده کنیم.

برای استفاده از trait کافیست فایل آن را در کلاس مدنظر ایمپورت کرده و با استفاده از کلمه کلیدی use آن را وارد کلاس کنیم.

مثلا من در فایل traits.php قصد دارم trait های خودم را وارد کنم و در فایل index.php آن ها را استفاده کنم:

//traits.php
trait Footer {
    public $url = ' Biawp.ir';
    function copy_right() {
        return 'Biawp.ir';
    }
}

//index.php
require 'traits.php';
class Biawp {
    use Footer;
    function __construct() {
        echo $this->copy_right() . $this->url;
    }
}
new Biawp; //output: Biawp.ir Biawp.ir

 

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

همینکه از کلمه use استفاده می کنیم به صورت خودکار دستورات trait در محل موردنظر وارد می شود. مانند require کردن یک فایل!

 

استفاده از trait در subclass

اگر از یک trait در subclass استفاده کنیم، کلاس والد override می شود (Precedence Order):

trait Footer {
    function copy_right() {
        return 'Biawp.ir';
    }
}
class Biawp {
    function copy_right() {
        return 'www.Biawp.ir';
    }
}
class PluginDev extends Biawp {
    use Footer;
}
$object = new PluginDev;
echo $object->copy_right(); //output: Biawp.ir

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

 

استفاده از trait قبل از override شدن

ممکن است از یک trait در کلاس استفاده کنیم ولی پس از use کردن مشخصات مشابه ای بیاوریم. در اینصورت override صورت می گیرد:

trait Footer {
    function copy_right() {
        return 'Biawp.ir';
    }
}
class Biawp {
    use Footer;
    function copy_right() {
        return 'www.Biawp.ir';
    }
}
$object = new Biawp;
echo $object->copy_right(); //output: www.Biawp.ir

در کد بالا متد copy_right که در trait هم وجود داشت را پس از trait آوردیم. لذا متد trait بروزرسانی می شود.

 

استفاده از چند trait

یکی از ویژگی های مهم trait استفاده از چند trait می باشد. این قضیه، مشکل extend کردن چندگانه در inheritance را رفع می کند:

trait Hello {
    function say_hello() {
        return 'Hello ';
    }
}
trait World {
    function say_world() {
        return 'World';
    }
}
class Biawp {
    use Hello, World;
    function say_hello_world() {
        return $this->say_hello() . $this->say_world();
    }
}
$object = new Biawp;
echo $object->say_hello_world(); //output: Hello World

مشاهده می کنید که برای use کردن چند trait می توانیم از کاما استفاده کنیم. البته use کردن به صورت جدا نیز امکان پذیر می باشد.

 

تداخل نام متدهای trait یا Conflict Resolution در trait

اگر بیش از یک trait را use کنیم و متدهای تکراری در آنها وجود داشته باشد، خطای fatal خواهیم گرفت:

trait Hello {
    function say_hello(){}
}
trait World {
    function say_hello(){}
}
class Biawp {
    use Hello, World;
}
new Biawp; //error methods collision

یکی از راه حل های php برای حل این مشکل، استفاده از کلمه کلیدی insteadof می باشد.

با استفاده از این کلمه مشخص می کنیم که متد مشخصی از trait مشخص را مدنظر داریم. مثلا در مثال بالا می خواهیم بگوییم که متد say_hello برای trait با نام World مدنظر می باشد:

trait Hello {
    function say_hello(){
        return 'Hello1';
    }
}
trait World {
    function say_hello(){
        return 'Hello2';
    }
}
class Biawp {
    use Hello, World {
        World::say_hello insteadof Hello;
    }
    function say_hello_main() {
        return $this->say_hello();
    }
}
$object = new Biawp;
echo $object->say_hello_main(); //output: Hello2

 

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

راه حل این مشکل، استفاده از as می باشد. با کلمه کلیدی as می توانیم یک نام ثانویه برای متد در نظر بگیریم تا از تداخل نام متدها جلوگیری شود:

trait Hello {
    function say_hello(){
        return 'Hello1';
    }
}
trait World {
    function say_hello(){
        return 'Hello2';
   }
}
class Biawp {
    use Hello, World {
        World::say_hello insteadof Hello;
        Hello::say_hello as say_hello2;
    }
    function say_hello_main() {
        return $this->say_hello2();
    }
}
$object = new Biawp;
echo $object->say_hello_main(); //output: Hello1

 

تغییر سطح دسترسی متدهای trait در کلاس

ممکن است نیاز داشته باشیم سطح دسترسی متد trait را در کلاس و هنگام use کردن تغییر دهیم. برای این کار از همان روش as استفاده می کنیم:

trait Hello {
    protected function say_hello(){
        return 'Hello';
    }
}
class Biawp {
    use Hello{
        say_hello as public;
    }
}
$object = new Biawp;
echo $object->say_hello(); //output: Hello

 

تداخل نام ویژگی ها در trait و کلاس

اگر در trait، یک ویژگی خاص تعریف شد، امکان بازتعریف آن ویژگی در کلاس وجود ندارد:

trait Hello {
    public $url = ' Biawp.ir';
} 
class Biawp { 
    use Hello; 
    public $url = 'Www.Biawp.ir'; //fatal error
} 
new Biawp;

 

دیدگاهتان را بنویسید