在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。 -
接口适合定义跨类族的能力契约,抽象类适合提供有共同血缘的代码框架,根据场景合理选择。