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 strtr() 函数使用说明
Nov 21 PHP
PHP的SQL注入实现(测试代码安全不错)
Feb 27 PHP
PHP面向接口编程 耦合设计模式 简单范例
Mar 23 PHP
php数组相加 array(“a”)+array(“b”)结果还是array(“a”)
Sep 19 PHP
Javascript与PHP验证用户输入URL地址是否正确
Oct 09 PHP
php判断输入是否是纯数字,英文,汉字的方法
Mar 05 PHP
浅谈本地WAMP环境的搭建
May 13 PHP
php中memcache 基本操作实例
May 17 PHP
PHP中PDO连接数据库中各种DNS设置方法小结
May 13 PHP
php中实现进程锁与多进程的方法
Sep 18 PHP
PHP数据对象PDO操作技巧小结
Sep 27 PHP
PHP 模拟登陆功能实例详解
Sep 10 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将回调函数作用到给定数组单元的方法
2014/08/19 PHP
CI框架装载器Loader.php源码分析
2014/11/04 PHP
php xhprof使用实例详解
2019/04/15 PHP
php进程(线程)通信基础之System V共享内存简单实例分析
2019/11/09 PHP
自适应图片大小的弹出窗口
2006/07/27 Javascript
JavaScript入门教程(6) Window窗口对象
2009/01/31 Javascript
document.getElementById方法在Firefox与IE中的区别
2010/05/18 Javascript
jquery插件之easing使用
2010/08/19 Javascript
理清apply(),call()的区别和关系
2011/08/14 Javascript
jquery 判断滚动条到达了底部和顶端的方法
2014/04/02 Javascript
JavaScript数据类型判定的总结笔记
2015/07/31 Javascript
使用jQuery的ajax方法向服务器发出get和post请求的方法
2017/01/13 Javascript
详解Vue方法与事件
2017/03/09 Javascript
JS 组件系列之BootstrapTable的treegrid功能
2017/06/16 Javascript
详解Vue.js搭建路由报错 router.map is not a function
2017/06/27 Javascript
JS表单传值和URL编码转换
2018/03/03 Javascript
Vue点击切换颜色的方法
2018/09/13 Javascript
微信小程序的mpvue框架快速上手指南
2019/05/15 Javascript
JavaScript实现随机五位数验证码
2019/09/27 Javascript
webpack是如何实现模块化加载的方法
2019/11/06 Javascript
Python求导数的方法
2015/05/09 Python
python算法表示概念扫盲教程
2017/04/13 Python
python 反向输出字符串的方法
2018/07/16 Python
把JSON数据格式转换为Python的类对象方法详解(两种方法)
2019/06/04 Python
Python中正反斜杠(‘/’和‘\’)的意义与用法
2019/08/12 Python
浅析Python数字类型和字符串类型的内置方法
2019/12/22 Python
世界上最值得信赖的多日游在线市场:TourRadar
2018/07/20 全球购物
美国购物网站:Clickhere2shop
2021/01/28 全球购物
市级青年文明号申报材料
2014/05/26 职场文书
电子商务专业自荐信
2014/06/02 职场文书
三问三解心得体会
2014/09/05 职场文书
财务科长个人对照检查材料
2014/09/18 职场文书
2014法制宣传日活动总结范文
2014/11/01 职场文书
教师党员学习十八届四中全会思想汇报
2014/11/03 职场文书
小学一年级学生评语大全
2014/12/25 职场文书
Python爬取科目四考试题库的方法实现
2021/03/30 Python