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 相关文章推荐
我的论坛源代码(六)
Oct 09 PHP
PHP转换文件夹下所有文件编码的实现代码
Jun 06 PHP
PHP判断指定时间段的2个方法
Mar 14 PHP
PHP采用curl模仿用户登陆新浪微博发微博的方法
Nov 07 PHP
php简单操作mysql数据库的类
Apr 16 PHP
百度工程师讲PHP函数的实现原理及性能分析(二)
May 13 PHP
Smarty foreach控制循环次数的一些方法
Jul 01 PHP
php时间计算相关问题小结
May 09 PHP
thinkphp3.x中session方法的用法分析
May 20 PHP
PHP生成word文档的三种实现方式
Nov 14 PHP
关于PHP通用返回值设置方法
Mar 31 PHP
php如何利用pecl安装mongodb扩展详解
Jan 09 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开发者事半功倍的十大技巧小结
2010/04/20 PHP
php异步多线程swoole用法实例
2014/11/14 PHP
php英文单词统计器
2016/06/23 PHP
启用OPCache提高PHP程序性能的方法
2019/03/21 PHP
IE DOM实现存在的部分问题及解决方法
2009/07/25 Javascript
最短的javascript:地址栏载入脚本代码
2011/10/13 Javascript
深入理解JavaScript系列(1) 编写高质量JavaScript代码的基本要点
2012/01/15 Javascript
jquery星级插件、支持页面中多次使用
2012/03/25 Javascript
首页图片漂浮效果示例代码
2014/06/05 Javascript
jQuery实现表单提交时判断的方法
2014/12/13 Javascript
jQuery圆形统计图开发实例
2015/01/04 Javascript
JavaScript实现点击按钮复制指定区域文本(推荐)
2016/11/25 Javascript
详解基于angular-cli配置代理解决跨域请求问题
2017/07/05 Javascript
React 无状态组件(Stateless Component) 与高阶组件
2018/08/14 Javascript
JavaScript学习笔记之DOM操作实例分析
2019/01/08 Javascript
js嵌套的数组扁平化:将多维数组变成一维数组以及push()与concat()区别的讲解
2019/01/19 Javascript
微信小程序实现转盘抽奖
2020/09/21 Javascript
[01:28]一分钟告诉你DOTA2 TI9不朽宝藏Ⅱ中有什么!
2019/07/09 DOTA
python在文本开头插入一行的实例
2018/05/02 Python
python 自定义异常和异常捕捉的方法
2018/10/18 Python
Python爬取成语接龙类网站
2018/10/19 Python
python对于requests的封装方法详解
2019/01/03 Python
使用HTML5的File实现base64和图片的互转
2013/08/01 HTML / CSS
eDreams葡萄牙:全球最大的在线旅行社之一
2019/04/15 全球购物
木工主管岗位职责
2013/12/08 职场文书
原材料检验岗位职责
2014/03/15 职场文书
机械工程师岗位职责
2014/06/16 职场文书
电气工程及其自动化专业求职信
2014/06/23 职场文书
少年派的奇幻漂流观后感
2015/06/08 职场文书
2016年母亲节寄语
2015/12/04 职场文书
SONY AN-LP1 短波有源天线放大器
2021/04/22 无线电
深入理解以DEBUG方式线程的底层运行原理
2021/06/21 Java/Android
微软Win11什么功能最惊艳? Windows11新功能特性汇总
2021/11/21 数码科技
Python Pygame实战之塔防游戏的实现
2022/03/17 Python
python中redis包操作数据库的教程
2022/04/19 Python
mysql sock 文件解析及作用讲解
2022/07/15 MySQL