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 fread()使用技巧
Jan 22 PHP
php 修改zen-cart下单和付款流程以防止漏单
Mar 08 PHP
基于php iconv函数的使用详解
Jun 09 PHP
php遍历数组的4种方法总结
Jul 05 PHP
php的XML文件解释类应用实例
Sep 22 PHP
php is_writable判断文件是否可写实例代码
Oct 13 PHP
php可变长参数处理函数详解
Feb 22 PHP
thinkPHP5框架auth权限控制类与用法示例
Jun 12 PHP
PHP实现无限极分类的两种方式示例【递归和引用方式】
Mar 25 PHP
PHP面向对象程序设计子类扩展父类(子类重新载入父类)操作详解
Jun 14 PHP
Yii框架操作cookie与session的方法实例详解
Sep 04 PHP
PHP实现新型冠状病毒疫情实时图的实例
Feb 04 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分多步骤填写发布信息的简单方法实例代码
2012/09/23 PHP
php生成excel列名超过26列大于Z时的解决方法
2014/12/29 PHP
php官方微信接口大全(微信支付、微信红包、微信摇一摇、微信小店)
2015/12/21 PHP
Apache无法自动跳转却显示目录的解决方法
2020/11/30 PHP
关于PHP虚拟主机概念及如何选择稳定的PHP虚拟主机
2018/11/20 PHP
图像替换新技术 状态域方法
2010/01/28 Javascript
通过jQuery源码学习javascript(一)
2012/12/27 Javascript
JavaScrpt中如何使用 cookie 设置查看与删除功能
2017/07/09 Javascript
jQuery实现键盘回车搜索功能
2017/07/25 jQuery
jQuery选择器之子元素过滤选择器
2017/09/28 jQuery
vue在使用ECharts时的异步更新和数据加载详解
2017/11/22 Javascript
从0到1构建vueSSR项目之路由的构建
2019/03/07 Javascript
关于AOP在JS中的实现与应用详解
2019/05/06 Javascript
vue视频播放插件vue-video-player的具体使用方法
2019/11/08 Javascript
微信小程序个人中心的列表控件实现代码
2020/04/26 Javascript
vue自定义指令和动态路由实现权限控制
2020/08/28 Javascript
[05:22]DOTA2 2015国际邀请赛中国区预选赛首日TOP10
2015/05/26 DOTA
[01:32]完美世界DOTA2联赛10月29日精彩集锦
2020/10/30 DOTA
Python读取和处理文件后缀为.sqlite的数据文件(实例讲解)
2017/06/27 Python
使用tensorflow实现线性svm
2018/09/07 Python
django 通过url实现简单的权限控制的例子
2019/08/16 Python
Python函数式编程实例详解
2020/01/17 Python
css3实现书本翻页效果的示例代码
2021/03/08 HTML / CSS
html5借用repeating-linear-gradient实现一把刻度尺(ruler)
2019/09/09 HTML / CSS
We Fashion荷兰:一家国际时装公司
2018/04/18 全球购物
什么是数据库锁?Oracle中都有哪些类型的锁?
2015/08/21 面试题
Java如何支持I18N?
2016/10/31 面试题
电大毕业生自我鉴定
2013/11/10 职场文书
个人求职信范文分享
2014/01/31 职场文书
纪念一二九运动演讲稿
2014/09/16 职场文书
旷工检讨书1000字
2015/01/01 职场文书
2015年汽车销售经理工作总结
2015/04/27 职场文书
火锅店的开业营销方案范本!
2019/07/05 职场文书
扩展多台相同的Web服务器
2021/04/01 Servers
解决Pytorch半精度浮点型网络训练的问题
2021/05/24 Python
SpringBoot深入分析讲解监听器模式下
2022/07/15 Java/Android