谈谈PHP中的trait
前言
PHP因为其单继承的特性,无法满足继承多个文件的情况下,在php 5.4版本出现了trait 。use 了某个trait 使用trait中的方法就像是调用自己的方法一样简便。在 trait 中定义基础的方法,方便其他类直接调用。
声明一个 trait 需要使用关键字 trait 调用它需要使用 use,且它无法被实例化。
目的
它存在的目的是为了减少代码的重复性,提高代码的复用性。
应用场景
当有些基础方法使用率比较高,或者是一种规则的定义需要我们统一计算,但他们不是每个类都需要用到时,我们可以考虑将它放入 trait 中。
假如一个类继承了基础类,这个类代码的大小其实是自己的代码大小加上基础类代码量的大小,所有基础类里面我们尽量定义那种每个类中都会用到的方法,并尽量保持基础类文件简洁。
它不仅仅是可复用代码段的集合,它应该是一组描述了某个特性的的属性与方法的集合。它的优点在于随意组合,耦合性低,可读性高
简单使用
1. 基础代码
trait dog{
public function eat(){
echo '狗吃狗粮</br>';
}
}
trait cat{
public function eat(){
echo '猫吃猫粮</br>';
}
}
class animal{
public function eat(){
echo '请问动物吃什么</br>';
}
}
class whatEat extends animal {
public function questios(){
echo '发起提问</br>';
}
}
$whatEat = new whatEat();
$whatEat->questios();
$whatEat->eat();
返回结果
此处只继承了基础类 animal 所以调用eat返回的是 ‘请问动物吃什么’
2. 调用其中一个trait中的方法
class whatEat extends animal {
use cat;
public function questios(){
echo '发起提问</br>';
}
}
$whatEat = new whatEat();
$whatEat->questios();
$whatEat->eat();
得到结果:
结论:我们很明显看出来 基础类animal中的eat直接被 cat中的eat覆盖掉了
3. 在自己的方法中定义一个和trait中同名的方法
class whatEat extends animal {
use cat;
public function questios(){
echo '发起提问</br>';
}
public function eat(){
echo '各个动物吃的东西都不同';
}
}
$whatEat = new whatEat();
$whatEat->questios();
$whatEat->eat();
得到结果:
结论:从上面我们知道 基础类animal 和whatEat自身,trait cat都拥有一个同名的方法 但是通过 【3】 我们知道了优先级最高的是类本身的方法。通过【2】我们知道了 基础类和trait 拥有同名方法,优先级最高的是trait内的方法。
所有 优先级是类本身内的方法 > trait中的方法 > 基础类的方法
4. 若是我们在类中同时引用两个 trait ,且这两个trait都拥有一个同名方法呢?
class whatEat extends animal {
use cat;
use dog;
public function questios(){
echo '发起提问</br>';
}
}
$whatEat = new whatEat();
$whatEat->questios();
$whatEat->eat();
返回结果:
结论:直接报 500 。所有同时引进的 trait 中有同名方法会直接导致类报错。因为不同trait中存在同名方法会存在冲突。
那我们如何处理这种错误呢。解决方法有两个
4.1. 冲突解决方法一,使用 insteadof 替换
class whatEat extends animal {
use cat,dog{
dog::eat insteadof cat;
cat::eat as cateat;
}
public function questios(){
echo '发起提问</br>';
}
}
$whatEat = new whatEat();
$whatEat->questios();
$whatEat->eat();
$whatEat->cateat();
返回结果:
结论:cat 中的方法直接被 dog 中的替换掉了。调用类中的 eat 直接返回的是 dog 中的 eat ,而 cat 中的 eat 被重新命名为 cateat 使用
4.2 冲突解决方法二 使用优先级的特性 让类的同名文件将其覆盖掉
class whatEat extends animal {
use cat,dog;
public function questios(){
echo '发起提问</br>';
}
public function eat(){
cat::eat();
}
public function eat2Dog(){
dog::eat();
}
}
$whatEat = new whatEat();
$whatEat->questios();
$whatEat->eat();
$whatEat->eat2Dog();
返回结果:
结论:将trait中的方法通过 优先级别将其覆盖掉。在定义一个别名方法将被覆盖掉了的方法重新实现出来。
5. as取别名后 原来的方法名依旧能使用
class whatEat extends animal {
use cat{
cat::eat as cateat;
}
public function questios(){
echo '发起提问</br>';
}
}
$whatEat = new whatEat();
$whatEat->questios();
$whatEat->eat();
$whatEat->cateat();
返回结果:
结论:不论是使用 eat 这个原本的名字 还是使用在类中重命名之后的 cateat 都得到了返回结果 ‘猫吃猫粮’
6. 进阶知识
as除了可以用来取别名之外 还可以给方法定义权限
6.1 公有的转为私有的
class whatEat extends animal {
use cat{
cat::eat as private cateat;
}
public function questios(){
echo '发起提问</br>';
}
}
$whatEat = new whatEat();
$whatEat->questios();
echo 'eat 返回的结果:';
$whatEat->eat();
echo '</br>';
echo 'private cateat返回的结果:';
$whatEat->cateat();
echo '</br>';
返回结果:
结论:返回未报错,方法 eat 被正常调用 但是被定义为 private 的 cateat 并没有返回结果。同样测试protected 也没有得到结果 只有定义为public 时候才返回出结果。
6.2 私有的变为公有的
trait cat{
public function eat(){
echo '猫吃猫粮</br>';
}
protected function eat2(){
echo '猫也吃小鱼干</br>';
}
}
class animal{
public function eat(){
echo '请问动物吃什么</br>';
}
}
class whatEat extends animal {
use cat{
cat::eat2 as public eat22;
}
public function questios(){
echo '发起提问</br>';
}
}
$whatEat = new whatEat();
$whatEat->questios();
echo 'eat2 返回的结果:';
返回结果
在看 取别名为 eat22 返回的结果
$whatEat = new whatEat();
$whatEat->questios();
echo 'eat22 返回的结果:';
$whatEat->eat22();
结论: 原本 trait 文件中权限为 protected 的方法 在class里面被重新定义了 权限和命名后,变为了公有方法,可以直接在外部调用。