Trait 是为类似 PHP 的单继承语言而准备的一种代码复用机制。Trait 为了减少单继承语言的限制,使开发人员能够自由地在不同层次结构内独立的类中复用 method。Trait 和 Class 组合的语义定义了一种减少复杂性的方式,避免传统多继承和 Mixin 类相关典型问题。
Trait 和 Class 相似,但仅仅旨在用细粒度和一致的方式来组合功能。 无法通过 trait 自身来实例化。它为传统继承增加了水平特性的组合;也就是说,应用的几个 Class 之间不需要继承。
1.为什么要用trait
假如我们现在有两个类:order和user,我们希望这两个类都能提供记录日志的功能,那我们有几种实现方式呢,我想到的只有下面几种:
使用extends
使用interface
使用trait
使用extends
class Log{
public function WriteLog(){
}
}
class Order extends Log{
}
class User extends Log{
}
这种方法在语法上是可行的,但是仔细想想总感觉这样不舒服,我觉得继承并不是这样用的,继承是分层次的,下一层的分类是上一层的特化,也继承了上一层的所有特性。但是在这儿Order和User根本就和Log没有任何关系,也就是Order和User根本就不是一种Log类,所以说,在这种场景下用继承我觉得是错误的用法。
使用interface
interface ILog{
public function WriteLog();
}
class Order implements Log{
public function WriteLog()
{
}
}
class User implements Log{
public function WriteLog()
{
}
}
现在看用接口的方式比继承好多了,不过仔细想想,感觉还是不好,因为Order和User类中需要实现WriteLog的方法,这导致出现一个问题,以后万一WriteLog出现了Bug,那么Order和User都需要修改WriteLog的代码,这明显的违反了DRY原则(Don’t Repeat Yourself),所以这种方式也不是最优方案。
使用trait
其实trait就是为这类问题而生(我也是刚学,姑且这么认为吧),trait允许你写一个半Class的Log trait,然后将Log trait注入到Order和User中。
trait Log{
public function WriteLog(){
...
}
}
class Order{
Use Log;
}
class User{
Use Log;
}
因为trait允许我们把实现代码写在trait中的,这样我们就可以避免用接口实现带来的代码重复的问题。
既然trait已经将WriteLog都写好了,其他类就不用实现了,PHP使用了Use关键字,让我们将trait的函数直接注入到Order类与User类。也就是说, 虽然Order与User都没有实现WriteLog,但是通过使用注入Log trait之后,就相当于有了WiteLog方法了。
对于PHP来说,在使用Use关键字时,PHP只是将trait的所有变量与方法“复制”进Class内,让class马上拥有Trait的所有功能。
2.trait的作用域
因为trait在实作上是使用复制, 所以原本在trait内申明的public、protected、private变量与方法都会复制到class内,也就是说,class内被trait加进来变量与方法的作用域将与原trait完全一样。
比较一下继承的extends,当使用继承时,只有public与protected的变数与函式会被继承下来,private函式将不会被继承下来。
3.insteadof和as关键字
由于PHP使用“复制”的办法来把trait引入到类中来,所以有可能出现trait所定义的变量与方法已经在其他class已经被定义,也就是变量与方法可能同名冲突,为了解决这种冲突,PHP给出了insteadof与as关键字来解决。
trait 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;//通过insteadof关键字告诉PHP我们要用B trait中的smallTalk,而不要使用A trait中的smallTalk
A::bigTalk insteadof B;
}
}
class Aliased_Talker {
use A, B {
B::smallTalk insteadof A;
A::bigTalk insteadof B;
B::bigTalk as talk; //通过 as 关键字告诉PHP我们在随后的代码中我们要用talk来替代B::bigTalk
}
}
可能有人会问如果我要用A::smallTalk怎么办,其实很简单的,直接用as关键字就可以了,A::smallTalk as AsmallTalk。
如果我在解决冲突时直接用as而不用insteadof是否可以,不可以的,必须先用insteadof来告诉PHP冲突时使用哪个trait中的方法,如果你想用被排除的方法时就需要用as关键字了
4.优先级
1)同名方法优先级
优先级从大到小依次是这样的:
当前类-->trait-->父类
咱们通过代码来说明
class Base {
public function sayHello() {
echo 'Hello ';
}
}
trait SayWorld {
public function sayHello() {
echo 'World!';
}
}
class MyHelloWorld extends Base {
use SayWorld;
public function sayHello() {
echo 'PHP!';
}
}
$o = new MyHelloWorld();
$o->sayHello(); //此处输出的是 PHP!
咱们再修改下代码
class Base {
public function sayHello() {
echo 'Hello ';
}
}
trait SayWorld {
public function sayHello() {
echo 'World!';
}
}
class MyHelloWorld extends Base {
use SayWorld;
}
$o = new MyHelloWorld();
$o->sayHello(); //此处输出的是World!
2)变量优先级
Trait 定义了一个属性后,类就不能定义同样名称的属性,否则会产生 fatal error。 有种情况例外:属性是兼容的(同样的访问可见度、初始默认值)。 在 PHP 7.0 之前,属性是兼容的,则会有 E_STRICT 的提醒。。
class Base {
public $a='World!';
}
trait SayWorld {
public $a='PHP!';
}
class MyHelloWorld extends Base {
use SayWorld;
}
$o = new MyHelloWorld();
echo $o->a;
这个代码会产生一个Fatal错误:PHP Fatal error: Base and SayWorld define the same property ($a) in the composition of MyHelloWorld. However, the definition differs and is considered incompatible.
class Base {
public $a='World!';
}
trait SayWorld {
public $a='World!';
}
class MyHelloWorld extends Base {
use SayWorld;
}
$o = new MyHelloWorld();
echo $o->a;
这个代码正常输出:World!
5.修改方法的访问控制
使用 as 语法来调整方法的访问控制。
trait HelloWorld {
public function sayHello() {
echo 'Hello World!';
}
}
// 修改 sayHello 的访问控制
class MyClass1 {
use HelloWorld { sayHello as protected; }
}
// 给方法一个改变了访问控制的别名
// 原版 sayHello 的访问控制则没有发生变化
class MyClass2 {
use HelloWorld { sayHello as private myPrivateHello; }
}
6.Trait的抽象成员
为了对使用的类施加强制要求,trait 支持抽象方法的使用。
trait Hello {
public function sayHelloWorld() {
echo 'Hello'.$this->getWorld();
}
abstract public function getWorld();
}
class MyHelloWorld {
private $world;
use Hello;
public function getWorld() {
return $this->world;
}
public function setWorld($val) {
$this->world = $val;
}
}
7.Trait的静态成员
trait Counter {
public function inc() {
static $c = 0;
$c = $c + 1;
echo "$c\n";
}
}
class C1 {
use Counter;
}
class C2 {
use Counter;
}
$o = new C1(); $o->inc(); // echo 1
$p = new C2(); $p->inc(); // echo 1
8.Trait的静态方法
trait StaticExample {
public static function doSomething() {
return 'Doing something';
}
}
class Example {
use StaticExample;
}
Example::doSomething();
9.Trait的常量
在Trait中不允许定义常量的
5.最佳实践
转载于:https://blog.51cto.com/13715626/2106734