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作的文本留言本的例子(三)
Oct 09 PHP
关于文本留言本的分页代码
Oct 09 PHP
windows下zendframework项目环境搭建(通过命令行配置)
Dec 06 PHP
ThinkPHP独立分组使用的注意事项
Nov 25 PHP
摘自织梦CMS的HTTP文件下载类
Aug 08 PHP
php实现的SSO单点登录系统接入功能示例分析
Oct 12 PHP
Docker配置PHP开发环境教程
Dec 21 PHP
详解php用curl调用接口方法,get和post两种方式
Jan 13 PHP
微信公众号OAuth2.0网页授权问题浅析
Jan 21 PHP
php通过pecl方式安装扩展的实例讲解
Feb 02 PHP
php中curl和soap方式请求服务超时问题的解决
Jun 11 PHP
php下的原生ajax请求用法实例分析
Feb 28 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
BBS(php &amp; mysql)完整版(一)
2006/10/09 PHP
用Php编写注册后Email激活验证的实例代码
2013/03/11 PHP
ThinkPHP的L方法使用简介
2014/06/18 PHP
关于Yii中模型场景的一些简单介绍
2019/09/22 PHP
Javascript解决常见浏览器兼容问题的12种方法
2010/01/04 Javascript
JQuery中getJSON的使用方法
2010/12/13 Javascript
设置iframe的document.designMode后仅Firefox中其body.innerHTML为br
2012/02/27 Javascript
如何判断微信内置浏览器(通过User Agent实现)
2014/09/01 Javascript
jQuery的one()方法用法实例
2015/01/19 Javascript
JavaScript实现跑马灯抽奖活动实例代码解析与优化(二)
2016/02/16 Javascript
JavaScript数组合并的多种方法
2016/05/22 Javascript
初识简单却不失优雅的Vue.js
2016/09/12 Javascript
JS给swf传参数的实现方法
2016/09/13 Javascript
JavaScript中String对象的方法介绍
2017/01/04 Javascript
Javascript自定义事件详解
2017/01/13 Javascript
Easyui笔记2:实现datagrid多行删除的示例代码
2017/01/14 Javascript
简单谈谈Javascript函数中的arguments
2017/02/09 Javascript
php 解压zip压缩包内容到指定目录的实例
2018/01/23 Javascript
vue自动化表单实例分析
2018/05/06 Javascript
JavaScript中创建原子的方法总结
2018/08/26 Javascript
Vuex的基本概念、项目搭建以及入坑点
2018/11/04 Javascript
Vue3.0数据响应式原理详解
2019/10/09 Javascript
ES6学习笔记之字符串、数组、对象、函数新增知识点实例分析
2020/01/22 Javascript
解决vue+webpack项目接口跨域出现的问题
2020/08/10 Javascript
nuxt.js 在middleware(中间件)中实现路由鉴权操作
2020/11/06 Javascript
python3.0 字典key排序
2008/12/24 Python
在python中的socket模块使用代理实例
2014/05/29 Python
Python中%r和%s的详解及区别
2017/03/16 Python
python assert的用处示例详解
2019/04/01 Python
深入剖析webstorage[html5的本地数据处理]
2016/07/11 HTML / CSS
幼儿园五一劳动节活动总结
2015/02/09 职场文书
人力资源部岗位职责
2015/02/11 职场文书
失恋33天观后感
2015/06/11 职场文书
党风廉政建设心得体会
2019/05/21 职场文书
2019已经过半,你知道年中工作总结该怎么写吗?
2019/07/03 职场文书
MySQL分库分表与分区的入门指南
2021/04/22 MySQL