Skip to content

9. 特质

特质(Trait)是类似于类的结构。不过,它们无法被实例化。它们旨在被包含在类中。将一个特质包含到类中,其效果等同于将特质的代码复制到类中。我们通常将可能在多个类中重复使用的代码放入特质中

9.1. 脚本树结构

Image

9.2. 在类中包含一个特质

脚本 [traits-01.php] 展示了在类中使用特性的基本方法:


<?php

class Class04 {
  // 属性
  private $name;

  // 构造函数
  public function __construct(string $name) {
    $this->name = $name;
  }

  // 获取器和设置器
  public function getName(): string {
    return $this->name;
  }

  public function setName(string $name): void {
    $this->name = $name;
  }

}

trait Trait04 {
  // 属性
  private $name;

  // 获取器和设置器
  public function getName(): string {
    return $this->name;
  }

  public function setName(string $name): void {
    $this->name = $name;
  }

}

class Class05 {
  // 特性的包含
  use Trait04;

  // 构造函数
  public function __construct(string $name) {
    $this->name = $name;
  }

}

// 测试 --------------
$class04 = new Class04("Tim");
$class05 = new Class05("Burton");
print $class04->getName() . "\n";
print $class05->getName() . "\n";
// 两个类的显示
print_r($class04);
print_r($class05);

代码注释

  • 第3-21行:定义类[Class04],包含一个属性、其get/set方法以及一个构造函数;
  • 第23-36行:将[Class04]的代码(不含构造函数)原样复制到[Trait04]特质中。由于特质无法实例化,因此不包含构造函数;
  • 第 23 行:正是关键字 [trait] 使 [Trait04] 成为一个特质而非类;
  • 第 38-45 行: 定义了一个类 [Class05],该类继承了特性 [Trait04] 的代码(第 40 行),并为其添加了一个构造函数(第 43-45 行),该构造函数与类 [Class04] 的构造函数相同,以使该类可实例化;
  • 第 40 行:关键字 [use] 用于将一个 Trait 包含到类中;
  • 第50-56行:测试表明,[Class04]和[Class05]的工作方式相同;

结果

Tim
Burton
Class04 Object
(
    [name:Class04:private] => Tim
)
Class05 Object
(
    [name:Class05:private] => Burton
)

第3-10行的结果表明,类[Class04]和[Class05]的内容相同;

结论

在类中使用 [use Trait] 语句,等同于将 [Trait] 的代码包含在该类中。

9.3. 在不同类中使用相同的特质

特性的首要优势似乎在于可以在不同类之间复用相同的代码(属性+方法)。不过,我们将看到,使用简单的类也能达到同样的目的。

以下脚本 [trait-02.php] 演示了在类之间共享 trait 的情况:


<?php

trait Trait01 {
  // 属性
  private $id = 0;

  // 方法
  public function doSomething() {
    print "Trait01::doSomething… ($this->id)\n";
  }

}

class Class02 {
  // 包含 Trait01
  use Trait01 {
    // 方法 [Trait01::doSomething] 可用
    // 在类中以名称 [doSomethingInTrait] 存在
    Trait01::doSomething as doSomethingInTrait;
  }

  // 该类专有的方法
  public function doSomething(): void {
    // id 属性
    $this->id += 10;
    // 调用Trait01方法
    $this->doSomethingInTrait();
    // 本地显示
    print "Class02->doSomething\n";
  }

}

class Class03 {
  // 包含 Trait01
  use Trait01;

  // 类本地方法
  public function doSomethingElse(): void {
    // ID属性
    $this->id += 10;
    // Trait01 方法的使用
    $this->doSomething();
    // 本地显示
    print "Class03->doSomethingElse\n";
  }

}

// test01 ----------------
function test01(): void {
  $class02 = new Class02();
  $class03 = new Class03();
  $class02->doSomething();
  $class03->doSomethingElse();
}

// test01
print "test01-----------------\n";
test01();

注释

  • 第 3-10 行:一个特质,定义了一个属性(第 5 行)和一个方法(第 8-10 行)。
  • [Trait01] 特质被注入到两个类中:[Class02](第 14-32 行)和 [Class03](第 34-46 行)。
  • 第16-20行:将[Trait01]注入到[Class02]中
  • 第 19 行旨在解决冲突:[Trait01] [Class02] 都包含一个名为 [doSomething] 的方法。需要考虑两种情况:
    • 方法 [Class02::doSomething] 由类外部调用。在此情况下,方法 [Class02::doSomething] 的优先级高于方法 [Trait01::doSomething],因此将调用前者;
    • 方法 [Class02::doSomething] 由类内部调用。此时会发生冲突:解释器 PHP 无法确定应调用哪个方法;

第 19 行将方法 [Trait01::doSomething] 重命名为 [doSomethingInTrait]。因此,在 [Class02] 内部将使用以下写法:

  • 使用 [doSomethingInTrait] 调用方法 [Trait01::doSomething]
  • [doSomething] 用于调用方法 [Class02::doSomething]
  • 第 25、27 行:类 [Class02] [Trait01] 的属性和方法作为其自身的属性和方法使用;
  • 第34-48行:类[Class03]与类[Class02]完全相同。 此处包含 [Trait01] 更为简单,因为 [Trait01] [Class03] 的方法之间不存在冲突;

结果

1
2
3
4
5
test01-----------------
Trait01::doSomething… (10)
Class02->doSomething
Trait01::doSomething… (10)
Class03->doSomethingElse

需特别注意,[Trait01] 这一特征并未在 [Class02][Class03] 类之间共享。因此,[Trait01::i] 属性因 [Trait01] 被包含在 [Class02][Class03] 类中,而变成了两个不同的属性:[Class02::i][Class03::i]。结果的第2行和第4行显示了这一点。 如果 [Trait01::i] 属性被 [Class02][Class03] 这两个类共享,那么第 4 行显示的数值将是 20 而不是 10。

脚本 [trait-03.php] 表明,使用类代替特征也能得到相同的结果:


<?php

// 替换该特性的类
class Class01 {
  // 属性
  private $id = 0;

  // 设置器
  public function setId(int $id) {
    $this->id = $id;
  }

  // getter
  public function getId(): int {
    return $this->id;
  }

  // 方法
  public function doSomething(): void {
    print "Class01::doSomething… ($this->id)\n";
  }

}

class Class02 {
  // 包含 Class01
  private $class01;

  // 设置器
  public function setClass01(Class01 $class01) {
    $this->class01 = $class01;
  }

  // 类专属方法
  public function doSomething(): void {
    // 修改 Class01 的属性
    $id = $this->class01->getId();
    $id += 10;
    $this->class01->setId($id);
    // 调用 Class01 的方法
    $this->class01->doSomething();
    // 本地显示
    print "Class02->doSomething\n";
  }

}

class Class03 {
  // 包含 Class01
  private $class01;

  // 设置器
  public function setClass01(Class01 $class01) {
    $this->class01 = $class01;
  }

  // 类本地方法
  public function doSomethingElse(): void {
    // 修改 Class01 的属性
    $id = $this->class01->getId();
    $id += 10;
    $this->class01->setId($id);
    // 调用 Class01 的方法
    $this->class01->doSomething();
    // 本地显示
    print "Class03->doSomethingElse\n";
  }

}

// test01 ----------------
function test01(): void {
  // 两个对象
  $class02 = new Class02();
  $class03 = new Class03();
  // 将访问 [Class01] 的两个不同实例
  $class02->setClass01(new Class01());
  $class03->setClass01(new Class01());
  // 验证
  $class02->doSomething();
  $class03->doSomethingElse();
}

// test02 ----------------
function test02(): void {
  // [Class01] 的共享实例
  $class01 = new Class01();
  // 两个对象
  $class02 = new Class02();
  $class03 = new Class03();
  // 将访问同一个 [Class01] 实例
  $class02->setClass01($class01);
  $class03->setClass01($class01);
  // 验证
  $class02->doSomething();
  $class03->doSomethingElse();
}

// test01
print "test01-----------------\n";
test01();
// 测试02
print "test02-----------------\n";
test02();

注释

  • 第 4-23 行:类 [Class01] 取代了属性 [Trait01][Trait01] 的代码被整合到了类 [Class02] [Class03] 的代码中。 在此情况下则不然。将引用类代码 [Class01],并将其注入类 [Class02] [Class03] 中。 这意味着类 [Class01] 的属性将位于类 [Class02] [Class03] 的代码之外。 由于此处 [id] 属性为私有(第 6 行),因此需要提供一个 getter(第 14-16 行)和一个 setter(第 9-11 行)。这是与特性的第一个区别:必须编写访问类中私有属性的代码。 虽然可以将属性 [id] 的可见性改为 [public],但绝不建议这样做。通过 setter 方法设置属性值可以验证其有效性;
  • 第 27 行:在 [Class02] 的代码中引入对类 [Class01] 的引用。由于该属性为私有,我们需要创建一个 setter(第 30-32 行)来对其进行初始化;
  • 第 37-41 行:在调用 [Class01] 代码时,必须通过 [$this→class01] 属性进行;
  • 第48-69行:类[Class03]是类[Classe02]的克隆,只是其方法名称不同;
  • 第72-82行:第一个测试。该测试通过向类[Class02]和[Class03]注入两个不同的[Class01]类实例(第77和78行)来实现;
  • 第 85-97 行:第二个测试将 [Class01] 类的同一个实例注入到 [Class02] [Class03] 类中(第 97、92、93 行);
  • 第 100-101 行:执行测试 [test01]
  • 第 103-104 行:执行测试 [test02]

结果

test01-----------------
Class01::doSomething… (10)
Class02->doSomething
Class01::doSomething… (10)
Class03->doSomethingElse
test02-----------------
Class01::doSomething… (10)
Class02->doSomething
Class01::doSomething… (20)
Class03->doSomethingElse

结果说明

  • 第2-5行:结果与使用[Trait01]特性的情况相同。 由此可得出结论:此处使用特质并非必不可少,但它确实能减少代码量,因为无需额外方法来访问特质的属性——这些属性已是包含该特质的代码的组成部分;
  • 第7-10行:由于在类[Class02][Class03]中注入了相同的[Class01]引用, [Class01::id] 属性被这两个类共享。这就是为什么结果的第 9 行显示 20 而不是 10(使用特性的情况)。由此可得出结论:如果特性的属性需要在类之间共享,那么该特性就无法使用,此时必须使用类;

9.4. 将方法归类到一个特质中

在前面的示例中,特质包含属性与方法。这里我们考虑特质仅包含方法的情况。在此情况下,特质类似于方法的抽象,可用于不同类中。由于该质没有属性,我们将探讨其中聚合的方法仅处理传入参数的情况。 实际上,这并非强制要求:一个特质可以操作属性 [$this→attribut1],而自身并不拥有该属性。此时,需要使用该特质的类来提供属性 [$this→attribut1]

特质仅包含处理传入参数的方法,我们将证明该特质可被一个具有相同方法(且声明为静态)的类所替代。

脚本 [trait-04.php] 演示了该特性的使用,它沿用了前一个示例中的特性,但去除了所有属性:


<?php

trait Trait01 {

  // 共享方法
  public function doSomething() {
    print "Trait01::doSomething ….\n";
  }

}

class Class02 {
  // 包含 Trait01
  use Trait01 {
    // 方法 [Trait01::doSomething] 已启用
    // 在类中以名称 [doSomethingInTrait] 存在
    Trait01::doSomething as doSomethingInTrait;
  }

  public function doSomething(): void {
    // 调用Trait01方法
    $this->doSomethingInTrait();
    // 显示局部变量
    print "Class02->doSomething\n";
  }

}

class Class03 {
  // 包含 Trait01
  use Trait01;

  // 类的方法
  public function doSomethingElse(): void {
    // 调用Trait01的方法
    $this->doSomething();
    // 本地显示
    print "Class03->doSomethingElse\n";
  }

}

// 测试 ----------------
(new Class02())->doSomething();
(new Class03())->doSomethingElse();

注释

  • 第 3-10 行:[Trait01] 特质不再包含任何属性;
  • 第 14-18 行:将 [Trait01] 包含在 [Class02] 中。第 22 行使用了 [Trait01] 的方法;
  • 第 31 行:将 [Trait01] 包含在 [Class03] 中。第 36 行使用了 [Trait01] 的方法;

结果

1
2
3
4
Trait01::doSomething ….
Class02->doSomething
Trait01::doSomething ….
Class03->doSomethingElse

在此用例中,[Trait01] 特性可轻松替换为类。如下面的 [trait-05.php] 脚本所示:


<?php

abstract class Class01 {

  // 共享静态方法
  public static function doSomething() {
    print "Class01::doSomething ….\n";
  }

}

class Class02 {

  public function doSomething(): void {
    // 调用 Class01 的方法
    Class01::doSomething();
    // 本地显示
    print "Class02->doSomething\n";
  }

}

class Class03 {

  // 类本地方法
  public function doSomethingElse(): void {
    // 调用 Class01 的方法
    Class01::doSomething();
    // 本地显示
    print "Class03->doSomethingElse\n";
  }

}

// 测试 ----------------
(new Class02())->doSomething();
(new Class03())->doSomethingElse();

注释

  • 第 3-10 行:将特征 [Trait01] 替换为抽象类 [Class01],该类的所有方法均声明为静态方法。该类仅被声明为抽象类,目的是防止其实例化。 原本也想编写 [final] 来阻止其派生,但 PHP 7 不接受类的前缀 [final abstract]。只能选其一,不能两者兼得;
  • 第16行:不再写 [$this→doSomethingInTrait],而是写 [Class01::doSomething],即调用类 [Class01] 的静态方法 [doSomething]
  • 第 28 行:在 [Class03] 中重复相同的操作;

结果

1
2
3
4
Class01::doSomething ….
Class02->doSomething
Class01::doSomething ….
Class03->doSomethingElse

结果确实与使用特征 [Trait01] 时完全相同,这表明可以避免使用该特征。我们曾提到,特征的方法可以在不包含该特征的情况下,对属性 [$this→attribut1] 进行操作。 因此,应由使用该特性的类来提供 [$this→attribut1] 属性。这是一种特殊情况:不妨将 [$this→attribut1] 属性“上移”到特性本身中,因为使用该特性的类必须拥有该属性。这样,它就必然会成为使用该特性的类的属性之一。

9.5. 使用特性的多重继承

在 PHP 的相关文献中,常提到特质(trait)支持多重继承:即一个类可以继承自多个类。C++ 语言具备这一特性,但 Java 或 C# 语言仅支持单重继承。 我们将证明,虽然在派生类中使用特质确实可以实现类似多重继承的功能,但这种用例同样可以通过简单的类来实现。

脚本 [trait-06.php] 演示了派生类和特性的应用:


<?php

trait Trait01 {
  // 属性
  private $i;

  // 共享方法
  public function doSomethingInTrait01() {
    // 修改 Trait01::$i
    $this->i++;
    // 显示
    print "Trait01::doSomethingInTrait01… i=$this->i\n";
  }

}

class Class02 {
  // 属性
  protected $j = 0;

  // 方法
  public function doSomethingInClass02(): void {
    // 修改 Class02::j
    $this->j += 10;
    // 显示
    print "Class02->doSomethingInClass02… j=$this->j\n";
  }

}

// 派生类
class Class03 extends Class02 {
  // 继承自 Class02:j 和 Trait01::i
  // 包含 Trait01
  use Trait01;

  // 方法
  public function doSomethingInClass03(): void {
    // 使用 Trait01 的方法
    $this->doSomethingInTrait01();
    // 修改 Trait01::i
    $this->i += 100;
    // 修改 Class03::j (==Class02::j)
    $this->j += 1000;
    // 显示
    print "Class03->doSomethingInClass03… i=$this->i, j=$this->j\n";
  }

}

// 测试 ----------------
(new Class02())->doSomethingInClass02();
(new Class03())->doSomethingInClass03();

注释

  • 第 3-15 行:回归到一个名为 [Trait01] 的特质,该特质包含一个属性及其操作方法;
  • 第 17-29 行:一个名为 [Class02] 的类,与特征 [Trait01] 毫无关联。它并未使用该特征;
  • 第 19 行:声明了 [Class02] 的唯一属性,并将其可见性设为 [protected],以便在派生类中访问该属性;
  • 第 32 行:类 [Class03] 继承自类 [Class02]。此外,它还实现了特征 [Trait01](第 35 行)。 最后,它继承了 [Class02] 的属性和方法,并集成了 [Trait01] 的属性和方法。因此,这确实类似于多重继承;

结果

1
2
3
Class02->doSomethingInClass02… j=10
Trait01::doSomethingInTrait01… i=1
Class03->doSomethingInClass03… i=101, j=1000

与前面的示例一样,我们将证明:

  • 特质可以被类所替代;
  • 与其将特性嵌入派生类中,不如嵌入该类的实例引用;

脚本 [trait-07.php] 如下:


<?php

class Class01 {
  // 属性
  protected $i;

  // getter 和 setter
  public function getI(): int {
    return $this->i;
  }

  public function setI(int $i): void {
    $this->i = $i;
  }

  // 方法
  public function doSomethingInClass01(): void {
    // 修改 Class01::$i
    $this->i++;
    // 显示
    print "Class01::doSomething in Class01… i=$this->i\n";
  }

}

class Class02 {
  // 属性
  protected $j = 0;

  // 类专属方法
  public function doSomethingInClass02(): void {
    // 修改 Class02::j
    $this->j += 10;
    // 显示
    print "Class02->doSomethingInClass02… j=$this->j\n";
  }

}

class Class03 extends Class02 {
  // 包含 Class01
  private $class01;

  // 设置器
  public function setClass01(Class01 $class01) {
    $this->class01 = $class01;
  }

  // 类局部方法
  public function doSomethingInClass03(): void {
    // 调用 Class01 的方法
    $this->class01->doSomethingInClass01();
    // 修改 Class01::i
    $i = $this->class01->getI();
    $i += 100;
    $this->class01->setI($i);
    // 修改 Class03::j
    $this->j += 1000;
    // 显示
    print "Class03->doSomethingInClass03… i=$i, j=$this->j\n";
  }

}

// 测试 ----------------
$class01 = new Class01();
$class02 = new Class02();
$class03 = new Class03();
$class03->setClass01($class01);
$class02->doSomethingInClass02();
$class03->doSomethingInClass03();

注释

  • 第 3-24 行:类 [Class01] 取代了特性 [Trait01]。 由于类 [Class01] 将通过引用被纳入其他类中,因此为属性 [$i] 提供了 get / set 方法;
  • 第26-38行:类[Classe02]保持不变;
  • 第 40 行:类 [Class03] 继承自类 [Classe02]
  • 第 42 行:类 [Classe01] 通过引用被包含在 [Classe03] 中;
  • 第45-47行:定义setter,用于初始化对[Classe01]的引用;
  • 第50-61行:方法[doSomethingInClass03]与前文功能相同,但代码更为复杂;

结果

1
2
3
Class02->doSomethingInClass02… j=10
Class01::doSomething in Class01… i=1
Class03->doSomethingInClass03… i=101, j=1000

从这个示例中可以得出结论:特质(trait)在此处并非必不可少,但必须承认它能让派生类中的代码写得更简洁。

9.6. 使用特质代替抽象类

我们经常遇到以下使用场景:创建一个相当通用的接口 I,它可以衍生出多个实现。这些实现共享共同的代码,但在其他方法上有所不同。可以通过两种方式实现这种使用场景:

  1. 我们创建一个抽象类 C,用于封装派生类共有的代码。类 C 实现了接口 I,但其中某些需要在派生类中声明的方法在类 C 中被声明为抽象方法,因此类 C 本身也是抽象类。 随后创建从 C 派生的类 C1 和 C2,它们各自以自己的方式实现了父类 C 中未定义(抽象)的方法;
  2. 创建一个与前一解决方案中的抽象类 C 几乎完全相同的特征 T。该特征不实现接口 I,因为从语法上讲它无法实现。 随后创建类 C1 和 C2,它们实现接口 I 并使用特征 T。这些类只需实现特征 T 未实现的接口 I 中的方法即可;

以下示例展示了这两种解决方案的相似性。

应用程序 1 实现了前面描述的解决方案 1 [trait-08.php]


<?php

interface Interface1 {

  public function doSomething(): void;

  public function doSomethingElse(): void;
}

abstract class AbstractClass implements Interface1 {
  // 属性
  protected $attr1 = 11;
  protected $attr2 = 12;

  // 获取器和设置器
  public function getAttr1() {
    return $this->attr1;
  }

  public function getAttr2() {
    return $this->attr2;
  }

  public function setAttr1($attr1) {
    $this->attr1 = $attr1;
    return $this;
  }

  public function setAttr2($attr2) {
    $this->attr2 = $attr2;
    return $this;
  }

  // 已实现的方法
  public function doSomething(): void {
    print "AbstractClass::doSomething [$this->attr1,$this->attr2]\n";
  }

  // 未实现的方法
  abstract public function doSomethingElse(): void;
}

// 派生类 1
class Class1 extends AbstractClass {
  // 属性
  private $attr3 = 13;

  // 获取器和设置器
  public function getAttr3() {
    return $this->attr3;
  }

  public function setAttr3($attr3) {
    $this->attr3 = $attr3;
    return $this;
  }

  // 实现 doSomethingElse
  public function doSomethingElse(): void {
    print "Class1::doSomethingElse [$this->attr1,$this->attr2,$this->attr3]\n";
  }

}

// 派生类 2
class Class2 extends AbstractClass {
  // 属性
  private $attr4 = 14;

  public function getAttr4() {
    return $this->attr4;
  }

  public function setAttr4($attr4) {
    $this->attr4 = $attr4;
    return $this;
  }

  // 实现 doSomethingElse
  public function doSomethingElse(): void {
    print "Class2::doSomethingElse [$this->attr1,$this->attr2,$this->attr4]\n";
  }

}

// 外部函数
function useInterfaceWith(Interface1 $interface):void{
  $interface->doSomething();
  $interface->doSomethingElse();
}

// 测试
useInterfaceWith(new Class1());
useInterfaceWith(new Class2());

注释

  • 第 3-8 行:接口 [Interface1] 有两个方法;
  • 第 10-41 行:抽象类 [AbstractClass] 实现了接口 [Interface1](第 10 行)。 它拥有两个属性及其相应的获取器和设置器(第12-32行),实现了接口[Interface1]中的方法[doSomething](第35-37行) 但无法实现方法 [doSomethingElse]。因此该方法被声明为抽象方法(第 40 行)。抽象类 [AbstractClass] 无法被实例化,若要发挥作用,必须通过派生来实现;
  • 第44-63行:Class1类继承了抽象类[AbstractClass],因此实现了接口[Interface1](第14行)。 它为其父类未定义的方法 [doSomethingElse] 提供了具体实现(第 59-61 行)。它还为其父类的属性添加了新属性(第 46-56 行);
  • 第 66-82 行:Class2 类继承了抽象类 [AbstractClass],因此实现了接口 [Interface1](第 66 行)。 它为父类未定义的方法 [doSomethingElse] 提供了实现(第 80-82 行)。它还向父类的属性中添加了一个属性(第 68-77 行);
  • 第 87-90 行:函数 [useInterfaceWith] 接收类型 [Interface1] 作为参数,并调用该接口的两个方法;
  • 第 93-94 行:第一次调用函数 [useInterfaceWith] 时传入类型 [Class1],第二次调用时传入类型 [Class2]。 这是正确的,因为这两个类型都实现了 [Interface1] 接口;

结果


AbstractClass::doSomething [11,12]
Class1::doSomethingElse [11,12,13]
AbstractClass::doSomething [11,12]
Class2::doSomethingElse [11,12,14]

现在我们使用脚本 [trait-09.php] 实现方案 2。这涉及将抽象类替换为特质:


<?php

interface Interface1 {

  public function doSomething(): void;

  public function doSomethingElse(): void;
}

trait Trait1 {
  // 属性
  private $attr1 = 11;
  private $attr2 = 12;

  // 获取器和设置器
  public function getAttr1() {
    return $this->attr1;
  }

  public function getAttr2() {
    return $this->attr2;
  }

  public function setAttr1($attr1) {
    $this->attr1 = $attr1;
    return $this;
  }

  public function setAttr2($attr2) {
    $this->attr2 = $attr2;
    return $this;
  }

  // 已实现的方法
  public function doSomething(): void {
    print "Trait::doSomething [$this->attr1,$this->attr2]\n";
  }
}

// 派生类 1
class Class1 implements Interface1 {
  // 特性的使用
  use Trait1;
  // 属性
  private $attr3 = 13;

  // 获取器和设置器
  public function getAttr3() {
    return $this->attr3;
  }

  public function setAttr3($attr3) {
    $this->attr3 = $attr3;
    return $this;
  }

  // 实现doSomethingElse
  public function doSomethingElse(): void {
    print "Class1::doSomethingElse [$this->attr1,$this->attr2,$this->attr3]\n";
  }

}

// 派生类 2
class Class2 implements Interface1 {
  // 特性的使用
  use Trait1;
  // 属性
  private $attr4 = 14;

  public function getAttr4() {
    return $this->attr4;
  }

  public function setAttr4($attr4) {
    $this->attr4 = $attr4;
    return $this;
  }

  // 实现 doSomethingElse
  public function doSomethingElse(): void {
    print "Class2::doSomethingElse [$this->attr1,$this->attr2,$this->attr4]\n";
  }

}

// 使用接口的外部函数
function useInterfaceWith(Interface1 $interface): void {
  $interface->doSomething();
  $interface->doSomethingElse();
}

// 测试
useInterfaceWith(new Class1());
useInterfaceWith(new Class2());

注释

  • 第 3-8 行:接口 [Interface1] 未作更改;
  • 第10-41行:特征[Trait1]取代了方案1中的抽象类[AbstractClass]。代码基本相同,仅存在以下细微差异:
    • 第 10 行:[Trait1] 特质未实现 [Interface1] 接口。这在语法上是不可能的;
    • 第 12-13 行:抽象类 [AbstractClass] 的属性可见性 [protected] 在此处变为 [private]。 这两个属性旨在让派生类能够直接访问父类(protected)或特性的(private)属性,而无需通过getter和setter方法;
    • 特征 [Trait1] 未声明抽象方法 [doSomethingElse]
  • 第41-62行:方案2中的[Class1]与方案1中的[Class1]完全相同,仅存在以下细微差异:
    • 第 41 行:类 [Class1] 实现了接口 [Interface1],而在方案 1 中,它继承了抽象类 [AbstractClass]
    • 第 43 行:它使用特征 [Trait1] 来实现接口的一部分;
  • 第 65-85 行:可参照 [Class1] 的说明;
  • 第87-95行:其余代码保持不变;

结果


Trait::doSomething [11,12]
Class1::doSomethingElse [11,12,13]
Trait::doSomething [11,12]
Class2::doSomethingElse [11,12,14]

确实得到了相同的结果。

9.7. 结论

从前面的示例可以看出,使用破折号能带来净收益的使用场景并不明确。在我们的示例中,始终可以通过将其替换为类来省略它。然而,在将代码在不同的派生类之间进行提取时,使用破折号似乎很实用,仿佛该代码属于一个父类一样。这就是我们在接下来的示例中将要做的事情。