抽象类与接口在语义上的区别与选用场景

解读

国内面试中,这道题出现的频率极高,通常放在“面向对象基础”之后,用来快速判断候选人是否真正“写业务”而非“背概念”。
面试官想听的不是“抽象类可以有方法体,接口不行”这种课本答案,而是:

  1. 你在真实项目里什么时候用抽象类、什么时候用接口;
  2. 能否结合 PSR、Composer 包设计、框架扩展点给出可落地的经验;
  3. 是否理解“语义”即“业务含义”,而不是语法差异。
    回答节奏建议“30 秒给定义,60 秒给区别,90 秒给场景,30 秒给代码级案例”,总时长控制在 3 分钟以内,留时间让面试官追问。

知识点

  1. 语义定位
    抽象类=“is-a”关系,强调“它是什么”;接口=“can-do”关系,强调“它能做什么”。
  2. 继承与实现
    PHP 单继承,多实现;抽象类占掉唯一继承位,接口可无限实现。
  3. 版本差异
    PHP 7 以后接口可定义常量、可声明返回类型;PHP 8 接口可定义默认方法(public function foo(){}),但属性仍不可。
  4. 框架惯例
    Laravel 核心契约(Contracts)全是接口,留给用户多重实现;Symfony 的 Bundle 基类是抽象类,强制复用骨架代码。
  5. 组合成本
    抽象类可带私有属性、构造函数、模板方法,减少重复代码;接口一旦新增方法,所有实现类必须同步修改,破坏 BC 的风险高。
  6. 国内规范
    阿里《PHP 开发手册》建议:对外可扩展能力用接口,对内代码复用优先用抽象类;禁止把接口后缀成 *Interface,抽象类后缀成 *Abstract,保持命名干净。

答案

“从语义上讲,抽象类描述的是‘一类事物的本质’,接口描述的是‘一组可被外部依赖的能力’。
在选用时我遵循三条经验:

  1. 如果我要沉淀公共属性、构造函数或模板流程,让子类少写代码,就用抽象类。例如电商 SKU 计价规则,把公共的折扣算法、数据库查询写在抽象类里,子类只需实现特定类目规则。
  2. 如果我要为第三方暴露扩展点,或者需要多重能力组合,就用接口。比如支付网关,微信、支付宝、银联各自实现 Payable 接口,订单服务只依赖接口而不关心实现;同时一个网关类还可以实现 Loggable、Refundable 等多个接口。
  3. 当两者都要时,先接口定义能力,再抽象类提供默认实现。Laravel 的 Queue 作业就是这样:框架提供 ShouldQueue 接口,用户可选择继承抽象类 Job 来复用序列化逻辑,也可以直接实现接口保持轻量。
    这样设计既符合 PSR 依赖倒置,也避免单继承带来的扩展瓶颈,上线两年来我们团队没出现过因为新增支付方式而改动核心订单代码的情况。”

拓展思考

  1. PHP 8 的 trait 与接口、抽象类如何三选一?
    答:trait 是“代码复制”,无语义,仅做水平复用;若发现 trait 里出现抽象方法,就应考虑升级成接口或抽象类,否则业务方无法通过类型提示完成依赖注入。
  2. 国内微服务场景下,接口爆炸怎么办?
    答:采用“标记接口 + 中间层”模式:对外只暴露一个标记接口,如 DomainEvent,内部用抽象类实现序列化、校验;各业务模块通过 Composer 包把自己的事件类注册到总线,避免一个 git 仓库里出现几百个接口文件。
  3. 面试反向提问
    可以问面试官:“咱们团队对契约编程的粒度是怎么把握的?有出现过因为接口变更导致大面积改动的 case 吗?”既展示深度,又能拿到内部痛点信息,为下一轮谈薪做铺垫。