trait的使用技巧
trait是php5.4以后新增加的一個功能,可以將多個類中,共用的一些屬性和方法提取出來做來公共trait類,就像是裝配汽車的配件,如果你的類中要用到這些配件,就直接用use導入就可以了,相當於把trait中的代碼復制到當前類中.
因為trait不是類,所以不能有靜態成員,類常量,當然也不可能被實例化。
其實一個類中的代碼,可以分為二大部分:一是我們自己寫的代碼,暫且叫私有代碼吧,還有一部分就是公共代碼了,之前主要是由父類代碼組成。現在你的類中的公共代碼又多一個新成員:trait類代碼。
如果說:繼承可以縱向擴展一個類,那么trait就是橫向擴展一個類功能
下面以實例進行演示:
//1創建一個trait類Test1
<?php trait Test1 { public $name = 'PHP中文網'; //trait類中可以用屬性 public function hello1() //trait類中主要成員是方法 { return 'Test1::hello1()'; } } //2.創建triat類Test2 trait Test2 { function hello2() { return 'Test2::hello2()'; } } //3.創建Demo1類 class Demo1 { use Test1, Test2; } //進行測試 $obj = new Demo1; echo $obj->hello1(); //訪問trait類Test1中的hello1() echo '<hr>'; echo $obj->name; //訪問ttrait類Test1中的$name屬性 echo '<hr>'; echo $obj->hello2(); //訪問ttrait類Test1中的hello2()
trait可以互相嵌套,一個trait類中可以用use導入另一個trait類,理解成代碼復制就可以了.
例如本例中,在Test2中要用到Test1中的代碼,我們只要改動二個地方就可以了。
一是在Test2中用use Test1;導入Test1中的代碼,
二是在Demo1類中的,去掉對Test1的引用,只保留對Test2的引用,想想這是為什么?給大家當作一個思考題吧~
修改后的代碼如下:
//1創建一個trait類Test1
<?php trait Test1 { public $name = 'PHP中文網'; //trait類中可以用屬性 public function hello1() //trait類中主要成員是方法 { return 'Test1::hello1()'; } } //2.創建triat類Test2 trait Test2 { use Test1; function hello2() { //在Test2中訪問Test1中的屬性name,注意語法與普通類是一樣的 return 'Test2::hello2()'.$this->name; } } //3.創建Demo1類 class Demo1 { // use Test1, Test2; use Test2; } //進行測試 $obj = new Demo1; echo $obj->hello1(); //訪問trait類Test1中的hello1() echo '<hr>'; echo $obj->name; //訪問ttrait類Test1中的$name屬性 echo '<hr>'; echo $obj->hello2(); //訪問ttrait類Test1中的hello2()
剛才說過,類中導入的公共代碼,除了trait方法集,還可以有父類,如果在子類中訪問父類中的成員,大家應該很熟悉了,現在一個類除了可以從父類繼承成員,還可以從trait類中繼承,那么有一個問題就不可避免了,如果父類和trait類中的成員命名沖突怎么辦?說人話,就是重名了怎么辦?下面我們以方法重名來演示一下處理方案。
再創建一個類Demo,做為Demo1類的父類。
//3.創建父類Demo
class Demo { //在父類中創建一個與Test2重名的方法hello2() public function hello2() { return '父類Demo::hello2()'; } }
代碼如下:
//1創建一個trait類Test1 trait Test1 { public $name = 'PHP中文網'; //trait類中可以用屬性 public function hello1() //trait類中主要成員是方法 { return 'Test1::hello1()'; } } //2.創建triat類Test2 trait Test2 { use Test1; function hello2() { //在Test2中訪問Test1中的屬性name,注意語法與普通類是一樣的 return 'Test2::hello2()'.$this->name; } } //3.創建父類Demo class Demo { public function hello2() { return '父類Demo::hello2()'; } } //4.創建Demo1類 class Demo1 extends Demo { // use Test1, Test2; use Test2; } //進行測試 $obj = new Demo1; echo $obj->hello1(); //訪問trait類Test1中的hello1() echo '<hr>'; echo $obj->name; //訪問ttrait類Test1中的$name屬性 echo '<hr>'; echo $obj->hello2(); //訪問ttrait類Test1中的hello2()
再次訪問,會發現,結果與之前完全一樣沒有任何變化,父類Demo中的hello2方法好像隱身了,壓根不存在一樣的。事實上,父類Demo中的hello2方法當然是存在的,只是被trat類Test2中的同名方法hello2覆蓋掉了,原因就是:trait類中的同名方法,訪問優先級大於父類的同名方法。
如果我們就想訪問父類中的hello2方法,怎么辦呢?只有一個辦法,要么父類方法改名,要么Test2中的方法改名,我們把Test2中的hello2方法改成hello3,再次訪問,就可以看到父類的執行結果了。
那么,我們再進一點想一下,如果在子類也有一個hello2方法呢?那么結果會是什么樣?
我們來試一下,在Demo1類中添加如下代碼:
//4.創建Demo1類 class Demo1 extends Demo { // use Test1, Test2; use Test2; //在Demo1類中創建與Test2和父類Demo中同名的方法hello2() public function hello2() { return 'Demo1::hello()'; } }
在瀏覽器再次方法,果然不出所料,子類Demo1中的hello2方法的執行結果覆蓋掉了Test2中的同名方法
現在我們總結一下在同一個類中,同名方法的優先級:子類>Trait類>父類,與就是說,誰離調用者越近,誰的優先級就越高。
下面我們再討論最后一個問題:如果trait類中方法重名了,怎么辦?如果是trait類中被所有類共享的方法集,重名的可能性是非常大的。
下面我們修改一下代碼,刪除一些用不到代碼:
//1創建一個trait類Test1 trait Test1 { public function hello() { return 'Test1::hello()'; } } //2.創建triat類Test2 trait Test2 { function hello() { return 'Test2::hello()'; } } //3.創建類Demo class Demo { use Test1, Test2{ //用Test1中的hello()方法替代Test2中的同名方法 Test1::hello insteadof Test2; //Test2中的hello()方法用別名訪問 Test2::hello as test2Hello; } //這里千萬不要加分號 ; } //進行測試 $obj = new Demo; echo $obj->hello(); //訪問Test1中的hello() echo '<hr>'; echo $obj->test2Hello();//別名訪問Test2中的hello()