本文目录导读:

在 PHP 项目中,抽象类(Abstract Class)和接口(Interface)是两种核心的面向对象设计工具,选择哪一个,本质上是对“是什么”(is-a)与“能做什么”(can-do)两种关系的权衡。
下面从核心区别、适用场景和决策流程三个方面帮你理清思路。
核心区别速览
| 特性 | 抽象类 (Abstract) | 接口 (Interface) |
|---|---|---|
| 核心意图 | 定义“是什么”(is-a关系) 表示子类是父类的一种特殊类型 |
定义“能做什么”(can-do关系) 表示类具备某种能力或遵循某种规范 |
| 继承方式 | 单继承(一个类只能extends一个抽象类) | 多实现(一个类可以implement多个接口) |
| 属性 | 可以定义属性(成员变量,含访问修饰符) | 不能定义属性,只能定义常量(const) |
| 方法实现 | 可以包含已实现的方法和抽象方法 | 所有方法默认都是抽象的(PHP 8.0+ 可含默认实现,但不推荐) |
| 方法可见性 | 支持 public、protected、private |
仅 public |
| 构造函数 | 可以定义构造函数 | 不能定义构造函数(PHP 8.0 后正式废弃) |
选择场景:何时用抽象类?
一句话:当多个类共享相同的核心逻辑、状态(属性)或需要部分默认实现时,用抽象类。
典型场景:
- 共享代码与状态:多个子类有完全相同的成员变量(如
$name,$price)和相同的公共方法逻辑。 - 模板方法模式:父类定义算法骨架(一个
public模板方法),子类只需要实现其中的特定步骤(抽象方法)。 - 部分实现:父类已经实现了大部分功能,子类只需要覆写或补充少数关键方法。
- 需要
protected或private方法/属性:抽象类可以隐藏内部实现细节,而接口所有方法都必须公开。
伪代码示例:
abstract class Animal {
protected string $name;
protected int $age;
// 构造函数(子类可复用)
public function __construct(string $name, int $age) {
$this->name = $name;
$this->age = $age;
}
// 共同的行为(已实现)
public function breathe(): string {
return "{$this->name} is breathing";
}
// 子类必须实现的行为(抽象方法)
abstract public function makeSound(): string;
}
class Dog extends Animal {
public function makeSound(): string {
return "{$this->name} says Woof!";
// 子类可以直接使用 $this->name
}
// 自动继承了 breathe() 方法和构造函数
}
选择场景:何时用接口?
一句话:当定义一组行为规范,且该规范可能被完全不相关的多个类实现时,用接口。
典型场景:
- 多态能力:需要让不同的类(如
PdfExporter、XmlExporter、ExcelExporter)都支持export()方法。 - 解耦与依赖注入:通过接口编程,高层模块不依赖具体类,降低耦合(一个支付系统需要对接支付宝、微信、银联)。
- 多重继承能力:一个类需要同时具备多种能力(如
implements CanWalk, CanSwim)。 - 常量契约:定义一组不可变的常量(如
MAX_ITEMS_PER_PAGE = 20),强制实现类遵守。
伪代码示例:
interface Exportable {
public function export(): string;
}
interface Loggable {
public function log(string $message): void;
}
// 一个与 Animal 无关的类,但可以具备“可导出”能力
class Report implements Exportable {
public function export(): string {
return "Report data as CSV...";
}
}
// 一个类可以实现多个接口
class Invoice implements Exportable, Loggable {
public function export(): string {
return "Invoice data as PDF...";
}
public function log(string $message): void {
echo "[Invoice] $message";
}
}
决策流程图:一步一步选择
你可以按这个流程在脑海中快速判断:
-
是否需要定义属性(成员变量)?
- 是 → 抽象类(接口不能定义属性)
- 否 → 继续第2步
-
是否所有子类都共享大部分相同的逻辑代码?(如构造函数、公共方法实现)
- 是 → 抽象类(复用代码,减少重复)
- 否 → 继续第3步
-
这个类是否只能属于一个明确的父类?
- 是(单继承) → 考虑抽象类
- 否(可能需要实现多个能力) → 接口(多实现)
-
最关键的判断:描述的是“是什么关系”还是“能力契约”?
- “是一个特定的种类”(is-a):
Dog is an Animal→ 抽象类 - “具备某种能力”(can-do):
Report can be exported→ 接口 - 混合需求:先用抽象类提供核心骨架和共享代码,再让子类实现额外的接口来扩展能力。
- “是一个特定的种类”(is-a):
真实项目中的常见模式
在实际的 PHP 项目(Laravel、Symfony)里,你经常看到这种结合使用:
// 1. 先定义接口(能力契约)
interface PackageRepositoryInterface {
public function find(int $id): ?Package;
public function save(Package $package): void;
}
// 2. 再定义抽象类(共享实现骨架)
abstract class BaseRepository implements PackageRepositoryInterface {
protected Database $db;
public function __construct(Database $db) {
$this->db = $db;
}
// 公共实现
public function save(Package $package): void {
$this->db->insert($package->toArray());
}
// 抽象方法,强制子类实现
abstract public function find(int $id): ?Package;
}
// 3. 具体子类
class MysqlPackageRepository extends BaseRepository {
public function find(int $id): ?Package {
// 具体的 MySQL 查询逻辑
}
}
为什么这样设计?
- 接口:定义了契约,让业务逻辑可以解耦地依赖
PackageRepositoryInterface。 - 抽象类:提供了通用的数据库连接和
save()实现,减少重复代码。 - 子类:只关注实现
find()这个最关键的差异化行为。
记住三个关键词
| 工具 | 核心词 | 适用场景 |
|---|---|---|
| 抽象类 | 继承 & 复用 | 代码共享、状态共享、模板方法、单继承链 |
| 接口 | 契约 & 多态 | 行为规范、解耦、多重能力、依赖注入 |
最终建议:
- 如果你的直觉是“这个父类有默认行为和状态”,选抽象类。
- 如果你的直觉是“这个类必须遵循某种规范,但实现方式各不相同”,选接口。
- 两个都不冲突:可以先定义一个接口来搞解耦,再提供一个抽象类来实现默认的公共代码,这也是现代 PHP 框架最常用的模式。
版权声明:NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
CBA直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
足球直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播
英超直播