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中操作MySQL数据库的一些要注意的问题
Oct 09 PHP
PHP学习笔记之三 数据库基本操作
Jan 17 PHP
php和数据库结合的一个简单的web实例 代码分析 (php初学者)
Jul 28 PHP
解析php中call_user_func_array的作用
Jun 07 PHP
浅谈php和.net的区别
Sep 28 PHP
PHP IDE PHPStorm配置支持友好Laravel代码提示方法
May 12 PHP
常用的php图片处理类(水印、等比缩放、固定高宽)分享
Jun 19 PHP
纯PHP代码实现支付宝批量付款
Dec 24 PHP
Yii中CGridView禁止列排序的设置方法
Jul 12 PHP
yii2.0实现创建简单widgets示例
Jul 18 PHP
php 可变函数使用小结
Jun 12 PHP
php使用fputcsv实现大数据的导出操作详解
Feb 27 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伪静态写法附代码
2008/06/20 PHP
PHP中获取内网用户MAC地址(WINDOWS/linux)的实现代码
2011/08/11 PHP
微博短链接算法php版本实现代码
2012/09/15 PHP
php+ajax无刷新上传图片的实现方法
2016/12/06 PHP
JavaScript 特殊字符
2007/04/05 Javascript
利用js对象弹出一个层
2008/03/26 Javascript
javascript json2 使用方法
2010/03/16 Javascript
跨浏览器开发经验总结(三)   警惕“IE依赖综合症”
2010/05/13 Javascript
javascript将数组插入到另一个数组中的代码
2013/01/10 Javascript
jQuery列表拖动排列具体实现
2013/11/04 Javascript
JQuery设置获取下拉菜单某个选项的值(比较全)
2014/08/05 Javascript
JS实现两表格里数据来回转移的方法
2015/05/28 Javascript
基于jQuery实现点击最后一行实现行自增效果的表格
2016/01/12 Javascript
几行js代码实现自适应
2017/02/24 Javascript
移动端web滚动分页的实现方法
2017/05/05 Javascript
angular.fromJson与toJson方法用法示例
2017/05/17 Javascript
jQuery 开发之EasyUI 添加数据的实例
2017/09/26 jQuery
jQuery模拟html下拉多选框的原生实现方法示例
2019/05/30 jQuery
JavaScript之Blob对象类型的具体使用方法
2019/11/29 Javascript
js操作两个json数组合并、去重,以及删除某一项元素
2020/09/22 Javascript
解决VueCil代理本地proxytable无效报错404的问题
2020/11/07 Javascript
[02:47]DOTA2英雄基础教程 野性怒吼兽王
2013/12/05 DOTA
Python实现快速多线程ping的方法
2015/07/15 Python
对命令行模式与python交互模式介绍
2018/05/12 Python
树莓派与PC端在局域网内运用python实现即时通讯
2019/06/22 Python
python 判断linux进程,并杀死进程的实现方法
2019/07/01 Python
对python3中的RE(正则表达式)-详细总结
2019/07/23 Python
实例教程 一款纯css3实现的数字统计游戏
2014/11/10 HTML / CSS
美国最大的袜子制造商和零售商:Renfro Socks
2017/09/03 全球购物
真正的英国宝藏:Mappin & Webb
2019/05/05 全球购物
美国电子产品购物网站:BuyDig.com
2020/06/17 全球购物
公司同意接收函
2014/01/13 职场文书
幼儿园小班教学反思
2014/02/02 职场文书
2019秋季运动会口号
2019/06/25 职场文书
Vue接口封装的完整步骤记录
2021/05/14 Vue.js
解决pytorch读取自制数据集出现过的问题
2021/05/31 Python