php中trait的理解

trait是什么

trait是为解决php单继承而准备的一种代码复用机制。trait解决了单继承的限制,使开发人员能够在不同的类中复用trait中的method。trait和class组合的语义定义了一种减少复杂性的方式,避免传统多继承的相关典型问题。

trait说白了就是把复用代码写入到一个文件中,在类中通过use引入,然后使用$this调用。

trait的使用

我们之前的做法是把复用的代码放到父类中,子类继承父类,继而达到代码复用的效果的。

比如后台用户登录后根据用户得角色,权限,相应得进行菜单的展示。下面是我们之前得操作方式。

  • 首先在父类的controller中定义角色,权限等公用的方法。
<?php

namespace App\learn;

use GuzzleHttp\Psr7\Request;

class Controller
{

    public $userId;

    public function __construct($userId)
    {
        $this->userId = $userId;
    }

    /**
     * 获得用户角色
     * @param Request $request
     */
    public function roles(Request $request)
    {
        //******
        
        
        return $roles;
    }

    /**
     * 获得用户权限
     * @param Request $request
     */
    public function permissions(Request $request)
    {
        //*****
        return $permissions;
    }
}

  • 然后子类控制器中继承父类,相应的也就继承父类中的方法。
<?php

namespace App\learn;

class UserController extends Controller
{

    public function Login(\Request $request)
    {
        $roles = $this->roles($request);
        $permissions = $this->permissions($request);

        return response([
            'role' => $roles,
            'permission' => $permissions
        ])
    }
}

我们之前就是使用这种方式来达到代码复用的。现在可以思考一下这种方式有哪些缺点?

  • 父类会非常臃肿。现在只是在父类中增加权限的公共方法,如果后期增加更多的公共方法,父类将会非常庞大。
  • 不灵活。有些子类并不完全需要父类中的所有公共方法,可能只是需要1个或者完全不需要而已,不能做到即用即取
  • 可读性很差。父类那么庞大,阅读起来会非常困难。

而当我们使用trait时代码如下

  • 先定义一个角色权限的trait.
<?php

namespace App\learn;

use GuzzleHttp\Psr7\Request;

trait RoleTrait
{
    
    /**
     * 获得用户角色
     * @param Request $request
     */
    public function roles(Request $request)
    {
        //******


        return $roles;
    }

    /**
     * 获得用户权限
     * @param Request $request
     */
    public function permissions(Request $request)
    {
        //*****
        return $permissions;
    }
}


trait内的成员属性和class内的完全一致,class内能使用的成员属性在trait中也同样能使用。

  • 类中使用use关键字引入trait,使用$this关键字调用trait中的方法。

需要用到trait的类,使用use关键字引入即可。不需要trait的类则不需要引入,可以看到trait非常灵活,而不像上面那种方法只能在父类的controller中写入公共方法。

<?php

namespace App\learn;
use App\learn\RoleTrait;

class UserRoleTrait extends Controller
{
    use RoleTrait;

    public function Login(\Request $request)
    {
        $roles = $this->roles($request);
        $permissions = $this->permissions($request);

        return response([
            'role' => $roles,
            'permission' => $permissions
        ])
    }
}

trait有哪些好处?看下下面的伪代码

使用了trait的代码

class User extends Model
{
    use Authenticate, SoftDeletes, Arrayable, Cacheable;

    ...
}

传统的代码

class AdvansedUser {
  // ... 实现了 Authenticate, SoftDeletes, Arrayable, Cacheable 的所有方法
}
class User extends AdvansedUser
{
    ...
}

可以看出trait的优点如下:

  • 解决了父类臃肿的问题
  • 代码复用更加灵活。需用trait的类使用use关键字引入即可,做到了随机随用。
  • 可读性更强。使用trait后一眼就可以看出类中引用了哪些特性,如果trait名字起的好,可以正真做到见名知意。而在传统的代码中子类中根本看不出引入了哪些方法。
  • 耦合度低,随意组合,可读性高是以上3点的总结。

现在类似权限等操作也可以直接放到中间件中,同样可以做到代码复用的效果。

trait的使用场景

trait就是“特性”,“特性”,“特点”的意思。其实在我理解就是trait不仅仅是一个代码复用的方式,也是相关特性的一系列操作方法的一个集合,怎么理解呢?举个例子

  • 做过微信开发的同学知道微信有很多的api吧,比如“获取openid”,"获取用户信息",“发送模版消息”等等,我们就可以把这些操作抽象为一个wechat trait
<?php

namespace App\learn;

trait WechatTrait
{
    public function Openid()
    {
        //获取openid相关操作
    }

    public function userInfo()
    {
        //获取用户相关信息
    }
    
    public function sendTemplate
    {
        //发送模版消息
    }
}

需要微信相关操作的类use WechatTrait即可。

  • csv相关操作也是我们很常见的,类似“导出csv“,”上传csv“,"合并单元格",“设置字体颜色”等操作,我们就可以抽象成一个csv trait。
<?php
namespace App\learn;

trait CsvTrait
{
    public function export(){
        //导出相关操作
    }

    public function import()
    {
        //倒入相关操作
    }

    public function mergeCell()
    {
        //合并单元格
    }
    
    public function fontColor()
    {
        //设置字体颜色
    }
}

trait与父类的优先级

如果一个类继承了一个基类,同时也引入了一个trait。trait和基类中恰好有相同的方法名,那么在类中调用该方法时是调用trait内的方法还是基类中的方法呢?所以有一个trait和父类的优先级的问题。

经验证:trait的优先级会更高。当trait内的方法和基类中的方法名一致时,会优先调用trait内的方法

注意事项

  • trait不是一个class,interface同样也不是一个class,所以trait和interface不能被实例化,以至于不能使用__construct()构造函数,该魔术方法只有在类被实例化的时候才会调用。
  • trait内也是可以有abstract方法的。use了该trait的类必须实现trait内的抽象方法
<?php
namespace App\learn;

trait CsvTrait
{
    public function export(){
        //导出相关操作
        return 2;
    }
    abstract function ff();
}

调用CsvTrait

<?php
namespace App\learn;

class Csv
{
    use CsvTrait;
    
    public function ff(){
        return 22;
    }
}
  • interface,trait和class的结合
    上面说实现了interface的class必须全部重写interface中的抽象方法。
    现在还有另一种方式:继承了interface的class可以不用实现interface中的抽象方法,而抽象方法交给trait实现,然后class中use该trait即可。

具体实现如下:

  • 定义一个接口
<?php

namespace App\learn;

interface  UserInterface
{
    public const SEX=1;
    public function birthday($old);
    public function position($status);

}
  • 实现一个trait
<?php
namespace App\learn;

trait UserTrait
{
    public function birthday($old)
    {
        return $old;
    }
    public function position($status)
    {
        return $status;
    }
}

  • 定义一个实现了UserInterface接口的类
    由于此类use了一个UserTrait,而该trait内已经实现了接口中的所有抽象方法,所以此类不需要在重写接口中的所有抽象方法了。

这种方式是经常能够看到的。

<?php
namespace App\learn;

class School implements UserInterface
{
    use UserTrait;

}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 前言 众所周知,一直以来PHP和很多语言一样是单继承的语言,但是常常在编码过程中,我们需要在当前类中使用两个或两个...
    金星show阅读 1,898评论 0 3
  • 面向对象编程之类 定义一个简单的类 // 定义类,包含field以及方法 // 创建类的对象,并调用其方法 get...
    义焃阅读 819评论 0 2
  • 什么是面向对象? 面向对象是一种先进的编程模型,相对于面向过程编程,面向对象更具有逻辑性,使程序代码更加的健壮、易...
    我爱开发阅读 544评论 0 2
  • 这一朵花送给你, 让我为你戴在青丝里, 梳妆台前的花有两朵, 一朵是花,一朵是你。 我不送你并蒂开着的那三朵, 也...
    素絢阅读 281评论 6 14
  • 41.许多年过去了,生活静如止水。直到银河涣散成一面镜子,照亮我的肖像。 42.阳光生锈,葡萄金黄。拿什么献给你?...
    逆行的水星阅读 174评论 4 8