Skip to main content

Trait详解以及应用

一、为啥要学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

发表评论

电子邮件地址不会被公开。 必填项已用*标注

9 + 1 =