Trait
trait 是一个和类有点像的结构,其本身不能被实例化,但是可以被类引用。任何定义在 trait 中的方法,都能被包含trait 的类访问。可以简单的把 trait 的作用看做是 include 一个类。
Trait 解决什么问题
面向对象设置的目标就是代码重用(剔除重复的代码),trait 提供了一种解决方案。
<?php
// 定义一个 Trait
trait PriceUtilities
{
private $taxrate = 17;
public function calculateTax(float $price):float
{
return (($this->taxrate / 100) * $price);
}
}
class ShopProduct
{
use PriceUtilities; // 调用 Trait
}
abstract class Service{}
class UtilityService extends Service
{
use PriceUtilities;
}
$p = new ShopProduct();
echo $p->calculateTax(100) . "\n";
$u = new UtilityService();
echo $u->calculateTax(100) . "\n";
上面代码中 calculateTax 方法一处定义,多处使用。
调用多个 Trait
当需要调用多个 Trait 时,可以这么写
<?php
// ...
// 定义另外一个 Trait
trait IdentityTrait
{
public function generateId(): string
{
return uniqid();
}
}
// ...
class UtilityService extends Service {
use PriceUtilities, IdentityTrait;
}
// ...
$u = new UtilityService();
echo $u->calculateTax(100) . "\n";
echo $u->generateId() . "\n";
Trait 和 interface 结合
<?php
interface IdentityObject{
public function generateId(): string;
}
trait IdentityTrait{
public function generateId(): string
{
return uniqid();
}
}
trait PriceUtilities
{
private $taxrate = 17;
public function calculateTax(float $price): float
{
return (($this->taxrate / 100) * $price);
}
// other utilities
}
class ShopProduct implements IdentityObject
{
use PriceUtilities, IdentityTrait; // IdentityTrait Trait 的引入作为接口 IdentityObject 的实现
}
$p = new ShopProduct();
echo $p->calculateTax(100) . "\n";
echo $p->generateId() . "\n";
补充: interface 和抽象方法有和区别和联系?
关键字 insteadof 解决引入多个 Trait 的命名冲突问题
<?php
# 执行下面的代码,会产生一个致命错误 (因为引入的 trait 中的同名方法冲突了)
abstract class Service{};
trait TaxTools
{
function calculateTax(float $price): float
{
return 222;
}
}
trait PriceUtilities
{
private $taxrate = 17;
public function calculateTax(float $price): float
{
return (($this->taxrate / 100) * $price);
}
}
class UtilityService extends Service
{
use PriceUtilities, TaxTools;
}
$u = new UtilityService();
echo $u->calculateTax(100) . "\n";
通过改写 UtilityService 类如下来解决冲突问题:
<?php
// ...
class UtilityService extends Service
{
use PriceUtilities, TaxTools
{
TaxTools::calculateTax insteadof PriceUtilities; // 使用 TaxTools::calculateTax 方法覆盖同名方法
}
}
// ...
当然,如果不覆盖还有别名法可选:
<?php
class UtilityService extends Service
{
use PriceUtilities, TaxTools {
TaxTools::calculateTax insteadof PriceUtilities;
PriceUtilities::calculateTax as basicTax;
}
}
在 Trait 中使用静态方法
<?php
// 好吧,和类中并无不同
abstract class Service{};
trait PriceUtilities
{
private static $taxrate = 17;
public static function calculateTax(float $price): float
{
return ((self::$taxrate / 100) * $price);
}
}
class UtilityService extends Service
{
use PriceUtilities;
}
$u = new UtilityService();
echo $u::calculateTax(100) . "\n";
访问宿主类属性
<?php
// 尽管我们通常不会这么做,因为这是一个操作的设计
abstract class Service{};
trait PriceUtilities
{
function calculateTax(float $price): float
{
return (($this->taxrate / 100) * $price);
}
}
class UtilityService extends Service
{
public $taxrate = 17;
use PriceUtilities;
}
$u = new UtilityService();
echo $u->calculateTax(100) . "\n";
在 Trait 中定义抽象方法
下面使用抽象方法改写前面的例子:
<?php
// 和类中定义抽象方法相同
abstract class Service{};
trait PriceUtilities
{
function calculateTax(float $price): float
{
return (($this->getTaxRate() / 100) * $price);
}
abstract function getTaxRate():float;
}
class UtilityService extends Service
{
use PriceUtilities;
public function getTaxRate():float
{
return 17;
}
}
$u = new UtilityService();
echo $u->calculateTax(100) . "\n";
改变 Trait 的访问级别
<?php
abstract class Service{};
trait PriceUtilities
{
public function calculateTax(float $price): float
{
return (($this->getTaxRate() / 100) * $price);
}
public abstract function getTaxRate(): float;
}
class UtilityService extends Service
{
use PriceUtilities {
PriceUtilities::calculateTax as private; // 定义访问级别
}
private $price;
public function __construct(float $price) {
$this->price = $price;
}
public function getTaxRate():float
{
return 17;
}
public function getFinalPrice(): float
{
return ($this->price + $this->calculateTax($this->price));
}
}
$u = new UtilityService(100);
print $u->getFinalPrice() . "\n";
// 注意: 下面这个访问会报错,类似访问了私有的方法错误
print $u->calculateTax()."\n";
延时静态绑定
下面是一个列子:
<?php
abstract class DomainObject{}
class User extends DomainObject
{
public static function create(): User
{
return new User();
}
}
class Document extends DomainObject
{
public static function create(): Document
{
return new Document();
}
}
这段代码可以正常工作,只是有一些代码重复,我不想为每个 DomainObject 子类复制类似的代码,于是我将 create () 方超类中实现,如下:
<?php
// 代码执行会出现一个错误提示
abstract class DomainObject{
public function create(): DomainObject
{
return new self(); // 如果你使用的编辑器带有提示功能,会发现这里已经标记错误提示了
// return new static(); // 使用这行代替上面那行就可以正常工作了,这是 php5.3 一个不怎么优雅的变通方案
}
}
class User extends DomainObject
{
}
class Document extends DomainObject
{
}
Document::create();
上面的代码看起来解决了代码重复的问题,但是实际上代码没有按我们预想的那样工作 - 我们假设 self 关键字会和 $this 有类似的表现,实际上 self 和调用方法没有关联,和定义方法关联,这是报错的原因 ( 代码注释中,我们有一个变通方案 )。