php 5.4 全新的代码复用Trait详解


Posted in PHP onJanuary 05, 2017

从PHP的5.4.0版本开始,PHP提供了一种全新的代码复用的概念,那就是Trait。Trait其字面意思是"特性"、"特点",我们可以理解为,使用Trait关键字,可以为PHP中的类添加新的特性。

熟悉面向对象的都知道,软件开发中常用的代码复用有继承和多态两种方式。在PHP中,只能实现单继承。而Trait则避免了这点。下面通过简单的额例子来进行对比说明。

1. 继承 VS 多态 VS Trait

现在有Publish.phpAnswer.php这两个类。要在其中添加LOG功能,记录类内部的动作。有以下几种方案:

  1. 继承
  2. 多态
  3. Trait

1.1. 继承

如图:

php 5.4 全新的代码复用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 5.4 全新的代码复用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 5.4 全新的代码复用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应用中的优先级如下:

来自当前类的成员覆盖了 trait 的方法

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中如果出现了同名的属性或者方法该怎么办呢?这个时候就需要用到Insteadofas 这两个关键字了.请看如下实现代码:

<?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 相关文章推荐
mysq GBKl乱码
Nov 28 PHP
弄了个检测传输的参数是否为数字的Function
Dec 06 PHP
php 方便水印和缩略图的图形类
May 21 PHP
$_GET['goods_id']+0 的使用详解
Jun 06 PHP
php实例分享之二维数组排序
May 15 PHP
php算法实例分享
Jul 14 PHP
织梦sitemap地图实时推送给百度的教程
Aug 03 PHP
使用PHP uniqid函数生成唯一ID
Nov 18 PHP
PHP从尾到头打印链表实例讲解
Sep 27 PHP
Laravel 实现添加多语言提示信息
Oct 25 PHP
设定php简写功能的方法
Nov 28 PHP
PHP使用Http Post请求发送Json对象数据代码解析
Jul 16 PHP
golang 调用 php7详解及实例
Jan 04 #PHP
PHP 与 UTF-8 的最佳实践详细介绍
Jan 04 #PHP
详解Yii2 定制表单输入字段的标签和样式
Jan 04 #PHP
PHPExcel导出2003和2007的excel文档功能示例
Jan 04 #PHP
CI框架实现优化文件上传及多文件上传的方法
Jan 04 #PHP
PHP搭建大文件切割分块上传功能示例
Jan 04 #PHP
php实现的简单中文验证码功能示例
Jan 03 #PHP
You might like
PHP系统流量分析的程序
2006/10/09 PHP
深入解析php中的foreach问题
2013/06/30 PHP
PHP速成大法
2015/01/30 PHP
PHP统计目录中文件以及目录中目录大小的方法
2016/01/09 PHP
DHTML 中的绝对定位
2006/11/26 Javascript
Javascript 中的 &amp;&amp; 和 || 使用小结
2010/04/25 Javascript
js的延迟执行问题分析
2014/06/23 Javascript
jQuery结合ajax实现动态加载文本内容
2015/05/19 Javascript
js实现input密码框提示信息的方法(附html5实现方法)
2016/01/14 Javascript
javascript实现动态显示颜色块的报表效果
2017/04/10 Javascript
JS获取input[file]的值并显示在页面的实现方法
2018/03/09 Javascript
详解vue项目接入微信JSSDK的坑
2018/12/14 Javascript
微信小程序实现展示评分结果功能
2019/02/15 Javascript
微信小程序 简易计算器实现代码实例
2019/09/02 Javascript
JS实现随机抽选获奖者
2019/11/07 Javascript
Vue简单实现原理详解
2020/05/07 Javascript
JavaScript字符和ASCII实现互相转换
2020/06/03 Javascript
关于ES6尾调用优化的使用
2020/09/11 Javascript
解决Can't find variable: SockJS vue项目的问题
2020/09/22 Javascript
vue登录页实现使用cookie记住7天密码功能的方法
2021/02/18 Vue.js
使用Python编写类UNIX系统的命令行工具的教程
2015/04/15 Python
一个基于flask的web应用诞生 flask和mysql相连(4)
2017/04/11 Python
Python实现简单的HttpServer服务器示例
2017/09/25 Python
python中的内置函数max()和min()及mas()函数的高级用法
2018/03/29 Python
python 实现判断ip连通性的方法总结
2018/04/22 Python
python机器学习包mlxtend的安装和配置详解
2019/08/21 Python
pytorch 中pad函数toch.nn.functional.pad()的用法
2020/01/08 Python
Python爬虫教程之利用正则表达式匹配网页内容
2020/12/08 Python
Clarisonic美国官网:科莱丽声波洁面仪
2017/10/12 全球购物
精选鞋类、服装和配饰的全球领先目的地:Bodega
2021/02/27 全球购物
高一历史教学反思
2014/01/13 职场文书
工商治理实习生的自我评价
2014/01/15 职场文书
餐饮总经理岗位职责
2014/03/07 职场文书
2014年五一促销活动方案
2014/03/09 职场文书
Python 数据可视化之Bokeh详解
2021/11/02 Python
mysql下的max_allowed_packet参数设置详解
2022/02/12 MySQL