PHP类的秘密(12) Trait的秘密

402 阅读2分钟

Trait是一种在类中实现代码复制的方法.

记住这句话, 你就掌握了Trait的精髓. Trait有很多特性和规则, 但归根结底都是这句话的应用和体现.

定义Trait

trait TezhengA {
    public function smallTalk() {
        echo 'a';
    }
    public function bigTalk() {
        echo 'A';
    }
}

class SK
{
use TezhengA
}

$kl=new SK
$kl->smallTalk();   //'a'
$kl->bigTalk();     //'A'

上例就是最简单的trait定义和调用, 可以将class SK中的trait直接替换成它所包含的代码, 是没有问题的. 所以这就给开发者一个很简单的方式去复制代码, 只需要一个use语句即可!

Trait和普通类定义有哪些区别呢

主要是以下三点:

首先, Trait不是类, 它只是一段可以被复用的代码.

无法通过 trait 自身来实例化。因为它只是一段代码, 所以实例化没有意义.

可以包含抽象方法, 要求让继承子类去定义抽象方法.

trait Asd
{
    abstract function Rew();
}

class BDS
{
    use Asd;
    function Rew()
    {
   echo'abs'; // TODO: Implement Rew() method.
    }
}
$ls=new bds;
var_dump($ls);

可以看到除了抽象类以外, trait也可以定义抽象方法, 并要求使用的类去定义这些方法.

除了以上3点区别以外, trait并没有什么特殊的地方, 它可以包含静态属性, 静态成员, 私有属性, 支持各种可见度.

在使用trait时可能遇到的问题

两个trait中有同名的方法:

trait TezhengA {
    public function smallTalk() {
    echo 'a';
    }
    public function bigTalk() {
    echo 'A';
    }
}

trait TezhengB {
    public function smallTalk() {
    echo 'b';
    }
    public function bigTalk() {
    echo 'B';
    }
}

class Aliased_Talker {
use TezhengA, TezhengB {
    TezhengB::smallTalk insteadof TezhengA;   //TezhengB::smallTalk优先于A
    TezhengA::bigTalk insteadof TezhengB;     //TezhengA::bigTalk优先于B
    TezhengB::bigTalk as talk;                //起别名;
}
}
$as=new Aliased_Talker;
$as->bigTalk();         //A
$as->smallTalk();       //b
$as->talk();      //B

可以使用insteadof关键词指明替代关系, 或者使用as关键词起个别名. 问题解决.

如果trait中的方法和父类重名怎么办?

trait Counter1{
function inc(){
    echo'father';
}
}

class C1 {
use Counter1;
}

class C2 extends C1{
use counter1;
function inc()
{
    echo 'child';
}
}
$o = new C2();
$o->inc();    //child, 子类自己的方法优先

子类也会继承父类的trait, 当方法名冲突时, 优先级: 当前类的成员 > trait 的方法 > 被继承的方法

如果trait中使用了final, 会有什么影响?

trait Fas {
    final public function hello($s) { print "$s, hello!"; }
}
class Bar {
    use Fas;
    final public function hello($s) { print "hello, $s!"; }      
}
class Cee extends Bar
{
    public function hello($s) { print "hello!"; }    //报错, final在子类生效
    }

可以看到, trait中的final关键词在class Bar并没有影响, 但是子类继承中, 却产生了影响.

trait和类可以定义同名属性么?

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

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

通过上例可以看到, 如果同名属性的值也一样, 则正常, 如果同名属性有不同的值, 则会报错.

Trait使用中的一些技巧

Trait可以进行嵌套, trait 也能够使用 trait, trait中所有方法名字不变, 如果两个trait有同名方法, 一样使用insteadof或as解决冲突

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

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

trait HelloWorld {
    use Hello, World{
World:: sayHello instead of Hello;
}      //使用2个traits
}

class MyHelloWorld {
    use HelloWorld;      //调用helloworld
}

$o = new MyHelloWorld();
$o->sayHello();          //可以直接调用子trait中的方法

同时insteadof也可以用一个方法替代多个同名方法:

class TalkerD {
    use AD, BD, CD {
    AD::sayHello insteadof BD, CD;
    AD::sayWorld insteadof BD, CD;               
    }
}

关键词as可以指定方法可见度:

  use HelloWorld { sayHello as protected; }

特别注意

当trait中含有静态属性时, 要注意静态属性的值, 因为不同类use trait时可能会有不同的值:

trait Beer {
    protected static $type = 'Light';
    public static function printed(){
        echo static::$type.PHP_EOL;
    }
    public static function setType($type){
        static::$type = $type;
    }
}

class Ale {
    use Beer;
}

Beer::setType("Dark");   //通过trait直接调用方法

class Lager {
    use Beer;
}

Beer::setType("Amber");  //通过trait直接调用方法

Beer::printed();  // Prints: Amber
Ale::printed();   // Prints: Light
    Lager::printed(); //Dark
    

通过上例可以看到, trait可以直接调用自己的静态方法, 并修改自己的静态属性, 这样导致继承子类会有不同的属性值.

感谢阅读, 如有不准确或错误请留言指正, 我会及时修正, 感谢!

欢迎喜欢技术的小伙伴儿和我交流, 微信1296386616

总结不易, 请勿私自转载, 否则别怪老大爷不客气!

参考资料:

www.php.net PHP官方文档