特征与继承
本课属于 Coddy PHP 学习旅程的“面向对象编程”单元。第 55 课,共 91 课。
现在你已经了解了特征的工作方式,接下来我们来明确什么时候该使用特征,什么时候该使用继承。两者都能复用代码,但用途不同,特点也各不相同。
继承建立了“是一种”的关系。Dog 类扩展 Animal,表示狗是一种动物。子类继承父类的身份,并且可以在任何需要父类类型的地方使用。
特征提供“拥有某种功能”的能力。当一个类使用 Loggable 特征时,它就拥有日志记录功能,但它不会变成日志记录器。特征可以添加行为,而不改变类的本质。
<?php
// 继承:Dog 是 Animal
class Animal {
public function breathe(): void {
echo "Breathing";
}
}
class Dog extends Animal {
public function bark(): void {
echo "Woof!";
}
}
// Trait:Dog 具有日志记录功能
trait Loggable {
public function log(string $msg): void {
echo "Log: $msg";
}
}
class Cat extends Animal {
use Loggable;
}
需要记住的主要区别:
| 方面 | 继承 | 特征 |
|---|---|---|
| 关系 | 是一种 | 具有某种能力 |
| 限制 | 只能有一个父类 | 可以使用多个特征 |
| 类型检查 | 适用于 instanceof | 没有类型标识 |
| 适用场景 | 层级关系 | 无关类之间共享行为 |
当类具有共同身份时,使用继承。当彼此无关的类需要相同的功能,但不属于同一个继承体系时,使用 trait。
挑战
简单我们来构建一个车辆系统,展示何时该使用继承,何时该使用 trait。你将创建一个车辆层级结构,通过继承共享共同身份,同时通过 trait 添加横切功能。
你将把代码分为 4 个文件:
Trackable.php:创建一个名为Trackable的 trait,提供 GPS 跟踪功能。这个 trait 应包含:- 一个私有属性
$location(字符串) - 一个用于存储当前位置的方法
setLocation(string $location) - 一个返回
"Location: [location]"的方法getLocation()
- 一个私有属性
Vehicle.php:创建一个基类Vehicle,建立“属于某种类型”的关系。引入 trait 文件。使用构造函数属性提升定义一个受保护的$brand属性。添加一个返回"Vehicle: [brand]"的方法describe()。由于我们车队中的所有车辆都会配备 GPS,这个类应使用Trackabletrait。Car.php:创建一个继承自Vehicle的Car类。引入 Vehicle 文件。使用构造函数属性提升来接收品牌,并定义一个私有的$doors(整数)属性。使用品牌调用父类构造函数。重写describe()方法,使其返回"Car: [brand] with [doors] doors"。main.php:引入 Car 文件。你会收到 3 个输入:品牌名称、车门数量和位置字符串。创建一个Car实例,设置它的位置,然后打印两行:- 调用
describe()的结果 - 调用
getLocation()的结果
- 调用
注意继承和 trait 在这里如何协同工作:Car 属于 Vehicle(继承确立身份),而 Vehicle 拥有 跟踪功能(trait 添加行为)。你可以使用 instanceof Vehicle 检查某个对象是否是车辆,但没有名为“Trackable”的类型可供检查:这正是这两种方式的关键区别。
亲手试试
<?php
// 主文件:创建并测试 Car 实例
// TODO:引入 Car.php 文件
// 读取输入
$brand = trim(fgets(STDIN));
$doors = intval(trim(fgets(STDIN)));
$location = trim(fgets(STDIN));
// TODO:使用 brand 和 doors 创建 Car 实例
// TODO:设置汽车的位置
// TODO:打印 describe() 的结果
// TODO:打印 getLocation() 的结果
?>这节课包含一个小测验。开始学习即可作答,并记录你的进度。
面向对象编程中的所有课
自己练习: PHP 在线编译器