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 相关文章推荐
PHP生成网页快照 不用COM不用扩展.
Feb 11 PHP
Ext.data.PagingMemoryProxy分页一次性读取数据的实现代码
Apr 07 PHP
php 判断访客是否为搜索引擎蜘蛛的函数代码
Jul 29 PHP
php将mysql数据库整库导出生成sql文件的具体实现
Jan 08 PHP
PHP文件大小格式化函数合集
Mar 10 PHP
PHP批量生成静态HTML的简单原理和方法
Apr 20 PHP
PHP图片处理之使用imagecopyresampled函数实现图片缩放例子
Nov 19 PHP
Joomla实现组件中弹出一个模式(modal)窗口的方法
May 04 PHP
php单例模式的简单实现方法
Jun 10 PHP
PHP中引用类型和值类型功能与用法示例
Feb 26 PHP
ThinkPHP5.1框架页面跳转及修改跳转页面模版示例
May 06 PHP
YII2 全局异常处理深入讲解
Mar 24 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中通过数组进行高效随机抽取指定条记录的算法
2013/09/09 PHP
Yii2框架自定义类统一处理url操作示例
2019/05/25 PHP
BOOM vs RR BO3 第一场2.13
2021/03/10 DOTA
js null,undefined,字符串小结
2010/08/21 Javascript
Javascript 面试题随笔
2011/03/31 Javascript
js 图片随机不定向浮动的实现代码
2013/07/02 Javascript
jquery获取div距离窗口和父级dv的距离示例
2013/10/10 Javascript
Node.js中HTTP模块与事件模块详解
2014/11/14 Javascript
原生javascript实现简单的datagrid数据表格
2015/01/02 Javascript
js实现根据身份证号自动生成出生日期
2015/12/15 Javascript
JS刷新父窗口的几种方式小结(推荐)
2016/11/09 Javascript
超全面的javascript中变量命名规则
2017/02/09 Javascript
微信小程序页面开发注意事项整理
2017/05/18 Javascript
webpack 2.x配置reactjs基本开发环境详解
2017/08/08 Javascript
NodeJS简单实现WebSocket功能示例
2018/02/10 NodeJs
Flutter部件内部状态管理小结之实现Vue的v-model功能
2019/06/11 Javascript
基于vue+axios+lrz.js微信端图片压缩上传方法
2019/06/25 Javascript
VUE 实现element upload上传图片到阿里云
2020/08/12 Javascript
python入门之语句(if语句、while语句、for语句)
2015/01/19 Python
初步解析Python中的yield函数的用法
2015/04/03 Python
Python实现发送与接收邮件的方法详解
2018/03/28 Python
python 读写文件,按行修改文件的方法
2018/07/12 Python
Python 列表中的修改、添加和删除元素的实现
2020/06/11 Python
重新定义牛仔布,100美元以下:Warp + Weft
2018/07/25 全球购物
美国最大的船只买卖在线市场:Boat Trader
2018/08/04 全球购物
音乐教学案例
2014/01/30 职场文书
品质管理部岗位职责范文
2014/03/01 职场文书
怎样拟定创业计划书
2014/05/01 职场文书
项目经理任命书
2014/06/04 职场文书
酒店管理失职检讨书
2014/09/16 职场文书
大学生档案自我鉴定(2篇)
2014/10/14 职场文书
2016年大学生暑假爱心支教活动策划书
2015/11/26 职场文书
素质教育培训心得体会
2016/01/19 职场文书
Mysql数据库按时间点恢复实战记录
2021/06/30 MySQL
sql通过日期判断年龄函数的示例代码
2021/07/16 SQL Server