9. 特质
特质(Traits)是类似于类的结构。然而,它们无法被实例化。它们旨在被包含在类中。将一个特质包含在类中,其效果等同于将该特质的代码复制到类中。我们将可能在多个类中重复使用的代码放入特质中。
9.1. 脚本树

9.2. 在类中包含一个特质
脚本 [traits-01.php] 演示了在类中使用特性的基本方法:
<?php
class Class04 {
// attribute
private $name;
// manufacturer
public function __construct(string $name) {
$this->name = $name;
}
// getters and setters
public function getName(): string {
return $this->name;
}
public function setName(string $name): void {
$this->name = $name;
}
}
trait Trait04 {
// attribute
private $name;
// getters and setters
public function getName(): string {
return $this->name;
}
public function setName(string $name): void {
$this->name = $name;
}
}
class Class05 {
// trait inclusion
use Trait04;
// manufacturer
public function __construct(string $name) {
$this->name = $name;
}
}
// test --------------
$class04 = new Class04("Tim");
$class05 = new Class05("Burton");
print $class04->getName() . "\n";
print $class05->getName() . "\n";
// display of both classes
print_r($class04);
print_r($class05);
代码注释
- 第 3–21 行:定义了 [Class04] 类,其中包含一个属性、其 get/set 方法以及一个构造函数;
- 第 23–36 行:我们将 [Class04] 中的代码(不包含其构造函数)原样转移到 trait [Trait04] 中。由于 trait 无法被实例化,因此我们不包含构造函数;
- 第 23 行:关键字 [trait] 使 [Trait04] 成为一个 trait 而非类;
- 第 38–45 行:我们定义了一个类 [Class05],该类采用特性 [Trait04] 中的代码(第 40 行),并添加了一个构造函数(第 43–45 行),该构造函数与类 [Class04] 的构造函数完全相同,以使该类可实例化;
- 第 40 行:关键字 [use] 允许将一个特质包含到类中;
- 第 50–56 行:测试表明类 [Class04] 和 [Class05] 的运行方式相同;
结果
第3至10行的结果表明,类[Class04]和[Class05]的内容相同;
结论
在类中使用 [use Trait] 语句,相当于将 [Trait] 中的代码包含到该类中。
9.3. 在不同类中使用同一个特质
特质的主要优势之一似乎在于可以在不同类之间复用相同的代码(属性 + 方法)。不过,我们将看到,使用简单的类也能实现同样的目标。
以下 [trait-02.php] 脚本演示了在类之间共享特性的示例:
<?php
trait Trait01 {
// attribute
private $id = 0;
// method
public function doSomething() {
print "Trait01::doSomething… ($this->id)\n";
}
}
class Class02 {
// inclusion Trait01
use Trait01 {
// method [Trait01::doSomething] is accessible
// in the class under the name [doSomethingInTrait]
Trait01::doSomething as doSomethingInTrait;
}
// class-specific method
public function doSomething(): void {
// id attribute
$this->id += 10;
// using the Trait01 method
$this->doSomethingInTrait();
// local display
print "Class02->doSomething\n";
}
}
class Class03 {
// inclusion Trait01
use Trait01;
// method local to the class
public function doSomethingElse(): void {
// id attribute
$this->id += 10;
// using the Trait01 method
$this->doSomething();
// local display
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] 的方法之间不存在冲突;
结果
请注意,特质 [Trait01] 并未在类 [Class02] 和 [Class03] 之间共享。因此,通过在类 [Class02] 和 [Class03] 中包含 [Trait01],属性 [Trait01::i] 变成了两个独立的属性:[Class02::i] 和 [Class03::i]。 结果的第 2 行和第 4 行展示了这一点。如果属性 [Trait01::i] 在类 [Class02] 和 [Class03] 之间共享,那么第 4 行显示的数值将是 20 而不是 10。
脚本 [trait-03.php] 展示了通过使用类(class)而非特质(trait)也能实现相同的结果:
<?php
// class that replaces the
class Class01 {
// attribute
private $id = 0;
// setter
public function setId(int $id) {
$this->id = $id;
}
// getter
public function getId(): int {
return $this->id;
}
// method
public function doSomething(): void {
print "Class01::doSomething… ($this->id)\n";
}
}
class Class02 {
// class01 inclusion
private $class01;
// setter
public function setClass01(Class01 $class01) {
$this->class01 = $class01;
}
// class-specific method
public function doSomething(): void {
// chgt Class01 attribute
$id = $this->class01->getId();
$id += 10;
$this->class01->setId($id);
// using the Class01 method
$this->class01->doSomething();
// local display
print "Class02->doSomething\n";
}
}
class Class03 {
// class01 inclusion
private $class01;
// setter
public function setClass01(Class01 $class01) {
$this->class01 = $class01;
}
// method local to the class
public function doSomethingElse(): void {
// chgt Class01 attribute
$id = $this->class01->getId();
$id += 10;
$this->class01->setId($id);
// using the Class01 method
$this->class01->doSomething();
// local display
print "Class03->doSomethingElse\n";
}
}
// test01 ----------------
function test01(): void {
// two objects
$class02 = new Class02();
$class03 = new Class03();
// will access two different instances of [Class01]
$class02->setClass01(new Class01());
$class03->setClass01(new Class01());
// check
$class02->doSomething();
$class03->doSomethingElse();
}
// test02 ----------------
function test02(): void {
// shared instance of [Class01]
$class01 = new Class01();
// two objects
$class02 = new Class02();
$class03 = new Class03();
// will access the same instance of [Class01]
$class02->setClass01($class01);
$class03->setClass01($class01);
// check
$class02->doSomething();
$class03->doSomethingElse();
}
// test01
print "test01-----------------\n";
test01();
// test02
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],但这绝不推荐。使用设置器来设置属性的值,可以让我们对其进行验证;
- 第 27 行:在 [Class02] 代码中包含对 [Class01] 类的引用。由于该属性是私有的,我们需要创建一个设置器(第 30–32 行)来初始化它;
- 第 37–41 行:在使用 [Class01] 中的代码时,必须使用 [$this→class01] 属性;
- 第 48–69 行:类 [Class03] 是类 [Class02] 的克隆,只是其方法名称不同;
- 第 72–82 行:第一个测试。该测试涉及将 [Class01] 类的两个不同实例注入到 [Class02] 和 [Class03] 类中(第 77 和 78 行);
- 第 85–97 行:第二个测试将 [Class01] 类的同一个实例注入到 [Class02] 和 [Class03] 类中(第 97、92、93 行);
- 第 100–101 行:执行测试 [test01];
- 第 103–104 行:执行测试 [test02];
结果
对结果的评论
- 第 2–5 行:我们得到了与使用 [Trait01] 特质时相同的结果。我们可以得出结论:在此处使用特质并非必需,但它确实减少了代码量,因为无需通过方法访问特质的属性——这些属性已成为包含该特质的代码不可分割的一部分;
- 第 7–10 行:由于对 [Class01] 的同一引用被注入到类 [Class02] 和 [Class03] 中,因此属性 [Class01::id] 在这两个类之间是共享的。这就是为什么在使用特质时,结果的第 9 行显示 20 而不是 10 的原因。 我们可以得出结论:如果特性的属性需要在类之间共享,那么该特性就无法使用,必须改用类;
9.4. 在特质中分组方法
在前面的示例中,特质包含属性和方法。这里,我们考虑特质仅包含方法的情况。在这种情况下,特质类似于方法的抽象化,这些方法随后可在不同的类中使用。由于此处的特质没有属性,我们将考虑其所聚合的方法仅作用于传递给它们的参数的情况。 实际上,这并非强制要求:特质可以操作属性 [$this→attribute1],而无需自身拥有该属性。此时,由使用该特质的类来提供属性 [$this→attribute1]。
当特质仅包含完全基于传入参数进行操作的方法时,我们将证明该特质可以被一个具有相同方法且声明为静态的类所替代。
脚本 [trait-04.php] 演示了该特性的用法,该脚本复用了前一个示例中的特性,同时移除了所有属性:
<?php
trait Trait01 {
// method to share
public function doSomething() {
print "Trait01::doSomething ….\n";
}
}
class Class02 {
// inclusion Trait01
use Trait01 {
// method [Trait01::doSomething] is accessible
// in the class under the name [doSomethingInTrait]
Trait01::doSomething as doSomethingInTrait;
}
public function doSomething(): void {
// trait01 method call
$this->doSomethingInTrait();
// local display
print "Class02->doSomething\n";
}
}
class Class03 {
// inclusion Trait01
use Trait01;
// method local to the class
public function doSomethingElse(): void {
// trait01 method call
$this->doSomething();
// local display
print "Class03->doSomethingElse\n";
}
}
// test ----------------
(new Class02())->doSomething();
(new Class03())->doSomethingElse();
注释
- 第 3–10 行:特质 [Trait01] 不再拥有任何属性;
- 第 14–18 行:将 [Trait01] 包含在 [Class02] 中。第 22 行使用了 [Trait01] 的方法;
- 第 31 行:将 [Trait01] 包含在 [Class03] 中。第 36 行使用了 [Trait01] 的方法;
结果
在此用例中,特质 [Trait01] 可以轻松地被一个类所替代。以下脚本 [trait-05.php] 演示了这一点:
<?php
abstract class Class01 {
// static method to share
public static function doSomething() {
print "Class01::doSomething ….\n";
}
}
class Class02 {
public function doSomething(): void {
// calling the Class01 method
Class01::doSomething();
// local display
print "Class02->doSomething\n";
}
}
class Class03 {
// method local to the class
public function doSomethingElse(): void {
// calling the Class01 method
Class01::doSomething();
// local display
print "Class03->doSomethingElse\n";
}
}
// test ----------------
(new Class02())->doSomething();
(new Class03())->doSomethingElse();
注释
- 第 3-10 行:将特质 [Trait01] 替换为抽象类 [Class01],其中所有方法均声明为静态方法。该类仅因需防止实例化而被声明为抽象类。我们本希望同时添加 [final] 来禁止其派生,但 PHP 7 不支持类使用 [final abstract] 前缀。二者只能选其一,不可兼得;
- 第 16 行:不再写 [$this→doSomethingInTrait],而是写 [Class01::doSomething],即调用类 [Class01] 的静态方法 [doSomething];
- 第 28 行:我们在 [Class03] 中重复相同的操作;
结果
我们得到了与特质 [Trait01] 相同的结果,这表明可以避免使用它。我们注意到,即使特质本身不具备某个属性,其方法仍可对属性 [$this→attribut1] 进行操作。 因此,是否提供该属性 [$this→attribut1] 取决于使用该特性的类。这是一个特殊情况:我们不妨将该属性 [$this→attribut1](使用该特性的类必须拥有)“传递”给特性本身。这样一来,它必然会成为使用该特性的类的属性之一。
9.5. 使用特性的多重继承
在 PHP 文献中,常提到特质(trait)支持多重继承:即一个类可以继承自多个类。C++ 语言支持这一特性,但 Java 和 C# 则不支持,因为它们仅支持单继承。我们将展示:虽然在派生类中使用特质确实可以实现类似多重继承的功能,但这种用例也可以通过简单的类来实现。
脚本 [trait-06.php] 实现了一个派生类和一个特质:
<?php
trait Trait01 {
// attribute
private $i;
// method to share
public function doSomethingInTrait01() {
// modification Trait01::$i
$this->i++;
// display
print "Trait01::doSomethingInTrait01… i=$this->i\n";
}
}
class Class02 {
// attribute
protected $j = 0;
// method
public function doSomethingInClass02(): void {
// modification Class02::j
$this->j += 10;
// display
print "Class02->doSomethingInClass02… j=$this->j\n";
}
}
// derived class
class Class03 extends Class02 {
// inherits Class02:j and Trait01::i
// inclusion Trait01
use Trait01;
// method
public function doSomethingInClass03(): void {
// using the Trait01 method
$this->doSomethingInTrait01();
// modification Trait01::i
$this->i += 100;
// modification Class03::j (==Class02::j)
$this->j += 1000;
// display
print "Class03->doSomethingInClass03… i=$this->i, j=$this->j\n";
}
}
// test ----------------
(new Class02())->doSomethingInClass02();
(new Class03())->doSomethingInClass03();
注释
- 第 3–15 行:我们回到一个带有属性的特质 [Trait01],以及一个操作该属性的方法;
- 第 17–29 行:一个与特质 [Trait01] 毫无关联的类 [Class02]。它并未使用该特质;
- 第 19 行:我们已将 [Class02] 的唯一属性声明为 [protected] 可见性,以便在派生类中可以访问它;
- 第 32 行:类 [Class03] 继承自类 [Class02]。此外,它还实现了特质 [Trait01](第 35 行)。最后,它继承了 [Class02] 的属性和方法,并实现了 [Trait01] 的属性和方法。因此,我们得到了类似于多重继承的结构;
结果
正如我们在前面的例子中所做的那样,我们将证明:
- 该特质可以被类所替代;
- 与其将特质直接包含在派生类中,不如引入该类的实例引用;
脚本 [trait-07.php] 如下:
<?php
class Class01 {
// attribute
protected $i;
// getter and setter
public function getI(): int {
return $this->i;
}
public function setI(int $i): void {
$this->i = $i;
}
// method
public function doSomethingInClass01(): void {
// modification Class01::$i
$this->i++;
// display
print "Class01::doSomething in Class01… i=$this->i\n";
}
}
class Class02 {
// attribute
protected $j = 0;
// class-specific method
public function doSomethingInClass02(): void {
// modification Class02::j
$this->j += 10;
// display
print "Class02->doSomethingInClass02… j=$this->j\n";
}
}
class Class03 extends Class02 {
// class01 inclusion
private $class01;
// setter
public function setClass01(Class01 $class01) {
$this->class01 = $class01;
}
// method local to the class
public function doSomethingInClass03(): void {
// using the Class01 method
$this->class01->doSomethingInClass01();
// modification Class01::i
$i = $this->class01->getI();
$i += 100;
$this->class01->setI($i);
// modification Class03::j
$this->j += 1000;
// display
print "Class03->doSomethingInClass03… i=$i, j=$this->j\n";
}
}
// test ----------------
$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 行:类 [Class02] 保持不变;
- 第 40 行:类 [Class03] 继承自类 [Class02];
- 第 42 行:类 [Class01] 通过引用被引入到 [Class03] 中;
- 第 45–47 行:提供了一个 setter 方法,用于初始化指向 [Class01] 类的引用;
- 第 50–61 行:方法 [doSomethingInClass03] 与之前的功能相同,但代码更为复杂;
结果
从这个例子中,我们可以得出结论:特质(trait)并非必不可少,但必须承认它确实能让派生类的代码更简洁。
9.6. 使用特质代替抽象类
我们经常遇到以下用例:我们创建一个相当通用的接口 I,它可以衍生出多个实现。这些实现共享共同的代码,但在其他方法上有所不同。我们可以采用两种方式来实现这种用例:
- 创建一个抽象类 C,其中包含派生类共有的代码。类 C 实现了接口 I,但某些必须在派生类中声明的方法在类 C 中被声明为抽象方法,因此类 C 本身也是抽象的。随后,我们创建从 C 派生的类 C1 和 C2,它们各自以自己的方式实现了父类 C 中未定义(抽象)的方法;
- 我们创建一个特质 T,其定义与前一种解决方案中的抽象类 C 几乎完全相同。该特质不会实现接口 I,因为从语法上讲它无法实现。接着,我们创建类 C1 和 C2,它们实现接口 I 并使用特质 T。这些类只需实现接口 I 中未被导入的特质 T 所实现的方法即可;
以下示例说明了这两种解决方案的相似之处。
应用程序 1 实现了上述的解决方案 1 [trait-08.php]:
<?php
interface Interface1 {
public function doSomething(): void;
public function doSomethingElse(): void;
}
abstract class AbstractClass implements Interface1 {
// attributes
protected $attr1 = 11;
protected $attr2 = 12;
// getters and setters
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;
}
// implemented method
public function doSomething(): void {
print "AbstractClass::doSomething [$this->attr1,$this->attr2]\n";
}
// method not implemented
abstract public function doSomethingElse(): void;
}
// derived class 1
class Class1 extends AbstractClass {
// attribute
private $attr3 = 13;
// getter and setter
public function getAttr3() {
return $this->attr3;
}
public function setAttr3($attr3) {
$this->attr3 = $attr3;
return $this;
}
// implementation doSomethingElse
public function doSomethingElse(): void {
print "Class1::doSomethingElse [$this->attr1,$this->attr2,$this->attr3]\n";
}
}
// derived class 2
class Class2 extends AbstractClass {
// attribute
private $attr4 = 14;
public function getAttr4() {
return $this->attr4;
}
public function setAttr4($attr4) {
$this->attr4 = $attr4;
return $this;
}
// implementation doSomethingElse
public function doSomethingElse(): void {
print "Class2::doSomethingElse [$this->attr1,$this->attr2,$this->attr4]\n";
}
}
// external function
function useInterfaceWith(Interface1 $interface):void{
$interface->doSomething();
$interface->doSomethingElse();
}
// tests
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。这涉及用一个特质(trait)替换抽象类:
<?php
interface Interface1 {
public function doSomething(): void;
public function doSomethingElse(): void;
}
trait Trait1 {
// attributes
private $attr1 = 11;
private $attr2 = 12;
// getters and setters
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;
}
// implemented method
public function doSomething(): void {
print "Trait::doSomething [$this->attr1,$this->attr2]\n";
}
}
// derived class 1
class Class1 implements Interface1 {
// line usage
use Trait1;
// attribute
private $attr3 = 13;
// getter and setter
public function getAttr3() {
return $this->attr3;
}
public function setAttr3($attr3) {
$this->attr3 = $attr3;
return $this;
}
// implementation doSomethingElse
public function doSomethingElse(): void {
print "Class1::doSomethingElse [$this->attr1,$this->attr2,$this->attr3]\n";
}
}
// derived class 2
class Class2 implements Interface1 {
// line usage
use Trait1;
// attribute
private $attr4 = 14;
public function getAttr4() {
return $this->attr4;
}
public function setAttr4($attr4) {
$this->attr4 = $attr4;
return $this;
}
// implementation doSomethingElse
public function doSomethingElse(): void {
print "Class2::doSomethingElse [$this->attr1,$this->attr2,$this->attr4]\n";
}
}
// external function using the
function useInterfaceWith(Interface1 $interface): void {
$interface->doSomething();
$interface->doSomethingElse();
}
// tests
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. 结论
从前面的示例可以清楚地看到,使用特性的用例并不明确。在我们的示例中,我们总能通过用类来替代它,从而省去特性的使用。然而,似乎在将不同派生类之间的代码抽离出来时,使用特性是实用的,就好像该代码属于父类一样。这就是我们在接下来的示例中要做的。