作為PHP5.4引入新特性trait,有不少讀者感覺比較陌生。
本篇從背景、作用、使用三個方面分析trait,力求以最通俗的語言解釋清楚。
背景
PHP在面向對象的設計時, 被設計為單繼承. 什麼叫單繼承, 一個類A隻能繼承一個類B, 再由B類繼承C類. 而不能A類同時繼承B類和C類.
正确的寫法:demo1.php
<?php
class C{}
class B extends C{}
class A extends B{}
$obj = new A;
var_dump($obj);
會得到一個對象
/home/vagrant/Code/blog/demo1.php:11:
object(A)[1]
//注意
//A類同時繼承B類和C類, 這樣寫是不允許的
例如:
<?php
class C{}
class B{}
class A extends B,C{}
$obj = new A;
var_dump($obj);
//代碼是錯誤的。
( ! ) Parse error: syntax error, unexpected token ",", expecting "{" in /home/vagrant/Code/blog/demo2.php on line 6
說人話, 就是一個兒子隻能有一個父親, 但是反過來呢?一個父親是可以有多個兒子的.
如果想A類既使用B的方法又使用C的方法, PHP提出了接口(Interface). PHP雖然是單繼承, 但是可以多實作. 什麼叫多實作, 一個類A可以同時實作B接口和C接口.(demo3.php)
<?php
interface C{}
interface B{}
class A implements B,C{}
//A類同時實作接口B和接口C, 這樣是允許的
$obj = new A;
var_dump($obj);
同樣會得到一個對象
/home/vagrant/Code/blog/demo3.php:11:
object(A)[1]
作用
關鍵詞: 假多繼承, 代碼複用, 類的包含
現在有這樣一個場景
需求: A類既可以使用B類的方法又可以使用C的方法, A類和B類是繼承關系, 但是B和C沒有繼承關系, 怎麼解決?
方法:A類繼承B類, 同時實作C接口(demo4.php)
<?php
interface C{
public function ccc();
}
class B{
public function bbb()
{
echo "我是B類的bbb方法<br>";
}
}
class A extends B implements C{
public function ccc()
{
echo "我是A類,我要實作接口C定義的方法<br>";
}
}
$obj = new A;
$obj ->ccc();
$obj ->bbb();
~
~
強烈建議上面的代碼,敲10遍以上。
(拓展: 接口)
接口不能被執行個體化
接口是為了規範顯現它的子類,以達到統一的目的
接口中隻有:常量和抽象方法
接口裡的所有方法都是抽象方法,不能寫方法體,并且,接口的抽象方法不需要寫abstract。
子類繼承接口的方法,叫 實作,是以說接口是用來實作的(涉及php基礎知識)
如果這個時候有一個D類, 也要繼承B類, 實作C接口, 需要重複的寫這段代碼(demo5.php)
<?php
interface C{
public function ccc();
}
class B{
public function bbb()
{
echo "我是B類的bbb方法<br>";
}
}
class A extends B implements C{
public function ccc()
{
echo "我是A類,我要實作接口C定義的方法<br>";
}
}
class D extends B implements C{
//這段代碼是重複的
public function ccc()
{
echo "我是D類,我要實作接口C定義的方法<br>";
}
}
$obj = new A;
$obj ->ccc();
$obj ->bbb();
使用
思考: 能不能将這部分重複的代碼定義在一個類中, A類和D類包含這個類呢?
這個時候trait應用而生(demo6.php)
<?php
interface C{
public function ccc();
}
class B{
public function bbb()
{
echo "我是B類的bbb方法<br>";
}
}
trait E{
public function ccc()
{
echo "我是trait,我實作了ccc方法<br>";
// echo "我是trait,我是".__CLASS__."類,我實作了ccc方法<br>";
}
}
class A extends B implements C{
use E;
}
class D extends B implements C{
use E;
}
$obj = new A;
$obj ->ccc();
$obj ->bbb();
運作結果如下:
我是trait,我實作了ccc方法
我是B類的bbb方法
上面的代碼可以繼續優化一下
通過上面的用法, 我們就可以這樣總結
- trait本質上還是一個類
- 接口(interface)規定了方法的定義, trait規定了方法的實作
- 可以認為A類D類等等其他類在需要ccc方法的時候包含了trait
綜上, trait是PHP實作多繼承的一種折中的方法, 姑且叫它"假多繼承", 可以認為一個類(A類)包含了另一個類E(trait), 最終目的是為了實作代碼複用.