php之trait-实现多继承

PHP是单继承的语言,在PHP 5.4 Traits出现之前,PHP的类无法同时从两个基类继承属性或方法。php的Traits和Go语言的组合功能类似,通过在类中使用use关键字声明要组合的Trait名称,而具体某个Trait的声明使用trait关键词,Trait不能直接实例化。具体用法请看下面的代码:

 1 <?php
 2     trait Drive {
 3         public $carName = 'trait';
 4         public function driving() {
 5             echo "driving {$this->carName}\n";
 6         }
 7     }
 8     class Person {
 9         public function eat() {
10             echo "eat\n";
11         }
12     }
13     class Student extends Person {
14         use Drive;
15         public function study() {
16             echo "study\n";
17         }
18     }
19     $student = new Student();
20     $student->study();
21     $student->eat();
22     $student->driving();

输出结果如下:

1 study
2 eat
3 driving trait

上面的例子中,Student类通过继承Person,有了eat方法,通过组合Drive,有了driving方法和属性carName。

如果Trait、基类和本类中都存在某个同名的属性或者方法,最终会保留哪一个呢?通过下面的代码测试一下:

 1 <?php 
 2     trait Drive {
 3         public function hello() {
 4             echo "hello drive\n";
 5         }
 6         public function driving() {
 7             echo "driving from drive\n";
 8         }
 9     }
10     class Person {
11         public function hello() {
12             echo "hello person\n";
13         }
14         public function driving() {
15             echo "driving from person\n";
16         }
17     }
18     class Student extends Person {
19         use Drive;
20         public function hello() {
21             echo "hello student\n";
22         }
23     }
24     $student = new Student();
25     $student->hello();
26     $student->driving();

输出结果如下:

1 hello student
2 driving from drive

因此得出结论:当方法或属性同名时,当前类中的方法会覆盖 trait的 方法,而 trait 的方法又覆盖了基类中的方法。

如果要组合多个Trait,通过逗号分隔 Trait名称:

1 use Trait1, Trait2;

如果多个Trait中包含同名方法或者属性时,会怎样呢?答案是当组合的多个Trait包含同名属性或者方法时,需要明确声明解决冲突,否则会产生一个致命错误。

 1 <?php
 2 trait Trait1 {
 3     public function hello() {
 4         echo "Trait1::hello\n";
 5     }
 6     public function hi() {
 7         echo "Trait1::hi\n";
 8     }
 9 }
10 trait Trait2 {
11     public function hello() {
12         echo "Trait2::hello\n";
13     }
14     public function hi() {
15         echo "Trait2::hi\n";
16     }
17 }
18 class Class1 {
19     use Trait1, Trait2;
20 }

输出结果如下:

1 PHP Fatal error:  Trait method hello has not been applied, because there are collisions with other trait methods on Class1 in ~/php54/trait_3.php on line 20

使用insteadof和as操作符来解决冲突,insteadof是使用某个方法替代另一个,而as是给方法取一个别名,具体用法请看代码:

 1 <?php
 2 trait Trait1 {
 3     public function hello() {
 4         echo "Trait1::hello\n";
 5     }
 6     public function hi() {
 7         echo "Trait1::hi\n";
 8     }
 9 }
10 trait Trait2 {
11     public function hello() {
12         echo "Trait2::hello\n";
13     }
14     public function hi() {
15         echo "Trait2::hi\n";
16     }
17 }
18 class Class1 {
19     use Trait1, Trait2 {
20         Trait2::hello insteadof Trait1;
21         Trait1::hi insteadof Trait2;
22     }
23 }
24 class Class2 {
25     use Trait1, Trait2 {
26         Trait2::hello insteadof Trait1;
27         Trait1::hi insteadof Trait2;
28         Trait2::hi as hei;
29         Trait1::hello as hehe;
30     }
31 }
32 $Obj1 = new Class1();
33 $Obj1->hello();
34 $Obj1->hi();
35 echo "\n";
36 $Obj2 = new Class2();
37 $Obj2->hello();
38 $Obj2->hi();
39 $Obj2->hei();
40 $Obj2->hehe();

输出结果如下:

1 Trait2::hello
2 Trait1::hi
3 
4 Trait2::hello
5 Trait1::hi
6 Trait2::hi
7 Trait1::hello

as关键词还有另外一个用途,那就是修改方法的访问控制:

 1 <?php
 2     trait Hello {
 3         public function hello() {
 4             echo "hello,trait\n";
 5         }
 6     }
 7     class Class1 {
 8         use Hello {
 9             hello as protected;
10         }
11     }
12     class Class2 {
13         use Hello {
14             Hello::hello as private hi;
15         }
16     }
17     $Obj1 = new Class1();
18     $Obj1->hello(); # 报致命错误,因为hello方法被修改成受保护的
19     $Obj2 = new Class2();
20     $Obj2->hello(); # 原来的hello方法仍然是公共的
21     $Obj2->hi();  # 报致命错误,因为别名hi方法被修改成私有的

Trait 也能组合Trait,Trait中支持抽象方法、静态属性及静态方法,测试代码如下:

 1 <?php
 2 trait Hello {
 3     public function sayHello() {
 4         echo "Hello\n";
 5     }
 6 }
 7 trait World {
 8     use Hello;
 9     public function sayWorld() {
10         echo "World\n";
11     }
12     abstract public function getWorld();
13     public function inc() {
14         static $c = 0;
15         $c = $c + 1;
16         echo "$c\n";
17     }
18     public static function doSomething() {
19         echo "Doing something\n";
20     }
21 }
22 class HelloWorld {
23     use World;
24     public function getWorld() {
25         return 'get World';
26     }
27 }
28 $Obj = new HelloWorld();
29 $Obj->sayHello();
30 $Obj->sayWorld();
31 echo $Obj->getWorld() . "\n";
32 HelloWorld::doSomething();
33 $Obj->inc();
34 $Obj->inc();

输出结果如下:

1 Hello
2 World
3 get World
4 Doing something
5 1
6 2

转载自:http://tabalt.net/blog/php-traits/