美文网首页
白马非马 【Traits】

白马非马 【Traits】

作者: 做梦枯岛醒 | 来源:发表于2017-10-22 13:55 被阅读14次

那不是歌 那是孤单的歌 , 这白马非马的逻辑鲜有附和。《白马非马》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的类,可以自由选择方法,而不必生成无用代码。

那么这里得到的结果就是这样的

深度截图20171022120907.png

那么不知道通过这个生活中的例子你有没有发现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,比较浅显,如有错误请在评论区指出。

相关文章

网友评论

      本文标题:白马非马 【Traits】

      本文链接:https://www.haomeiwen.com/subject/blwjyxtx.html