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;
}