← PHP 接口:纯粹的“能力契约” PHP 封装:把数据和方法“打包”的艺术 →

PHP 抽象类与接口:两个“模板”的较量

原创 2026-05-12 PHP 已有人查阅

在PHP面向对象编程里,抽象类和接口这两个概念经常被放在一起比较。它们都能为类提供结构上的规范,但二者的设计哲学和应用场景有着本质的区别。一句话先帮你建立直觉印象:抽象类是有血有肉的半成品,接口是一张只有要求没有实现的设计图。

把这两个东西搞清楚,你的代码架构能力会迈上一个台阶。

抽象类:带默认实现的半成品

抽象类本质上还是一个类,它用 abstract 关键词声明,不能直接实例化。但和接口不同,抽象类里可以既有抽象方法(只有签名没有函数体),也有普通方法(有完整实现)。抽象类还可以拥有属性、构造方法,并且支持 publicprotectedprivate 三种访问修饰符。

它适合这样的场景:你预见到会有一系列子类共享大量相同的字段和方法逻辑,但其中某些行为必须由每个子类自己去定义。

抽象类的基础语法

abstract class ParentClass {
    abstract public function myMethod1();
    abstract public function myMethod2($name, $age);
    abstract public function myMethod3(): string;
}

注意,所有抽象方法前面都必须加上 abstract 关键词,而且一个类只要包含了至少一个抽象方法,这个类本身就必须声明为抽象类。

乐器示例:抽象类在行动

下面用乐器来演示一下抽象类的实际用法。所有乐器都有一个名字,也都能发出声音,但每种乐器的发声方式不同。Instrument 抽象类承担共享数据和共同行为,而 sound() 则作为抽象方法留给子类各自发挥:

abstract class Instrument {
    public $name;

    public function __construct($name) {
        $this->name = $name;
    }

    abstract public function sound(): string;
}

class Guitar extends Instrument {
    public function sound(): string {
        return "Strumming strings! I'm a $this->name.";
    }
}

class Piano extends Instrument {
    public function sound(): string {
        return "Melodic keys! I'm a $this->name.";
    }
}

class Drums extends Instrument {
    public function sound(): string {
        return "Loud and rhythmic! I'm a $this->name.";
    }
}

$guitar = new Guitar("Guitar");
echo $guitar->sound();
echo "<br>";

$piano = new Piano("Piano");
echo $piano->sound();
echo "<br>";

$drums = new Drums("Drums");
echo $drums->sound();

输出:

Strumming strings! I'm a Guitar.
Melodic keys! I'm a Piano.
Loud and rhythmic! I'm a Drums.

从这个例子可以明显看出抽象类的核心价值:$name 属性和 __construct() 构造方法放在父类里写一次就够了,三个子类自动拥有,不用重复声明。而 sound() 方法虽然在父类里定义了规范(必须返回 string),但具体内容被推迟到子类。这就是“部分抽象”——共享的部分写死,个性的部分强制子类填充。

抽象类的关键特征

  • 可以同时包含抽象方法和非抽象方法(部分抽象)

  • 可以声明属性,有自己的构造方法

  • 支持 publicprotectedprivate 访问修饰符

  • 使用 extends 关键词继承

  • 不能直接实例化

接口:一份纯粹的能力清单

接口比抽象类走得更远,它追求的是抽象——接口里不能有任何方法实现,也不能声明属性。接口里的所有方法自动为 public,也只能是 public。它的职责只有一个:定义一套方法签名,强制实现类去遵守。

接口用 interface 关键词定义,用 implements 关键词来被类实现。一个类可以同时实现多个接口,这是PHP绕过单继承限制的主要手段。

接口的基础语法

interface MyInterfaceName {
    public function methodA();
    public function methodB();
}

几何图形示例:接口的典型应用

还是用 Shape 接口来展示。它声明了两个方法:area() 和 getDimensions(),不关心面积怎么算出来的:

interface Shape {
    public function area(): float;
    public function getDimensions(): string;
}

class  implements Shape {
    private $base;
    private $height;

    public function __construct($base, $height) {
        $this->base = $base;
        $this->height = $height;
    }

    public function area(): float {
        return 0.5 * $this->base * $this->height;
    }

    public function getDimensions(): string {
        return "Base: {$this->base}, Height: {$this->height}";
    }
}

class Rectangle implements Shape {
    private $length;
    private $width;

    public function __construct($length, $width) {
        $this->length = $length;
        $this->width = $width;
    }

    public function area(): float {
        return $this->length * $this->width;
    }

    public function getDimensions(): string {
        return "Length: {$this->length}, Width: {$this->width}";
    }
}

$ = new (5, 10);
echo $->getDimensions() . " | Area of : " . $->area() . PHP_EOL;

$rectangle = new Rectangle(4, 6);
echo $rectangle->getDimensions() . " | Area of Rectangle: " . $rectangle->area() . PHP_EOL;

输出:

Base: 5, Height: 10 | Area of : 25
Length: 4, Width: 6 | Area of Rectangle: 24

这里  和 Rectangle 之间没有继承关系,但它们因为都实现了 Shape 接口而被纳入了同一个“形状生态”。接口的本质是能力契约,而不是血缘纽带。

接口的关键特征

  • 只包含方法声明,没有任何方法体(抽象)

  • 不能声明变量,但可以定义常量

  • 所有方法默认为 public,不允许其他访问修饰符

  • 一个类可以同时实现多个接口,用逗号分隔

  • 使用 implements 关键词实现

抽象类 vs 接口:一张表看懂区别

这个对照表值得你多看几遍,它把二者的差异做了系统梳理:

特性 抽象类 接口
抽象程度 部分抽象 抽象
方法类型 可同时包含抽象方法和普通方法 只能有抽象方法
变量声明 可以声明属性 不能声明变量(可声明常量)
访问修饰符 支持 publicprotectedprivate 仅支持 public
继承/实现关键词 extends implements
多继承 不支持 支持(实现多个接口)
代码复用 可通过非抽象方法实现在码复用 不提供任何代码复用

场景抉择:什么时候选谁?

很多人在做设计时会在抽象类和接口之间犹豫不决。我个人的判断逻辑是这样的——你先问自己一个问题:这些类之间,有没有可以被共享的数据和默认行为?

选抽象类的时机

  • 你的几个类确实属于同一个“家族”,有清晰的层级关系

  • 有一些属性和方法是所有子类共有的,适合抽到父类里避免重复

  • 你需要 protected 或 private 的成员来控制访问权限

  • 你想为某些方法提供默认实现,子类可以按需覆盖

乐器那个例子就是抽象类的典型应用场——吉他、钢琴、鼓都“是一种”乐器,共享 $name 属性和构造方法,sound() 则留给子类各自实现。

选接口的时机

  • 你需要定义一组跨类别的通用能力,实现类之间可能毫无血缘关系

  • 你想达到类似多继承的效果,让一个类具备多种独立的行为

  • 你只需要公开的方法声明,不需要任何实现细节

  • 你想设计松耦合、可插拔的组件

几何图形的例子正好切题——三角形和矩形之间没有任何继承关系,但通过实现同一个 Shape 接口,它们被统一纳入了面积计算的规范体系。更典型的还有PHP中内置的 CountableIterator 等接口,实现它们的类可能不搭边,但因为都遵守了同一套契约,所以可以用 count() 函数或 foreach 循环统一处理。

有时候两者可以配合使用

项目开发中不需要非此即彼。我就经常这么干:先写一个抽象类把共享的属性和方法抽取出来,同时让它实现某个接口,从而既有代码复用,又享受了接口的契约规范。子类继承抽象类后,也就自动成为接口的实现者之一,这是PHP面向对象设计里比较灵活的一种组合策略。

本节课程知识要点

  • 抽象类是部分抽象的,可包含抽象方法和普通方法,支持属性声明和各类访问修饰符,用 extends 继承。

  • 接口是抽象的,只有方法签名没有实现,不声明变量,方法强制为 public,用 implements 实现。

  • PHP 类只能继承一个父类,但可以同时实现多个接口,这是绕过单继承限制的主要途径。

  • 选择抽象类的核心判断依据是 “有共享数据和默认行为”,选择接口的判断依据是 “需要统一的能力契约”

  • 二者不是互斥关系,可以根据场景灵活搭配使用。

← PHP 接口:纯粹的“能力契约” PHP 封装:把数据和方法“打包”的艺术 →
分享笔记 (共有 篇笔记)
验证码:
微信公众号