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 相关文章推荐
oracle资料库函式库
Oct 09 PHP
PHP与MySQL开发中页面出现乱码的一种解决方法
Jul 29 PHP
PHP STRING 陷阱原理说明
Jul 24 PHP
php安装xdebug/php安装pear/phpunit详解步骤(图)
Dec 22 PHP
PHP提交表单失败后如何保留已经填写的信息
Jun 20 PHP
php示例详解Constructor Prototype Pattern 原型模式
Oct 15 PHP
Docker 如何布置PHP开发环境
Jun 21 PHP
PHP实现清除MySQL死连接的方法
Jul 23 PHP
laravel创建类似ThinPHP中functions.php的全局函数
Nov 26 PHP
Paypal实现循环扣款(订阅)功能
Mar 23 PHP
PHP操作MongoDB实现增删改查功能【附php7操作MongoDB方法】
Apr 24 PHP
tp5框架使用composer实现日志记录功能示例
Jan 10 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 adodb操作mysql数据库
2009/03/19 PHP
整理的9个实用的PHP库简介和下载
2010/11/09 PHP
PHP获取文件绝对路径的代码(上一级目录)
2011/05/29 PHP
php的array_multisort()使用方法介绍
2012/05/16 PHP
解析用PHP实现var_export的详细介绍
2013/06/20 PHP
PHP实现的比较完善的购物车类
2014/12/02 PHP
php实现Session存储到Redis
2015/11/11 PHP
javascript多种数据类型表格排序代码分析
2010/09/11 Javascript
某人初学javascript的时候写的学习笔记
2010/12/30 Javascript
Chrome Form多次提交表单问题的解决方法
2011/05/09 Javascript
用Jquery重写windows.alert方法实现思路
2013/04/03 Javascript
jQuery在iframe中无法弹出对话框的解决方法
2014/01/12 Javascript
table insertRow、deleteRow定义和用法总结
2014/05/14 Javascript
JS运动框架之分享侧边栏动画实例
2015/03/03 Javascript
angularjs学习笔记之完整的项目结构
2015/09/26 Javascript
JS中判断字符串中出现次数最多的字符及出现的次数的简单实例
2016/06/03 Javascript
jQuery+ajax的资源回收处理机制分析
2017/01/07 Javascript
基于JavaScript实现抽奖系统
2018/01/16 Javascript
node实现生成带参数的小程序二维码并保存到本地功能示例
2018/12/05 Javascript
VSCode搭建React Native环境
2020/05/07 Javascript
UEditor 自定义图片视频尺寸校验功能的实现代码
2020/10/20 Javascript
关于python2 csv写入空白行的问题
2018/06/22 Python
用Python PIL实现几个简单的图片特效
2019/01/18 Python
Django REST framework 视图和路由详解
2019/07/19 Python
Python安装及Pycharm安装使用教程图解
2019/09/20 Python
基于python实现微信好友数据分析(简单)
2020/02/16 Python
python opencv实现简易画图板
2020/08/27 Python
python 深度学习中的4种激活函数
2020/09/18 Python
python 利用百度API识别图片文字(多线程版)
2020/12/14 Python
孕妇装中的著名品牌:Isabella Oliver(伊莎贝拉·奥利弗)
2016/10/31 全球购物
平面设计自荐信
2013/10/07 职场文书
初中英语教学反思
2014/01/25 职场文书
活动总结报告格式
2014/05/09 职场文书
品质标语大全
2014/06/21 职场文书
2016年第32个教师节致辞
2015/11/26 职场文书
Python使用random模块实现掷骰子游戏的示例代码
2021/04/29 Python