Menu

PHP 抽象类:抽象方法与使用场景

PHP 抽象类用 abstract class Name 声明,不能被实例化:它的存在就是为了被继承。它可以包含属性、构造函数和完整的方法,还可以包含没有方法体的 abstract 方法,每个子类都必须实现这些方法。

本页包含可运行的编辑器。编辑、运行,立即查看输出。

PHP 抽象类用 abstract class Name 声明,不能用 new 创建。它是其他类的基类:可以有属性、构造函数和完整的方法,还可以有没有方法体的 abstract 方法,每个子类都必须实现它们。

describe() 只在 Shape 中写了一次,它调用的 area() 由每个子类提供。抽象类保证了这个方法一定存在,所以 describe() 可以放心地依赖它。

不能用 new 创建抽象类

抽象类是故意不完整的:Shape::area() 没有方法体,所以一个普通的 Shape 对象没有可运行的东西。PHP 拒绝创建它,并抛出一个 Error:

抽象方法的规则

抽象方法只有签名,后面跟一个分号,没有方法体。每个子类都必须用兼容的签名实现它,可见性要相同或更宽:abstract protected 方法在子类中可以变成 protected 或 public,但永远不能是 private。只要一个类有一个抽象方法,它本身就必须声明为抽象类。

如果子类漏掉了某个抽象方法,脚本会在任何代码运行之前就停下来,除非这个子类也声明为抽象类,把这个方法留给它自己的子类去实现:

PHP Fatal error:  Class Square contains 1 abstract method and must therefore be declared abstract or implement the remaining methods (Shape::area) in /home/index.php on line 3

模板方法模式

编写抽象类最常见的原因:一组固定顺序的步骤,其中少数几步各不相同。基类用一个 final 方法掌控整个顺序,子类负责填写各个步骤。下面同一份报表分别以纯文本和 HTML 表格的形式输出:

运行它,“页面”标签页会在纯文本版本上方显示 HTML 表格。open() 和 close() 有默认的方法体,所以只有 HtmlReport 需要重写它们;row() 是抽象的,因为它没有合理的默认实现。把 render() 设为 final,可以防止子类改变步骤的顺序。

抽象类可以实现接口

抽象类可以声明 implements,并把接口中的部分方法留给它的子类实现。这是把接口(类型)和基类(共享代码)搭配使用的标准方式:

代码的类型声明用的是接口 Cache,所以一个无法继承 BaseCache 的类(因为它已经继承了别的类)仍然可以直接实现 Cache。

什么时候用抽象类,什么时候不用

当一组类共享真正的代码时,就用抽象类:属性、构造函数、对它们都一样的方法。如果基类里除了抽象方法什么都没有,它其实就是一个伪装的接口,而接口是更好的选择,因为一个类可以实现多个接口,却只能继承一个类。如果你只是想在互不相关的类之间共享几个辅助方法,就用 trait,而不是硬把它们塞到同一个父类下面。

写 abstract class 之前先做个简单的测试:每个子类真的都是基类的一种吗(Circle 是一种 Shape)?如果诚实的回答是“它能做这件事”,而不是“它是其中一种”,那就写接口。

常见问题

PHP 中的抽象类是什么?

用 abstract class Name 声明、不能用 new 创建的类。它是其他类的基类:可以包含属性、构造函数和普通方法,也可以声明每个子类都必须实现的 abstract 方法。

PHP 中的抽象方法是什么?

用 abstract 声明、没有方法体的方法:abstract protected function render(array $rows): string;。包含抽象方法的类本身必须声明为 abstract,每个非抽象子类都必须定义一个签名兼容的方法。

PHP 抽象类可以有构造函数吗?

可以。你不能对抽象类调用 new,但创建子类对象时它的构造函数会运行:要么自动运行(如果子类没有构造函数),要么通过 parent::__construct(...) 调用。

PHP 中可以实例化抽象类吗?

不可以。对抽象类执行 new Shape() 会抛出 Error:Cannot instantiate abstract class Shape。请改为创建一个具体子类的对象,例如 new Circle(2)。

PHP 中什么时候应该使用抽象类?

当几个相关的类共享真正的代码(属性、构造函数、公共方法),只在少数几个步骤上不同时。把共享的代码放进抽象类,把不同的步骤声明为抽象方法。如果没有共享代码、只需要一份约定,就用接口。

Coddy 编程语言插图

和 Coddy 一起学编程

开始