PHP Traits
在本教程中,我们将学习如何使用PHP Traits在位于不同继承层次结构的独立类之间共享功能。
PHP traits的介绍
代码复用是面向对象编程最重要的方面之一。
在PHP中,可以使用继承在共享相同继承层次结构的不同类中重用代码。
但是要实现代码复用,需要将类的公共功能移到父类的方法中。继承使得代码耦合非常紧密,因此很难维护代码。
为了克服这个问题,从5.4.0版本开始,PHP引入了一个新的代码重用单元,名为trait
。Traits
允许您在许多不同的类中自由地重用一组方法,这些类不需要在同一个类层次结构中。
Trait和class类似,但它只用于以细粒度和一致的方式对方法进行分组。不允许单独实例化一个trait
。
PHP traits 示例
声明一个新的trait 和声明一个新的类类似,如下面的例子所示:
<?php trait Logger { function log($msg) { echo '<pre>'; echo date('Y-m-d h:i:s') . ':' . '(' . __CLASS__ . ') ' . $msg . '<br/>'; echo '</pre>'; } }
要在类中使用trait,可以使用use关键字。
trait的所有方法在使用它的类中都是可用的。调用trait的方法类似于调用实例方法。
下面的例子演示了如何在BankAccount类中使用trait Logger
:
class BankAccount{ use Logger; private $accountNumber; function __construct($accountNumber){ $this->accountNumber = $accountNumber; $this->log("创建一个新的银行帐号 $accountNumber"); } }
我们也可以在User类中复用Logger trait,如下:
class User{ use Logger; function __construct() { $this->log("创建了一个新用户"); } }
BankAccount和User类都重用了Logger trait的方法,这是非常灵活的。
我们可以测试一下我们的脚本,看它是否工作。
$user = new User(); $account = new BankAccount('123546');
使用多个trait
一个类可以使用多个trait。
下面的例子演示了如何在IDE类中使用多个trait。为了便于演示,它在PHP中模拟了C编译模型。
<?php trait Preprocessor { function preprocess() { echo '预处理…完成' . '<br/>'; } } trait Compiler { function compile() { echo '编译代码…完成' . '<br/>'; } } trait Assembler { function createObjCode() { echo '创建目标代码文件…完成' . '<br/>'; } } trait Linker { function createExec() { echo '创建可执行文件…完成' . '<br/>'; } } class IDE { use Preprocessor, Compiler, Assembler, Linker; function run() { $this->preprocess(); $this->compile(); $this->createObjCode(); $this->createExec(); echo '执行文件…完成' . '<br/>'; } } $ide = new IDE(); $ide->run();
组合多个trait
通过在trait声明中使用use语句,可以由其他trait组成一个新的trait。请看下面的例子:
<?php trait Reader { public function read($source) { echo sprintf("Read from %s <br/>", $source); } } trait Writer { public function write($destination) { echo sprintf("Write to %s <br/>", $destination); } } trait Copier { use Reader, Writer; public function copy($source, $destination) { $this->read($source); $this->write($destination); } } class FileUtil { use Copier; public function copyFile($source, $destination) { $this->copy($source, $destination); } }
首先,我们创建了Reader和Writer trait。
其次,我们创建了一个新是Copier trait,它由Reader和Writer trait组成。
在Copier trait的copy()方法中,我们调用了Reader中的read()方法和Writer trait的write()方法。
最后,我们使用FileUtil类的Copier trait中的copyFile()方法来模拟复制文件操作。
解决PHP trait的方法名冲突问题
重载(覆盖)trait方法
如果一个类使用了多个有相同方法名的trait,将会引发PHP致命错误。
幸运的是,我们可以通过inteadof关键字来告诉PHP使用哪个trait的哪个方法。
让我们看看下面的例子:
<?php trait FileLogger { public function log($msg) { echo '文件日志记录 ' . date('Y-m-d h:i:s') . ':' . $msg . '<br/>'; } } trait DatabaseLogger { public function log($msg) { echo '数据库日志记录 ' . date('Y-m-d h:i:s') . ':' . $msg . '<br/>'; } } class Logger { use FileLogger, DatabaseLogger { FileLogger::log insteadof DatabaseLogger; } } $logger = new Logger(); $logger->log(' 测试消息 #1'); $logger->log(' 测试消息 #2');
FileLogger和DatabaseLogger trait都具有相同的log()方法。
在Logger类中,我们通过这种方式来解决方法名称冲突的问题:指定了将使用FileLogger trait的log()方法而不是使用DatabaseLogger的log()方法。
如果我们想同时使用FileLogger和DatabaseLogger trait中的方法,该怎么办呢?
我们可以使用alias为trait的方法起别名。
为trait方法起别名
通过对多个trait中相同的方法名使用别名,您可以重用这些trait中的所有方法。
您可以使用as关键字将trait的方法名改成另一个名称。
下面的例子说明了如何使用别名方法解决trait中的方法名冲突问题:
class Logger{ use FileLogger, DatabaseLogger{ DatabaseLogger::log as log2DB; FileLogger::log insteadof DatabaseLogger; } } $logger = new Logger(); $logger->log('测试消息 #1'); $logger->log2DB('测试消息 #2');
DatabaseLogger类的方法log()在Logger类的上下文中有了一个新名字(log2DB)。