一、为啥要学Trait
首先大家都知道,面向对象有三大特性:封装、继承、多态
封装,封装类、属性、方法,提供调用,抽象化流程。
继承, 扩展父类的内容、增加新的功能点。
多态,指在父类中定义的属性或行为被子类继承之后,可以具有不同的数据类型或表现出不同的行为。这使得同一个属性或行为在父类及其各个子类中具有不同的语义。
但是,PHP这里的继承是单继承,即extends后面只能有1个类名;但是1个类可以有无数个子类,子类和父类只能出现在有继承关系的2个类之间,所以Trait这个特性就是为了解决单继承的问题,自 PHP 5.4.0 起,PHP 实现了一种代码复用的方法,称为 trait,Trait 和 Class 相似,但仅仅旨在用细粒度和一致的方式来组合功能,无法通过 trait 自身来实例化。它为传统继承增加了水平特性的组合,也就是说,应用的几个 Class 之间不需要继承。
二、Trait语法
- 声明的关键字为必须trait , 替换class关键字
- 在引用的类里面调用,直接使用use 类名即可
- 需要注意,声明trait之后,只可以被use引用,无法再实例化调用
- 只要声明了trait类,成员属性就会失效,即便是声明了private,也可以被调用,此处可以理解为use关键字将Trait的实现代码Copy了一份到引用该Trait的类中。
- 只要应用了Trait类,就可以应用Trait类的属性、方法。
代码:
<?php
trait Log
{
public function start()
{
echo 'start';
}
public function middle()
{
echo 'middle';
}
protected function stop()
{
echo 'stop';
}
}
class Person
{
use Log;
public function execute()
{
$this->start();
$this->middle();
$this->stop();
}
}
$obj = new Person();
$obj->execute();
返回结果:startmiddlestop
三、Trait的优先级
当前类的方法 > trait方法 > 基类 , 即当前类中的方法会覆盖 trait 方法,而 trait 方法又覆盖了基类中的方法
例子1:
<?php
class Base {
public function sayHello() {
echo 'Hello ';
}
}
trait SayWorld {
public function sayHello() {
parent::sayHello();
echo 'World!';
}
}
class MyHelloWorld extends Base {
use SayWorld;
}
$o = new MyHelloWorld();
$o->sayHello();
?>
这个例子可以说明,Trait的优先级要大于继承的父类。
例子2:
<?php
trait HelloWorld {
public function sayHello() {
echo 'Hello World!';
}
}
class TheWorldIsNotEnough {
use HelloWorld;
public function sayHello() {
echo 'Hello Universe!';
}
}
$o = new TheWorldIsNotEnough();
$o->sayHello();
?>
返回值为Hello Universe
可以证明类本身的优先级大于其Trait的优先级。
三、引用多个Trait类
通过逗号分隔,在 use 声明列出多个 trait,可以都插入到一个类中。
代码:
<?php
trait 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();
?>
四、解决Trait冲突
如果两个 trait 都插入了一个同名的方法,如果没有明确解决冲突将会产生一个致命错误。
为了解决多个 trait 在同一个类中的命名冲突,需要使用 insteadof 操作符来明确指定使用冲突方法中的哪一个。
以上方式仅允许排除掉其它方法,as 操作符可以 为某个方法引入别名。 注意,as 操作符不会对方法进行重命名,也不会影响其方法。
<?php
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;
A::bigTalk insteadof B;
}
}
class Aliased_Talker {
use A, B {
B::smallTalk insteadof A;
A::bigTalk insteadof B;
B::bigTalk as talk;
}
}
//相同类名字固定方法,使用insteadof来确认方法来源
(new Talker())->smallTalk(); //echo b
(new Talker())->bigTalk(); //echo A
//相同类名还要所有方法,可以是as关键字创建别名解决冲突
(new Aliased_Talker())->talk(); //echo B