PHP中Trait及其应用详解


Posted in PHP onFebruary 14, 2017

从PHP的5.4.0版本开始,PHP提供了一种全新的代码复用的概念,那就是Trait。Trait其字面意思是”特性”、”特点”,我们可以理解为,使用Trait关键字,可以为PHP中的类添加新的特性。 

熟悉面向对象的都知道,软件开发中常用的代码复用有继承和多态两种方式。在PHP中,只能实现单继承。而Trait则避免了这点。下面通过简单的额例子来进行对比说明。 

1. 继承 VS 多态 VS Trait 

现在有Publish.php和Answer.php这两个类。要在其中添加LOG功能,记录类内部的动作。有以下几种方案:

 继承
 多态
 Trait 

1.1. 继承 

如图:

PHP中Trait及其应用详解

代码结构如下:

// Log.php
<?php
Class Log
{
 public function startLog()
 {
  // echo ...
 }

 public function endLog()
 {
  // echo ...
 }
}
// Publish.php
<?php
Class Publish extends Log
{

} // Answer.php
<?php
Class Answer extends Log
{

}

可以看到继承的确满足了要求。但这却违背了面向对象的原则。而发布(Publish)和回答(Answer)这样的操作和日志(Log)之间的关系并不是子类与父类的关系。所以不推荐这样使用。 

1.2. 多态 

如图:

PHP中Trait及其应用详解 

实现代码:

// Log.php
<?php
Interface Log
{
 public function startLog();
 public function endLog();
}
// Publish.php
<?php
Class Publish implements Log
{
 public function startLog()
 {
  // TODO: Implement startLog() method.
 }
 public function endLog()
 {
  // TODO: Implement endLog() method.
 }
}
// Answer.php
<?php
Class Answer implements Log
{
 public function startLog()
 {
  // TODO: Implement startLog() method.
 }
 public function endLog()
 {
  // TODO: Implement endLog() method.
 }
}

记录日志的操作应该都是一样的,因此,发布(Publish)和回答(Answer)动作中的日志记录实现也是一样的。很明显,这违背了DRY(Don't Repeat Yourself)原则。所以是不推荐这样实现的。 

1.3. Trait 

如图:

PHP中Trait及其应用详解 

实现代码如下:

// Log.php
<?php
trait Log{
 public function startLog() {
  // echo ..
 }
 public function endLog() {
  // echo ..
 }
}
// Publish.php
<?php
class Publish {
 use Log;
}
$publish = new Publish();
$publish->startLog();
$publish->endLog();
// Answer.php
<?php
class Answer {
 use Log;
}
$answer = new Answer();
$answer->startLog();
$answer->endLog();

可以看到,我们在没有增加代码复杂的情况下,实现了代码的复用。 

1.4. 结论 

继承的方式虽然也能解决问题,但其思路违背了面向对象的原则,显得很粗暴;多态方式也可行,但不符合软件开发中的DRY原则,增加了维护成本。而Trait方式则避免了上述的不足之处,相对优雅的实现了代码的复用。 

2. Trait的作用域 

了解了Trait的好处,我们还需要了解其实现中的规则,先来说一下作用域。这个比较好证明,实现代码如下:

<?php
class Publish {
 use Log;
 public function doPublish() {
  $this->publicF();
  $this->protectF();
  $this->privateF();
 }
}
$publish = new Publish();
$publish->doPublish();

执行上述代码输出结果如下:
public function
protected function
private function

可以发现,Trait的作用域在引用该Trait类的内部是都可见的。可以理解为use关键字将Trait的实现代码Copy了一份到引用该Trait的类中。 

3. Trait中属性的优先级

说到优先级,就必须要有一个对比的参照物,这里的参照对象时引用Trait的类及其父类。 

通过以下的代码来证明Trait应用中的属性的优先级:

<?php
trait Log
{
 public function publicF()
 {
  echo __METHOD__ . ' public function' . PHP_EOL;
 }
 protected function protectF()
 {
  echo __METHOD__ . ' protected function' . PHP_EOL;
 }
}

class Question
{
 public function publicF()
 {
  echo __METHOD__ . ' public function' . PHP_EOL;
 }
 protected function protectF()
 {
  echo __METHOD__ . ' protected function' . PHP_EOL;
 }
}

class Publish extends Question
{
 use Log;

 public function publicF()
 {
  echo __METHOD__ . ' public function' . PHP_EOL;
 }
 public function doPublish()
 {
  $this->publicF();
  $this->protectF();
 }
}
$publish = new Publish();
$publish->doPublish();

上述代码的输出结果如下:
Publish::publicF public function
Log::protectF protected function

通过上面的例子,可以总结出Trait应用中的优先级如下:
 1.来自当前类的成员覆盖了 trait 的方法
 2.trait 覆盖了被继承的方法 

类成员优先级为:当前类>Trait>父类

4. Insteadof和As关键字 

在一个类中,可以引用多个Trait,如下:

<?php
trait Log
{
  public function startLog()
  {
    echo __METHOD__ . ' public function' . PHP_EOL;
  }
  protected function endLog()
  {
    echo __METHOD__ . ' protected function' . PHP_EOL;
  }
}

trait Check
{
  public function parameterCheck($parameters) {
    // do sth
  }
}

class Publish extends Question
{
  use Log,Check;
  public function doPublish($para) {
    $this->startLog();
    $this->parameterCheck($para);
    $this->endLog();
  }
}

通过上面的方式,我们可以在一个类中引用多个Trait。引用多个Trait的时候,就容易出问题了,最常见的问题就是两个Trait中如果出现了同名的属性或者方法该怎么办呢?这个时候就需要用到Insteadof 和 as 这两个关键字了.请看如下实现代码:

<?php

trait Log
{
  public function parameterCheck($parameters)
  {
    echo __METHOD__ . ' parameter check' . $parameters . PHP_EOL;
  }

  public function startLog()
  {
    echo __METHOD__ . ' public function' . PHP_EOL;
  }
}

trait Check
{
  public function parameterCheck($parameters)
  {
    echo __METHOD__ . ' parameter check' . $parameters . PHP_EOL;
  }

  public function startLog()
  {
    echo __METHOD__ . ' public function' . PHP_EOL;
  }
}

class Publish
{
  use Check, Log {
    Check::parameterCheck insteadof Log;
    Log::startLog insteadof Check;
    Check::startLog as csl;
  }

  public function doPublish()
  {
    $this->startLog();
    $this->parameterCheck('params');
    $this->csl();
  }
}

$publish = new Publish();
$publish->doPublish();

执行上述代码,输出结果如下:
 Log::startLog public function
Check::parameterCheck parameter checkparams
Check::startLog public function

就如字面意思一般,insteadof关键字用前者取代了后者,as 关键字给被取代的方法起了一个别名。 

在引用Trait时,使用了use关键字,use关键字也用来引用命名空间。两者的区别在于,引用Trait时是在class内部使用的。

 以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。

PHP 相关文章推荐
php时间不正确的解决方法
Apr 09 PHP
php中time()和mktime()方法的区别
Sep 28 PHP
php多文件上传实现代码
Feb 20 PHP
ThinkPHP框架任意代码执行漏洞的利用及其修复方法
Jul 04 PHP
PHP魔术方法的使用示例
Jun 23 PHP
CI框架(ajax分页,全选,反选,不选,批量删除)完整代码详解
Nov 01 PHP
php 使用redis锁限制并发访问类示例
Nov 02 PHP
php mysql实现mysql_select_db选择数据库
Dec 30 PHP
php传值方式和ajax的验证功能
Mar 27 PHP
PHP实现基于面向对象的mysqli扩展库增删改查操作工具类
Jul 18 PHP
PHP扩展mcrypt实现的AES加密功能示例
Jan 29 PHP
Yii框架安装简明教程
May 15 PHP
php数组指针操作详解
Feb 14 #PHP
PHP中include()与require()的区别说明
Feb 14 #PHP
php中的抽象方法和抽象类
Feb 14 #PHP
PHP静态成员变量和非静态成员变量详解
Feb 14 #PHP
PHP静态成员变量
Feb 14 #PHP
php中序列化与反序列化详解
Feb 13 #PHP
PHP最常用的正则表达式
Feb 13 #PHP
You might like
PHP的explode和implode的使用说明
2011/07/17 PHP
匹配csdn用户数据库与官方用户的重合度并将重叠部分的用户筛选出来
2011/12/25 PHP
php获取目标函数执行时间示例
2014/03/04 PHP
PHP的Yii框架中Model模型的学习教程
2016/03/29 PHP
PHP strcmp()和strcasecmp()的区别实例
2016/11/05 PHP
浅谈php(codeigniter)安全性注意事项
2017/04/06 PHP
Prototype使用指南之dom.js
2007/01/10 Javascript
jquery ui resize 中border-box的bug修正
2015/04/26 Javascript
ECMAScript6函数剩余参数(Rest Parameters)
2015/06/12 Javascript
JS模拟酷狗音乐播放器收缩折叠关闭效果代码
2015/10/29 Javascript
javascript如何定义对象数组
2016/06/07 Javascript
Bootstrap基本样式学习笔记之标签(5)
2016/12/07 Javascript
bootstrap中模态框、模态框的属性实例详解
2017/02/17 Javascript
gulp解决跨域的配置文件问题
2017/06/08 Javascript
ES6解构赋值实例详解
2017/10/31 Javascript
JS字符串去除连续或全部重复字符的实例
2018/03/08 Javascript
全面解析vue router 基本使用(动态路由,嵌套路由)
2018/09/02 Javascript
JS/HTML5游戏常用算法之碰撞检测 包围盒检测算法详解【凹多边形的分离轴检测算法】
2018/12/13 Javascript
vue之延时刷新实例
2019/11/14 Javascript
Vue computed 计算属性代码实例
2020/04/22 Javascript
vue项目中企业微信使用js-sdk时config和agentConfig配置方式详解
2020/12/15 Vue.js
python单链表实现代码实例
2013/11/21 Python
python基于queue和threading实现多线程下载实例
2014/10/08 Python
numpy中实现ndarray数组返回符合特定条件的索引方法
2018/04/17 Python
django的登录注册系统的示例代码
2018/05/14 Python
对python读取zip压缩文件里面的csv数据实例详解
2019/02/08 Python
python面试题小结附答案实例代码
2019/04/11 Python
Python3 Tkinter选择路径功能的实现方法
2019/06/14 Python
HTML5 Canvas中绘制椭圆的4种方法
2015/04/24 HTML / CSS
HomeAway的巴西品牌:Alugue Temporada
2018/04/10 全球购物
木马的传播途径主要有哪些
2016/04/08 面试题
护士试用期自我鉴定
2014/02/08 职场文书
公司活动总结范文
2014/07/01 职场文书
CSS3 实现的图片悬停的切换按钮
2021/04/13 HTML / CSS
Python办公自动化解决world文件批量转换
2021/09/15 Python
Android开发手册TextInputLayout样式使用示例
2022/06/10 Java/Android