那不是歌 那是孤单的歌 , 这白马非马的逻辑鲜有附和。《白马非马》By Vae
从PHP的5.4.0版本开始,PHP提供了一种全新的代码复用的概念,那就是Trait。Trait其字面意思是"特性"、"特点",可以理解为为类添加的特性
Trait 是为类似 PHP 的单继承语言而准备的一种代码复用机制。Trait 为了减少单继承语言的限制,使开发人员能够自由地在不同层次结构内独立的类中复用 method。Trait 和 Class 组合的语义定义了一种减少复杂性的方式,避免传统多继承和 Mixin 类相关典型问题。
Trait 和 Class 相似,但仅仅旨在用细粒度和一致的方式来组合功能。 无法通过 trait 自身来实例化。它为传统继承增加了水平特性的组合;也就是说,应用的几个 Class 之间不需要继承
引用segmentfault网友的一句话:
trait = abstract class - interface
//trait提供了另一个维度的灵活性
1.简单的Traits例子
<?php
trait Hello{
public function sayHello(){
echo 'Hello';
}
}
trait World{
public function sayWorld(){
echo 'World';
}
}
class HelloWorld{
use Hello,World;
public function sayMark(){
echo '!';
}
}
$a = new HelloWorld();
$a->sayHello();
$a->sayWorld();
$a->sayMark();
输出:Hello World!
我们使用trait关键字来定义类,在类里添加一个方法。
然后在主类里面用use关键字来使用trait,这样子主类对象就拥有访问trait类里的方法的能力
这样大概了解了他的使用方法,那么再看一个例子来理解一下他的使用。
<?php
trait fly{
public function fly(){
echo "我可以飞翔\n";
}
}
class Trans{
public function __construct(){
echo "父类交通工具\n";
}
public function move(){
echo "所有的交通工具都可以动\n";
}
}
class Plane extends Trans{
use fly;
public function __construct(){
parent::__construct();
echo "我是飞机\n";
}
}
class Car extends Trans{
public function __construct(){
parent::__construct();
echo "我是汽车\n";
}
}
$plane = new Plane();
$plane->move();
$plane->fly();
$car = new Car();
$car->move();
//$car->fly();
可以看到我这里写了一个不太准确的生活中的类,一个飞机,一个汽车,两者都可以move,但是飞机还可以fly,那么我们使用trait给飞机增加一个飞的属性。当然你可以说飞行这个属性直接写在飞机类里不就好了,但是如果我说此时又多了一个火箭的类呢,火箭也会飞,那么就不能在火箭里写fly了,那么就有重复代码了。
那么这时候你又会想到多继承或者接口,但这无疑会增加代码的重叠性,很可能最终得到一个复杂的架构,同时我们也可以看到traits比接口多的内容就有有方法定义,继承自接口的类必须全部实现其中方法,而继承自trait的类,可以自由选择方法,而不必生成无用代码。
那么这里得到的结果就是这样的
那么不知道通过这个生活中的例子你有没有发现trait的优势性。
2.升华一下
我们这样来举例子。
<?php
trait Fashi{
public function Fashi(){
echo "法师通用属性";
}
}
trait Fuzhu{
public function Fuzhu(){
echo "辅助通用属性";
}
}
class Guiguzi{
use Fashi,Fuzhu;
public function hero(){
echo "我的定位是法师和辅助";
echo "\n我有";
$this->Fashi();
$this->Fuzhu();
}
}
$guiguzi = new Guiguzi();
$guiguzi->hero();
拿我最喜欢的王者荣耀英雄鬼谷子来举例子,当然什么法师辅助这种单词就不要太在意了。
比如说鬼谷子是法师和辅助,那么他将拥有法师和辅助的一些本质属性,有了trait我们可以很轻易的加入这两种属性,但是用多继承或者接口的写法都是很不方便的,或者拿抽象类来说吧,假如我这个英雄是法师和辅助,那么我需要实现一个法师方法一个辅助方法,假如今天我修改了英雄定位,那么我又需要改动他的抽象类,这一改动不要紧,我就需要改动其他用了该类的英雄,很不方便,假如说我用继承来抽取公用方法,那可能需要写很多代码,但是使用trait就不一样了,假设王者荣耀有5大职业,那么我只需要定义五大职业的trait,对应每个英雄就可以use相应的职业属性了。
那么到这里我们可以总结一下trait的特点,他不是简单的抽取重复代码实现复用,他更多的是强调对于类的特性,以及即插即用的快捷,耦合度低可读性高是他主要的特点。
3.冲突
使用trait当然也会遇到冲突的问题,比如下面的这个例子
<?php
trait First{
public function first(){
echo "输出first";
}
}
trait Second{
public function first(){
echo "输出Second";
}
}
class Test{
use First,Second;
public function Test(){
echo "输出内容:\n";
}
}
$test = new Test();
$test->Test();
$test->first();
两个trait都有first方法,那么必定会导致系统无法判断你调用哪个方法,那样子就乱了。
所以运行结果如下:
PHP Fatal error: Trait method First has not been applied, because there are collisions with other trait methods on Testin /home/surine/Php/Trait_first.php on line 14
那么官方给我们的解决方法是insteadof和as操作符
insteadof的功能是指出当发生冲突时,系统会选择哪一个冲突方法来调用。
as是可以给冲突的方法加以别名,然后通过别名来访问,同时as还可以修改权限(别名修改,不是修改源)
我们修改一下class Test
class Test{
use First,Second{
First::first insteadOf Second;
}
public function Test(){
echo "输出内容:\n";
}
}
使用insteadOf优先调用First里的方法。
那么as的作用是当我们使用insteadof覆盖其中一个方法后,我们应该使用as来给它别名用于访问。
class Test{
use First,Second{
First::first insteadOf Second;
Second::first as second;
}
}
这样我们通过$test->second();就可以访问了。
同时我们可以修改他的权限
Second::first as private second_new;
4.组合使用和优先级
一个类可以调用多个trait,trait也可以调用trait。
<?php
trait Hello{
public function sayHello(){
echo "Hello";
}
}
trait World{
use Hello;
public function sayWorld(){
$this->sayHello();
echo "World";
}
}
class Test{
use World;
public function Test1(){
$this->sayWorld();
echo "!";
}
}
$a = new Test();
$a->Test1();
输出HelloWorld!
当类里和trait含有同名方法,那么类方法被调用。
<?php
trait Hello{
public function sayHello(){
echo "Hello";
}
}
class Test{
use Hello;
public function sayHello(){
echo "test say Hello";
}
}
$test = new Test();
$test->sayHello();
输出test say Hello
如果父类也有同名方法呢?
<?php
trait Hello{
public function sayHello(){
echo "Hello";
}
}
class Base{
public function sayHello(){
echo "base say Hello";
}
}
class Test extends Base{
use Hello;
}
$test = new Test();
$test->sayHello();
trait优先级要大于父类,输出Hello
那么我们可以总结如果类本身,类use的trait,类的父类都含有同名方法的时候,优先级 类本身>trait>父类
5.其他
- 同样的属性也可以被放在trait里,但是属性不能覆盖,也就是说当trait里面有一个a属性,在use他的类里不能重新定义a属性,只能使用a属性
- trait可以使用静态变量和方法,也可以使用抽象方法,他们所达成的效果和类里使用是一样的。
第一次结合书籍和网络资料来理解trait,比较浅显,如有错误请在评论区指出。