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分页函数
Oct 09 PHP
第五节--克隆
Nov 16 PHP
PHP连接SQLServer2005的实现方法(附ntwdblib.dll下载)
Jul 02 PHP
PHP CLI模式下的多进程应用分析
Jun 03 PHP
PHP正则表达式替换站点关键字链接后空白的解决方法
Sep 16 PHP
php将远程图片保存到本地服务器的实现代码
Aug 03 PHP
PHP的文件操作与算法实现的面试题示例
Aug 10 PHP
PHP可变变量学习小结
Nov 29 PHP
php 中的closure用法详解
Jun 12 PHP
PHP让网站移动访问更加友好方法
Feb 14 PHP
Laravel 实现Controller向blade前台模板赋值的四种方式小结
Oct 22 PHP
PHP7新特性
Mar 09 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
javascript document.images实例
2008/05/27 Javascript
Javascript 检测、添加、移除样式(className)函数代码
2009/09/08 Javascript
ASP.NET jQuery 实例17 通过使用jQuery validation插件校验ListBox
2012/02/03 Javascript
javascript suggest效果 自动完成实现代码分享
2012/02/17 Javascript
jQuery 开发者应该注意的9个错误
2012/05/03 Javascript
Jquery图片滚动与幻灯片的实例代码
2013/04/08 Javascript
JavaScript作用域链使用介绍
2013/08/29 Javascript
jquery实现的用户注册表单提示操作效果代码分享
2015/08/28 Javascript
JS+JSP通过img标签调用实现静态页面访问次数统计的方法
2015/12/14 Javascript
浅析JS运动
2015/12/28 Javascript
jQuery实现最简单的切换图效果【可兼容IE6、火狐、谷歌、opera等】
2016/09/04 Javascript
vue-router 中router-view不能渲染的解决方法
2017/05/23 Javascript
重学 JS:为啥 await 不能用在 forEach 中详解
2019/04/15 Javascript
微信小程序云开发之数据库操作
2019/05/18 Javascript
thinkjs微信中控之微信鉴权登陆的实现代码
2019/08/08 Javascript
vue总线机制(bus)知识点详解
2020/05/10 Javascript
关于Js中new操作符的作用详解
2021/02/21 Javascript
Python中的字符串查找操作方法总结
2016/06/27 Python
详解Python 实现元胞自动机中的生命游戏(Game of life)
2018/01/27 Python
python 将有序数组转换为二叉树的方法
2019/03/26 Python
python 利用turtle库绘制笑脸和哭脸的例子
2019/11/23 Python
澳大利亚旅游网站:Lastminute
2017/08/07 全球购物
澳大利亚小众服装品牌:Maurie & Eve
2018/03/27 全球购物
.NET remoting中对象激活的两种方式
2015/06/08 面试题
什么是Smart Navigation?
2016/07/03 面试题
建筑工程自我鉴定
2013/10/18 职场文书
商务邀请函范文
2014/01/14 职场文书
捐款倡议书格式范文
2014/05/14 职场文书
电子工程求职信
2014/07/17 职场文书
班子查摆四风个人对照检查材料思想汇报
2014/10/04 职场文书
领导干部考核评语
2015/01/04 职场文书
健康教育主题班会
2015/08/14 职场文书
2015年文秘个人工作总结
2015/10/14 职场文书
2016教师六五普法学习心得体会
2016/01/21 职场文书
python munch库的使用解析
2021/05/25 Python
世界十大动漫制作公司排行榜,迪士尼上榜,第二是美国代表性文化符
2022/03/18 欧美动漫