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 相关文章推荐
《APMServ 5.1.2》使用图解
Oct 23 PHP
用PHP实现的随机广告显示代码
Jun 14 PHP
php 引用(&amp;)详解
Nov 20 PHP
PHPWind 发帖回帖Api PHP版打包下载
Feb 08 PHP
PHP 学习路线与时间表
Feb 21 PHP
PHP扩展模块Pecl、Pear以及Perl的区别
Apr 09 PHP
dedecms函数分享之获取某一栏目所有子栏目
May 19 PHP
php实现的常见排序算法汇总
Sep 08 PHP
thinkphp模板赋值与替换实例简述
Nov 24 PHP
PHP使用CURL实现多线程抓取网页
Apr 30 PHP
Yii统计不同类型邮箱数量的方法
Oct 18 PHP
php 7新特性之类型申明详解
Jun 06 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 getimagesize 上传图片的长度和宽度检测代码
2010/05/15 PHP
调试一段PHP程序时遇到的三个问题
2012/01/17 PHP
php使用memcoder将视频转成mp4格式的方法
2015/03/12 PHP
Zend Studio使用技巧两则
2016/04/01 PHP
Laravel框架在本地虚拟机快速安装的方法详解
2018/06/11 PHP
Javascript hasOwnProperty 方法 &amp; in 关键字
2008/11/26 Javascript
nodejs 后缀名判断限制代码
2011/03/31 NodeJs
JS+CSS实现美化的下拉列表框效果
2015/08/11 Javascript
简单分析javascript中的函数
2016/09/10 Javascript
微信小程序 火车票查询实例讲解
2016/10/17 Javascript
javascript实现鼠标点击页面 移动DIV
2016/12/02 Javascript
总结javascript三元运算符知识点
2018/09/28 Javascript
js实现的在本地预览图片功能示例
2019/11/09 Javascript
jquery实现直播视频弹幕效果
2020/02/25 jQuery
vue-cli3中配置alias和打包加hash值操作
2020/09/04 Javascript
[27:28]Ti4 冒泡赛第二天 iG vs NEWBEE 1
2014/07/15 DOTA
Python基于有道实现英汉字典功能
2015/07/25 Python
Python按行读取文件的实现方法【小文件和大文件读取】
2016/09/19 Python
Python用for循环实现九九乘法表
2018/05/31 Python
pycharm安装和首次使用教程
2018/08/27 Python
python利用百度AI实现文字识别功能
2018/11/27 Python
使用Python实现微信提醒备忘录功能
2018/12/04 Python
Python编程在flask中模拟进行Restful的CRUD操作
2018/12/28 Python
Python根据欧拉角求旋转矩阵的实例
2019/01/28 Python
python 为什么说eval要慎用
2019/03/26 Python
python实现操作文件(文件夹)
2019/10/31 Python
python实现凯撒密码、凯撒加解密算法
2020/06/11 Python
通过代码实例了解Python异常本质
2020/09/16 Python
Mankind美国/加拿大:英国领先的男士美容护发用品公司
2018/12/05 全球购物
我想声明一个指针并为它分配一些空间, 但却不行。这些代码有什么 问题?char *p; *p = malloc(10);
2016/10/06 面试题
公务员职务工作的自我评价
2013/11/01 职场文书
运动会广播稿100字
2014/01/11 职场文书
投资意向书范本
2014/04/01 职场文书
2014领导班子“四风问题”对照检查材料思想汇报(执法局)
2014/09/21 职场文书
如何让2019年上半年的工作总结更出色!
2019/07/01 职场文书
Python+Appium实现自动抢微信红包
2021/05/21 Python