← PHP 抽象类:为什么不能直接用它创建对象? PHP 抽象类与接口:两个“模板”的较量 →

PHP 接口:纯粹的“能力契约”

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

在PHP的面向对象世界里,接口(Interface)扮演着一个严格的“合同拟定者”角色。如果说类是对象的模板,那么接口就是类的模板。它跟抽象类有几分神似,但更纯粹——接口里不能包含任何实际的代码或非抽象方法,唯一能做的事,就是定义方法名和它们的参数,不涉及具体实现。

这个设计的核心意图很明确:接口只负责“规定你能做什么”,而“具体怎么做”这件事,完全交给实现它的类去操心。接口里列出的每一个方法,在实现类中都必须有一个一模一样的定义。

定义接口使用 interface 关键词,除了把 class 换成 interface 之外,定义方式和类几乎一样:

interface InterfaceName {
    public function method1();
    public function method2($arg);
}

请注意,接口里的方法只有声明,没有任何函数体。所有方法默认就是 public 的,PHP 强制这个可见性,你也不能把接口方法标记为 private 或 protected——我个人理解,这个限制其实是在维护接口作为公开契约的纯粹性。

不是 extends,而是 implements

这是很多初学者容易混淆的地方。继承类时我们用 extends,但当一个类要遵循某个接口的约定时,关键词换成了 implements。这个用词本身就在强调:你在“实现”一份规范,而不是在“扩展”一个父类。

基础示例:一个类实现两个接口

下面这个例子很简单,两个接口分别声明了一个方法,一个类同时实现了它们:

interface i1 {
    public function fun1();
}

interface i2 {
    public function fun2();
}

class cls1 implements i1, i2 {
    function fun1() {
        echo "This function is from Interface i1.";
    }
    function fun2() {
        echo " This function is from Interface i2.";
    }
}

$obj = new cls1();
$obj->fun1();
$obj->fun2();

运行后输出:

This function is from Interface i1.
This function is from Interface i2.

注意看 implements i1, i2,这里用逗号分隔多个接口,这是PHP允许的。一个类可以在继承一个父类的同时实现多个接口,这个特性我后面会再细说。

现实场景:用接口规范“计算面积”的行为

接口真正派得上用场的场景,往往是一些不相关的类,需要共同遵守同一套行为规范。拿几何图形来举例就很直观:三角形和矩形,它们的数据结构和计算面积的方式不同,但它们都有“计算面积”这个能力。

第一步:定义 Shape 接口

我们先创建一个 Shape 接口,明确要求所有实现类都必须有一个 area() 方法,返回 float 类型:

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

这里没有写面积公式,因为接口不关心怎么算,它只关心你能不能算。

第二步: 类实现 Shape

 类需要两个属性:$base 和 $height,并在 area() 里按公式计算:

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() {
        return "Base: {$this->base}, Height: {$this->height}";
    }
}

第三步:Rectangle 类也实现 Shape

再写一个矩形类,属性和公式都不一样:

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() {
        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 接口的契约。这在团队协作中意义很大——你只要知道某个对象实现了 Shape 接口,就可以放心调用 area() 方法,不用管它内部是什么结构。这条经验来自我参与过的几个项目:接口让多人并行开发时的配合成本低了很多。

多继承的替代方案:实现多接口

PHP 不支持类似 class child extends parent1, parent2 这样的多继承语法。这在一些语言里是支持的,但PHP选择了用接口来解决这个问题——一个类只能继承一个父类,却可以同时实现多个接口。

这个设计取舍我个人认为是合理的,它规避了多继承带来的菱形继承问题(两个父类有同名方法该听谁的),同时又保留了“一个类拥有多种能力”的灵活性。

SmartPhone 示例:一个类实现三个接口

下面我们模拟一个智能手机的场景。手机要具备联网能力、导航能力、通话能力,这些能力之间并没有天然的父子关系,但它们可以各自定义成接口:

// 定义网络连接接口
interface NetworkConnect {
    public function connect(string $network): string;
}

// 定义导航接口
interface GPSNavigation {
    public function navigateTo(string $location): string;
}

// 定义通话接口
interface CallFunctionality {
    public function makeCall(string $number): string;
}

然后,SmartPhone 类一次性实现这三个接口:

class SmartPhone implements NetworkConnect, GPSNavigation, CallFunctionality {
    private $network = '';

    public function connect(string $network): string {
        $this->network = $network;
        return "Connected to network: $network";
    }

    public function navigateTo(string $location): string {
        if ($this->network === '') {
            return "Cannot navigate. No network connection.";
        }
        return "Starting navigation to: $location";
    }

    public function makeCall(string $number): string {
        if ($this->network === '') {
            return "Cannot make call. No network connection.";
        }
        return "Calling $number...";
    }

    public function deviceInfo(): string {
        return "Device: Galaxy X | OS: Android 14 | RAM: 8GB";
    }
}

测试一下:

$phone = new SmartPhone();
echo $phone->deviceInfo() . PHP_EOL;
echo $phone->connect("4G_Network") . PHP_EOL;
echo $phone->navigateTo("Central Park, NYC") . PHP_EOL;
echo $phone->makeCall("9876543210") . PHP_EOL;

输出:

Device: Galaxy X | OS: Android 14 | RAM: 8GB
Connected to network: 4G_Network
Starting navigation to: Central Park, NYC
Calling 9876543210...

这个例子展示了接口的一个重要用法:用能力组合替代多重继承SmartPhone 并不是“继承”了网络连接、导航和通话,而是“实现了”这些能力。这种语义上的区别,让代码的意图更加清晰——你在看代码时马上就能判断,哪些方法是这个类自己的,哪些方法是为了满足某个接口契约而写的。

什么时候该用接口而不是抽象类?

这是在做架构设计时经常遇到的问题。我个人遵循一条比较粗糙但有效的判断标准:

  • 如果多个类之间有清晰的“是一种(is-a)”血缘关系,并且有可以共享的属性或方法实现,那抽象类更合适。

  • 如果只是想定义一组“能做(can-do)”的行为规范,而且实现这些规范的类可能来自不同的家族,那接口就是正解。

举个例子:汽车和自行车都可以“移动”,但它们的内部结构不同。定义 Movable 接口让它们都实现 move() 方法就非常自然,不需要强行让它们继承同一个父类。

本节课程知识要点

  • 接口用 interface 关键词定义,内部方法不能有方法体,仅限方法签名。

  • 实现接口使用 implements 关键词,实现类必须提供接口中所有方法的具体定义。

  • 一个PHP类只能继承一个父类,但可以同时实现多个接口,用逗号分隔。

  • 接口方法隐式为 public,不可声明为 private 或 protected

  • 接口适合定义跨类族的能力契约,抽象类适合提供有共同血缘的代码框架,根据场景合理选择。

← PHP 抽象类:为什么不能直接用它创建对象? PHP 抽象类与接口:两个“模板”的较量 →
分享笔记 (共有 篇笔记)
验证码:
微信公众号