前言抽象类abstract class在 PHP 里是一个折中产物它一半是契约、一半是实现。用abstract修饰的类不能被实例化它可以包含没有方法体的抽象方法强制子类去实现也可以包含完整实现的普通方法供所有子类直接使用。关于它的误解集中在三点「抽象类里的方法必须全是抽象的」——不对。抽象类完全可以没有抽象方法一个方法都不抽象也是合法的此时abstract的作用只是「禁止直接new它」。「抽象类不能有构造器、不能有属性」——不对。抽象类可以有属性、常量、构造器、静态方法几乎所有普通类能有的它都有只差「不能被new」这一条。「抽象类和接口是二选一的对立面」——不准确。两者常常同时使用抽象类implements一个接口把接口里的一部分方法实现掉剩下的留给具体子类。本文按「语法限制 → 与接口的取舍 → 典型用法 → 实战代码」的顺序展开所有结论以 PHP 8 的语义为准。一、abstract的确切语法含义abstract可以用在两个位置修饰类abstract class Foo { }。唯一的效果是禁止new Foo()直接实例化会抛Error: Cannot instantiate abstract class Foo。修饰方法abstract public function area(): float;。它必须以分号结尾不能有方法体。子类必须实现它否则子类也得声明为abstract。几条硬性规则规则违反时的结果抽象方法不能是private致命错误Abstract function X::y() cannot be declared private含有抽象方法的类必须声明为abstract致命错误类含抽象方法必须声明为抽象或实现剩余方法抽象方法不能有方法体语法解析错误抽象类不能被final修饰致命错误abstract与final互相矛盾抽象类不能被newError: Cannot instantiate abstract class Xabstract方法最常见的是public和protected。用protected的场合是这个方法只是父类流程里的一个步骤不打算作为对外 API 暴露只希望子类去填实现。二、抽象类与接口的取舍这是初学者最容易卡住的地方。两者都能定义「必须被实现的方法」但限制完全不同特性抽象类接口属性可以有不能有只能有常量常量可以有可以有具体方法有方法体可以有不能有抽象方法可以有也可以一个都没有全部方法都是声明方法可见性public/protected只能是public一个类能继承/实现几个只能extends一个类implements可以列多个能否被new不能不能语义is-a是什么can-do能做什么选择标准一句话概括需要保存状态或提供公共实现就用抽象类只需要一份契约、而且一个类可能要满足多份契约就用接口。两者不是替代关系——一个类可以extends一个抽象类同时implements多个接口。抽象类还可以「部分实现」一个接口声明implements却只实现一部分方法剩下的抽象方法留给子类。这在框架里非常常见。三、典型用法模板方法抽象类最有价值的用法是模板方法Template Method把算法骨架放在父类把可变的步骤声明成抽象方法交给子类。?php // 适用于 PHP 8.1declare(strict_types1);abstract class Shape{public function __construct(protected readonly string $name){}// 抽象方法只给契约不给实现abstract public function area(): float;// 具体方法所有子类共用描述逻辑public function describe(): string{return sprintf(%s 的面积是 %.2f, $this-name, $this-area());}// 具体方法内部调用抽象方法构成「骨架」public function scaledArea(float $factor): float{if ($factor 0) {throw new InvalidArgumentException(缩放系数必须为正);}return $this-area() * $factor;}}final class Circle extends Shape{public function __construct(private readonly float $radius){parent::__construct(圆);}public function area(): float{return M_PI * $this-radius ** 2;}}final class Rect extends Shape{public function __construct(private readonly float $width,private readonly float $height,) {parent::__construct(矩形);}public function area(): float{return $this-width * $this-height;}}foreach ([new Circle(1.0), new Rect(3.0, 4.0)] as $shape) {echo $shape-describe(), PHP_EOL;}三点值得注意Circle和Rect的构造器里必须先parent::__construct(圆)把$name交给父类。因为子类自己定义了构造器父类构造器不会自动执行。describe()和scaledArea()是父类写死的骨架子类一行都不用重复它们调用$this-area()时会动态分派到子类的实现——这正是抽象类带来多态的路径。子类实现抽象方法时可见性必须与父类相同或更宽。父类写protected function foo()子类不能写成private。如果目标是 PHP 7.x把两块改掉即可构造器属性提升要展开成显式赋值readonly需要去掉并配上 getter 方法。四、不需要抽象方法也可以抽象一个常见的确认性问题抽象类里一个抽象方法都没有有意义吗有。下面两种场景都用得上?php // 适用于 PHP 7.1用到了 void 返回类型abstract class BaseController{protected function json(array $data, int $status 200): void{http_response_code($status);header(Content-Type: application/json; charsetutf-8);echo json_encode($data, JSON_UNESCAPED_UNICODE);}}它的用途是「强制通过继承来使用」调用方必须写一个具体的子类而不能直接new BaseController()。这在框架里用来禁止某一层被绕过。另一种是「延迟实现接口」抽象类声明implements一个接口只实现其中一部分方法剩下的留给子类。这样多个子类共享已实现的部分只在差异处各写各的。常见坑点❌ 在抽象类里调用还没有初始化完成的子类状态例如在父类构造器里调用抽象方法—— ✅ 此时子类的构造逻辑还没跑完属性可能未初始化构造器里只做不依赖子类实现的基础初始化。❌ 把抽象方法声明成private—— ✅ 致命错误抽象方法只能是public或protected因为子类必须能看见它。❌ 子类只实现了一部分抽象方法 —— ✅ 未实现完的子类自己也必须声明为abstract否则报致命错误。❌ 在非抽象类里写抽象方法 —— ✅ 报致命错误提示该类含抽象方法、必须声明为抽象或实现剩余方法。❌ 子类实现抽象方法时把可见性收窄父类public改成子类protected—— ✅ 致命错误实现时可见性只能相同或更宽。❌ 给抽象方法写上方法体 —— ✅ 语法错误抽象方法必须用分号结尾、没有花括号。❌ 想new一个抽象类来「临时用一下」—— ✅ 抛Error: Cannot instantiate abstract class先写一个具体子类。❌ 给抽象类加final—— ✅ 两者语义互斥PHP 会直接报致命错误要禁止继承就用final class但那样它就不能有未实现的抽象方法。总结要点说明abstract class不能被new可有属性、常量、构造器、具体方法abstract function无方法体以分号结尾子类必须实现可见性抽象方法只能是public/protected不能private实现要求全部抽象方法实现完子类才可以是具体类与接口的区别抽象类有属性、有实现、只能继承一个接口只有契约、可实现多个典型模式模板方法、延迟实现接口、强制通过继承使用结论把抽象类理解成「一个没写完的类」最贴切——它已经写好的部分被子类共享没写完的部分由子类填。设计时先问自己两句话这份逻辑需要保存状态吗需要就用抽象类这个契约有多个类要同时满足吗需要就再抽一个接口两者叠加使用就能既有共享实现、又保留多契约的灵活性。