面试风向:大厂面向对象考察已从“背诵概念”升级为“设计决策 + 代码演进能力”。本章聚焦 OOP 三大核心:抽象建模、SOLID 原则、模式选型与重构。
本章关键词:抽象 vs 接口选择、策略模式重构 if-else、观察者模式解耦、工厂模式与 DI、SOLID 违反场景
1. 如何在 PHP 中正确选择抽象类(abstract class)和接口(interface)?
| 考察点 | 面向对象设计能力、对 IS-A vs CAN-DO 的理解、实际项目中的权衡 |
| 回答框架 |
1. 指出核心区别:抽象类定义共性 + 部分实现;接口定义契约(纯抽象) 2. 结合业务举例:父类「支付处理」抽象类提供日志/事务骨架;接口「Payable」定义 charge()、refund() 3. 强调 PHP 单继承限制下优先用接口组合 4. 提及现代框架(Laravel Contract)的应用 |
| 示例回答 | “我倾向把‘不变流程’放抽象类,如订单支付模板;把‘行为契约’放接口,如可退款。项目中有多个支付渠道,用接口统一 charge(),再用抽象类复用验签逻辑,这样方便做 mock 测试。” (65字) |
| 可能追问 | 如果抽象类里全是抽象方法,那和接口有什么区别?——功能等价,但接口可被多实现,抽象类占用单继承位。 |
| 避坑提醒 | ❌ 不要背诵语法表,要结合“演进需求”——当未来需要加入新渠道时,哪种设计改动成本更低。 |
2. 一个订单结算方法里存在大量 if-else 判断不同优惠类型,如何用设计模式重构?
| 考察点 | 策略模式实践、开闭原则、代码可测试性 |
| 回答框架 |
1. 先分析 if-else 的变体是什么(优惠计算算法) 2. 抽象出策略接口(CouponStrategy)含 calculate(Order $order) 3. 每个优惠类实现该接口,用容器或工厂注册 4. 结算上下文类接受策略实例,循环执行 5. 同样可扩展:组合策略(Composite)、责任链(多级优惠叠加) |
| 示例回答 | “曾重构满减/折扣/红包混合结算:定义 Strategy 接口,每个优惠类实现;用数组收集所有可用策略,foreach 调用计算。新增‘拼团价’只需加一个类,无需改核心逻辑,同时单测覆盖每个策略类。” (73字) |
| 可能追问 | 策略之间需要依赖执行顺序或互斥时怎么处理?——引入责任链模式,或抽象出排序逻辑(PriorityAware)。 |
| 避坑提醒 | ⚠️ 避免过度设计——如果优惠类型固定且很少变化,保留 switch 但用 enum + match 更简洁。 |
3. 你如何理解依赖倒置原则(DIP)?在 PHP 框架中如何体现?
| 考察点 | SOLID 理解深度、框架源码阅读能力、解耦意识 |
| 回答框架 |
1. 一句话定义:高层模块不应依赖低层模块,两者都应依赖抽象 2. 举反例:Controller 直接 new UserModel() → 紧耦合,难测试 3. 正例:依赖注入 + 接口(UserRepositoryInterface) 4. 关联 Laravel 服务容器:bind()、interface 绑定具体类 5. 进阶说明:配置在外部(config/app.php)而非代码硬编码 |
| 示例回答 | “在 Laravel 中,UserController 构造注入 UserRepositoryInterface,容器自动决议具体类。这样切换数据源(从 MySQL 到 Redis)只需新增实现并改容器绑定,业务层零改动。这是 DIP 的典型应用。” (76字) |
| 可能追问 | 如果接口只有一个实现类,还有必要抽象吗?——有必要,测试时可 mock,且为未来扩展留接口位。 |
| 避坑提醒 | ❌ 不要只讲理论,必须结合 Laravel ServiceProvider 或 Yii 的依赖注入配置。 |
项目案例:高并发优惠券计算引擎
| 项目名称 | 秒杀优惠券叠加计算引擎 |
|---|---|
| 业务背景 | 大促期间用户可同时使用满减券、折扣券、红包,需按优先级顺序计算最终实付金额,并保证 QPS 5000+ 下的计算性能。 |
| 规模指标 | 日订单 100万;峰值 QPS 8000;优惠券策略 12 种;单次计算耗时要求 < 10ms(含 Redis 读取)。 |
| 技术栈 | PHP 8.1 + Laravel 9 + Redis + MySQL + RabbitMQ |
| 核心难点 |
1. 优惠券叠加规则复杂(互斥、排序、最大折扣封顶) 2. 每次计算需从 Redis 读取多个策略配置,网络开销大 3. 新活动频繁上线,需要可插拔策略而不改核心流程 |
| 面试高频追问 |
• 为什么选择策略模式而不是责任链? • 如何保证策略执行顺序?(配置排序 + 依赖注入优先级) • 缓存策略结果是否可行?(用 Rule Engine 预计算) • 单元测试如何覆盖所有组合?(参数化测试 + 工厂类生成场景) |
| 可迁移回答话术 | “我们采用策略模式 + 工厂注册表管理所有优惠策略。每个策略类实现 `calculate(Order $order, ApplyContext $ctx)` 接口,通过管道依次执行。新增策略只需在配置数组添加类名,并实现接口即可,完全符合开闭原则。同时用 Redis 缓存每个策略的配置项,减少数据库查询。单测使用 Mockery 模拟复杂组合,覆盖边界情况。” |
内化点:将设计模式与“可测试性”“可扩展性”挂钩,大厂面试官更看重你对“未来变化”的预见力。