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 和 XML: 使用expat函数(三)
Oct 09 PHP
一个PHP操作Access类(PHP+ODBC+Access)
Jan 02 PHP
浅谈apache和nginx的rewrite的区别
Feb 22 PHP
php正则取img标记中任意属性(正则替换去掉或改变图片img标记中的任意属性)
Aug 13 PHP
php删除指定目录的方法
Apr 03 PHP
PHP自毁程序(慎用)
Jul 09 PHP
php远程下载类分享
Apr 13 PHP
PHP微信开发之微信消息自动回复下所遇到的坑
May 09 PHP
PHP实现的曲线统计图表示例
Nov 10 PHP
PHP 根据key 给二维数组分组
Dec 09 PHP
PHP ADODB生成HTML表格函数rs2html功能【附错误处理函数用法】
May 29 PHP
WordPress多语言翻译插件 - WPML使用教程
Apr 01 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(3)
2006/10/09 PHP
PHP 和 MySQL 基础教程(三)
2006/10/09 PHP
PHP form 表单传参明细研究
2009/07/17 PHP
PHP实现根据数组的值进行分组的方法
2017/04/20 PHP
php readfile下载大文件失败的解决方法
2017/05/22 PHP
2010年最佳jQuery插件整理
2010/12/06 Javascript
javascript中的startWith和endWith的几种实现方法
2013/05/07 Javascript
HTML5之lang属性与dir属性的详解
2013/06/19 Javascript
浅谈javascript的调试
2015/01/28 Javascript
js+html5实现canvas绘制圆形图案的方法
2015/06/05 Javascript
强大Vue.js组件浅析
2016/09/12 Javascript
浅谈js控制li标签排序问题 js调用php函数的方法
2016/10/16 Javascript
对于input 框限定输入值为浮点型的js代码
2017/09/25 Javascript
基于element-ui的rules中正则表达式
2018/09/04 Javascript
微信小程序实现多个按钮的颜色状态转换
2019/02/15 Javascript
JS实现滑动导航效果
2020/01/14 Javascript
JavaScript队列结构Queue实现过程解析
2020/03/07 Javascript
js+canvas实现刮刮奖功能
2020/09/13 Javascript
vue3+typescript实现图片懒加载插件
2020/10/26 Javascript
python 捕获 shell/bash 脚本的输出结果实例
2017/01/04 Python
利用python获取Ping结果示例代码
2017/07/06 Python
python出现&quot;IndentationError: unexpected indent&quot;错误解决办法
2017/10/15 Python
Python提取转移文件夹内所有.jpg文件并查看每一帧的方法
2019/06/27 Python
用Python识别人脸,人种等各种信息
2019/07/15 Python
Django admin.py 在修改/添加表单界面显示额外字段的方法
2019/08/22 Python
Python类反射机制使用实例解析
2019/12/30 Python
pytorch获取模型某一层参数名及参数值方式
2019/12/30 Python
Vs Code中8个好用的python 扩展插件
2020/10/12 Python
使用BeautifulSoup4解析XML的方法小结
2020/12/07 Python
Trip.com澳大利亚:在线旅行社
2019/12/01 全球购物
普通PHP程序员笔试题
2016/01/01 面试题
运动会表扬稿大全
2014/01/16 职场文书
2015公务员年度考核评语
2015/03/25 职场文书
2015年出纳个人工作总结
2015/04/02 职场文书
感谢信
2019/04/11 职场文书
详解TypeScript中的类型保护
2021/04/29 Javascript