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调用Oracle存储过程
Oct 09 PHP
PHP获取文件后缀名的三个函数
Oct 15 PHP
谈谈关于php的优点与缺点
Apr 11 PHP
php中strtotime函数用法详解
Nov 15 PHP
THINKPHP支持YAML配置文件的设置方法
Mar 17 PHP
php获取网页上所有链接的方法
Apr 03 PHP
搭建基于Docker的PHP开发环境的详细教程
Jul 01 PHP
分享php邮件管理器源码
Jan 06 PHP
PHP处理数组和XML之间的互相转换
Jun 02 PHP
PHP入门教程之自定义函数用法详解(创建,调用,变量,参数,返回值等)
Sep 11 PHP
基于php中echo用逗号和用点号的区别详解
Jan 23 PHP
基于PHP实现用户登录注册功能的详细教程
Aug 04 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
thinkphp普通查询与表达式查询实例分析
2014/11/24 PHP
php字符串操作常见问题小结
2016/10/11 PHP
PHP排序算法之堆排序(Heap Sort)实例详解
2018/04/21 PHP
匹配任意字符的正则表达式写法
2010/04/29 Javascript
jQuery选择器简明总结(含用法实例,一目了然)
2014/04/25 Javascript
node.js实现BigPipe详解
2014/12/05 Javascript
JavaScript中的数值范围介绍
2014/12/29 Javascript
JavaScript代码因逗号不规范导致IE不兼容的问题
2016/02/25 Javascript
jQuery中的select操作详解
2016/11/29 Javascript
vue-router 组件复用问题详解
2018/01/22 Javascript
深入理解node.js http模块
2018/01/24 Javascript
JavaScript引用类型Array实例分析
2018/07/24 Javascript
深度了解vue.js中hooks的相关知识
2019/06/14 Javascript
layui 阻止图片上传的实例(before方法)
2019/09/26 Javascript
js实现固定区域内的不重叠随机圆
2019/10/24 Javascript
Vue项目利用axios请求接口下载excel
2020/11/17 Vue.js
Python 条件判断的缩写方法
2008/09/06 Python
python进阶教程之异常处理
2014/08/30 Python
python中json格式数据输出的简单实现方法
2016/10/31 Python
python3之模块psutil系统性能信息使用
2018/05/30 Python
pandas 快速处理 date_time 日期格式方法
2018/11/12 Python
pycharm无法导入本地模块的解决方式
2020/02/12 Python
使用darknet框架的imagenet数据分类预训练操作
2020/07/07 Python
基于python实现删除指定文件类型
2020/07/21 Python
python 爬取小说并下载的示例
2020/12/07 Python
Html5 postMessage实现跨域消息传递
2016/03/11 HTML / CSS
Java里面如何创建一个内部类的实例
2015/01/19 面试题
接受捐赠答谢词
2014/01/27 职场文书
函授毕业生自我鉴定范文
2014/03/25 职场文书
班主任经验交流会主持词
2014/04/01 职场文书
《大禹治水》教学反思
2014/04/27 职场文书
美国留学经济担保书
2014/05/20 职场文书
商业项目策划方案
2014/06/05 职场文书
2015教师年度考核评语
2015/03/25 职场文书
会议主持人开场白台词
2015/05/28 职场文书
英语读书笔记
2015/07/02 职场文书