PHP的trait
Table of Contents
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与接口、抽象类来对比,因为它们是完全不一样的东西;
- 一个类可以同时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接口类与抽象类的区别)。
您好,请问可以互加友链吗,个人博客alvincr.com。文章数量虽没有您的那么多,但大都是原创,个人QQ948191901。