版本需求
自PHP5.4.0 起,PHP 实现了代码复用的一个方法,称为traits.其实实现方式有点类似C++ 的多继承
概述
Traits 是一种为类似PHP 的单继承语言(就是说类的继承是单继承,不像C++那样可以继承多个类,只能多继承接口)而准备的代码复用机制。Trait 为了减少单继承语言的限制,使开发人员能够自由的在不同层次结构内独立的类中复用方法集。Traits 和类组合的语义就是定义了一种方式来减少复杂性,避免传统多继承和混入类相关的典型问题。
结构上,Trait 和一个类相似,但仅仅旨在用细粒度和一致的方式来组合功能。Trait 不能通过它自身来实例化(就是说不能new)。它为传统继承增加了水平特性的组合,也就是说应用类的成员不需要继承。(其实说起来结构上有点像抽象类,都是不能被实例化的)
<?phptrait TraitDemo {
public function sayHello() {
echo "TraitDemo: Hello", "\n";
}
public function sayWorld() {
// parent::sayWorld();
echo "TraitDemo: World", "\n";
}
public function extension() {
echo "TraitDemo: extension", "\n";
}
}
class Base_Demo {
public function sayHello() {
echo "Base_Demo: Hello", "\n";
}
public function sayWorld() {
echo "Base_Demo: World", "\n";
}
}
class Demo extends Base_Demo {
use TraitDemo;
public function sayHello() {
echo "Demo: Hello", "\n";
}
}
$obj = new Demo();
$obj->sayHello();
echo "+=======================+\n";
$obj->sayWorld();
echo "+=======================+\n";
$obj->extension();
执行结果:
Demo: Hello
+=======================+
TraitDemo: World
+=======================+
TraitDemo: extendsion
优先级
从基类继承的成员函数被trait 插入的成员函数覆盖。优先顺序是来自当前类的成员覆盖了trait 的方法,而trait 方法则覆盖被继承基类的方法。
由上面的代码中的方法 sayWorld()
可以看出。当基类和trait 中都有sayWorld() 方法的时候,最终使用的是 trait 中的sayWorld().
同时,在调用 sayHello()
可以看出。该方法来自派生类。
一个比较有意思的使用方式是,可以在Trait 中使用parent:: 来访问基类的方法。比如trait中的 parent::sayWorld();
执行结果:
Demo: Hello
+=======================+
Base_Demo: World
TraitDemo: World
+=======================+
TraitDemo: extension
小结
trait,基类,派生类的优先级为:派生类最高,trait 次之,基类最低。
多个Trait
在使用多个Trait 的时候,通过都好将多个trait 进行分隔,在use 声明列出多个trait,可以都插入到一个类中。
<?phptrait Hello {
public function sayHello() {
echo 'Hello ';
}
}
trait World {
public function sayWorld() {
echo 'World';
}
}
class MyHelloWorld {
use Hello, World;
public function sayExclamationMark() {
echo '!';
}
}
$o = new MyHelloWorld();
$o->sayHello();
$o->sayWorld();
$o->sayExclamationMark();
?>
执行结果:
Hello World!
冲突的解决
如果在两个trait 都插入了一个同名的方法,如果没有明确解决冲突会产生一个致命错误。这里就类似C++中多继承的二义性。在继承了多个相同方法的时候,需要明确告诉程序选择哪个方法来被执行。
为了解决多个trait 在同一个类中的命名冲突,需要使用insteadof
操作符来明确指定使用冲突方法的哪一个。这样就排除了其他的重名方法。也可以使用as
操作符将其中一个冲突的方法以另外一个名称来引入(这个方式就有点像namespace 中的as 方法了,做了一个别名)。
<?phptrait A {
public function smallTalk() {
echo 'a';
}
public function bigTalk() {
echo 'A';
}
}
trait B {
public function smallTalk() {
echo 'b';
}
public function bigTalk() {
echo 'B';
}
}
class Talker {
use A, B {
B::smallTalk insteadof A;
A::bigTalk insteadof B;
}
}
class Aliased_Talker {
use A, B {
B::smallTalk insteadof A;
A::bigTalk insteadof B;
B::bigTalk as talk;
}
}
修改方法的访问控制
<?phptrait HelloWorld {
public function sayHello() {
echo 'Hello World!';
}
}
// 修改 sayHello 的访问控制class MyClass1 {
use HelloWorld { sayHello as protected; }
}
// 给方法一个改变了访问控制的别名// 原版 sayHello 的访问控制则没有发生变化class MyClass2 {
use HelloWorld { sayHello as private myPrivateHello; }
}
开始的担心
本来在了解到这个trait 的时候,还担心如果在使用自动加载的地方能够完成trait 的自动加载么?经过测试发现,只要将Trait 的命名规则符合autoload 的规则,保持跟类的方式一致,那么就不会出现这个问题。