PHP的trait

PHP的trait

Trait是什么?

Trait是一种代码复用机制!

Trait的意思是“特点、特性、品质,少许”,说实话,我不知道中文到底解释为哪个意思合适,这也是为什么网上的文章都直接把它叫trait,而不像interface可以叫接口,abstract class可以叫抽象类!

Trait是了解决像PHP、Java(Java也有Trait)等这样的单继承语言而准备的一种代码复用机制。

通俗的说,就是各个类有完全相同的方法,我们把这些方法都抽出来写到一个trait中,然后分别在这些类里用use引入这个trait,以达到复用代码,精简代码的目的。

有些人可能会说,有相同的方法为什么不写到一个base类里,然后让那些要用到这些相同方法的类都继承base类呢?这样不是同样能代码复用吗?然而,问题是,有时候这些“类”并不是同一类的,它们无法继承同一个类。

Trait的写法

只需要把类的class关键字改为trait,那么这个类就变成一个trait了,也就是说trait的写法其实跟类是一样的,只不过关键字换成trait而已

trait UploadTrait {
    public function upload($filePath){
        echo 'Upload file success!';
    }
}

同名方法优先级

如果有同名方法,则当前类中的方法会覆盖trait中的方法,而trait中的方法又会覆盖了父类中的方法,即:本类 > trait > 父类

比如,现在有类A、类B、trait C,它们都有一个同名的方法common(),那么类A的对象调用该方法时,到底调的哪个呢?会调类A的,如果类A没有,只有类B(A的父类)和trait C有,那就会使用trait C的。

Trait的特点

  • Trait只能被类use,而不能被实例化;
  • Trait中的方法必须有方法体,因为它里面的方法,是从各个类中抽出来的公共方法,所以必须要有方法体;
  • 抽象类可以引用trait,接口类不可以引入trait(因为接口类的方法不能有方法体,而trait的方法一定有方法体);
  • 不能拿trait与接口、抽象类来对比,因为它们是完全不一样的东西;
  • 一个类可以同时use多个trait,逗号隔开即可;
  • Trait之间可以相互use;
  • Trait可以写抽象方法(就是抽象类中的那种抽象方法);
  • Trait中可以用$this调用trait中不存在的方法(该方法存在于use它的类中);

Trait同名方法冲突

注意,这里的同名方法冲突,与前面说的同名方法覆盖是不同的,这里说的冲突,是两个trait有同名方法,而这两个trait又被同一个类use(引入)了的情况下的冲突。

前面说过一个类可以引入多个trait,那如果这多个trait中有名字相同的方法呢?答:会报错!

解决方法:在后面用大括号声明,用哪个代替哪个

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代替A中的
        B::smallTalk insteadof A;
        //使用A的bigTalk代替B中的
        A::bigTalk insteadof B;
        //把A中的bigTalk重命名为largeTalk
        A::bigTalk as largeTalk;
    }
}

使用as修改访问控制

访问控制就是public/protected/private。

trait HelloWorld {
    public function sayHello() {
        echo 'Hello World!';
    }
}

// 修改 sayHello 的访问控制
class MyClass1 {
    use HelloWorld { sayHello as protected; }
}

// 给方法一个改变了访问控制的别名
// 原版sayHello的访问控制则没有发生变化
class MyClass2 {
    //sayHello2的访问控制属性变成了private,但sayHello还是public(即没有变化)
    use HelloWorld { sayHello as private sayHello2; }
}

Trait可以use trait

trait Hello {
    public function sayHello() {
        echo 'Hello ';
    }
}

trait World {
    public function sayWorld() {
        echo 'World!';
    }
}

//该trait由trait Hello和trait World组成,当然它自己也能有它的方法
trait HelloWorld {
    use Hello, World;
}

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;
    }
}

在trait中调用不存在的方法

可以使用$this调用trait中不存在的方法(该方法存在于use这个trait的类中)

trait A {
    public function hello() {
        return 'Hello';
    }
    //world()方法在trait A中并不存在,但它还是可
    //以调用,因为这个world()方法存在于use它的类中
    public function talk() {
        echo $this->hello() . ' ' . $this->world();
    }
}

class Talker {
    use A;
    public function world(){
        return 'World';
    }
}

$talker = new Talker;
$talker->talk();

trait中含静态变量

我们知道,静态变量只会初始化一次,比如常见的,一个函数内有一个静态变量,先初始化为0,然后自加1,那么调用两个这个函数,这个静态变量是会累加的,因为它只初始化一次!

可是如果这样的方法在trait中,并且trait被两个类都use了,然后用这两个类的对象分别去调用来自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();
// echo 1
$o->inc();

$p = new C2();
// echo 1
$p->inc();

Trait属性冲突问题

前面说过,trait的写法,只需要把类的class关键字改成trait即可,其它写法都跟类相同,所以trait中自然也可以定义属性,但是如果use triat的类中有相同属性,而且初始值不同时,会报错

trait PropertiesTrait {
    public $same = true;
    public $different = false;
}

class PropertiesExample {
    use PropertiesTrait;
    // PHP 7.0.0 后没问题,之前版本是E_STRICT提醒
    public $same = true;
    // 致命错误
    public $different = true;
}

Trait与接口的区别

网上经常遇到有人问trait与接口的区别,其实这两个根本就不应该放在一起比较。

因为trait与接口根本就不是同一个东西,接口是为了提供一个规范,让大家都遵守,而trait只是公共代码块。

与接口相似的是抽象类,所以要问,也应该问接口与抽象类的区别(见php接口类与抽象类的区别)。

打赏
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
AlvinCR
14 days ago

您好,请问可以互加友链吗,个人博客alvincr.com。文章数量虽没有您的那么多,但大都是原创,个人QQ948191901。

0
Would love your thoughts, please comment.x
()
x

扫码在手机查看
iPhone请用自带相机扫
安卓用UC/QQ浏览器扫

PHP的trait