第3章:面向对象设计与设计模式 — 高频问题
核心命题:继承 vs 组合、单例模式的正确使用、策略模式的业务实战。面试官关注你对“可扩展性”和“权衡”的理解,回答框架应为 “UML + 代码 + 权衡”。
扫读提示:回答“继承与组合”必提“多继承问题”与“组合优于继承”;答“单例”必提线程安全与序列化;答“策略”必提开放封闭原则与消除条件判断。
📌 面试问题 1:继承与组合的区别?何时使用组合而非继承?
| 考察点 | 理解类间关系的本质(is-a vs has-a);多重继承的菱形问题;可扩展性与耦合度权衡。 |
|---|---|
| 回答框架 | ①用一句话定义两种关系 → ②画简单UML(继承用空心三角,组合用实心菱形) → ③举Ruby代码片段 → ④权衡:继承破坏封装、组合更灵活但需额外委托。 |
| 示例回答 | “继承表示is-a,如Car < Vehicle;组合表示has-a,如Car has-a Engine。Ruby中单继承避免菱形问题,但继承层级过深会僵化。我倾向用组合+委托,例如实现可飞行与可驾驶行为,用模块include注入,再用委托透明访问。” |
| 可能追问 | • Ruby的include与prepend本质是继承还是组合? • 如何用组合模拟多重继承? • 什么时候继承不可替代? |
| 避坑提醒 | ❌ 不要只背“组合优于继承”而不解释场景;❌ 避免说出“Ruby不用继承”这种结论;✅ 指出“行为复用用组合,本质标识用继承”。 |
📌 面试问题 2:Ruby中实现线程安全单例模式的几种方式?注意事项?
| 考察点 | 线程安全、延迟加载、序列化破坏、Ruby标准库Singleton模块的源码理解。 |
|---|---|
| 回答框架 | ①先给出最简洁实现(require 'singleton') → ②画出UML类图(私有构造、单一实例) → ③写出手动实现的双检锁代码 → ④权衡:全局状态测试困难 vs 资源节约。 |
| 示例回答 | “标准做法:require 'singleton' + include Singleton,它自动创建instance类方法并加锁。若手动实现双检锁:@@instance ||= begin ... end配合Mutex。注意Marshal.load会破坏单例,需覆盖_dump和_load。” |
| 可能追问 | • Singleton模块如何保证线程安全? • Rails中如何实现应用级别单例? • 单例模式与全局变量的本质区别? |
| 避坑提醒 | ❌ 不要忽略序列化/反序列化破坏单例;❌ 避免在测试中直接依赖单例(难Mock);✅ 建议用依赖注入包装单例实例。 |
📌 面试问题 3:策略模式在Ruby中的典型实现?相比if-else的优势?
| 考察点 | 开放封闭原则、消除条件分支、对象组合灵活性、策略注册与选择机制。 |
|---|---|
| 回答框架 | ①定义策略接口(Ruby中常用模块或鸭子类型) → ②画出UML(策略Context ↔ 策略接口 → 具体策略) → ③展示使用case/when的糟糕旧代码 vs 策略类重构后的代码 → ④权衡:增加类数量,但扩展性好、测试独立。 |
| 示例回答 | “定义一个ReportFormatter接口,CsvFormatter和HtmlFormatter实现format(data)。Context传入formatter实例,替换掉庞大的if-else。优势:新格式只需新建类,符合OCP;劣势是多出类文件,但Rails中可放app/services由自动加载管理。” |
| 可能追问 | • 如何动态选择策略而不在Client写if? • 如何用Proc/Lambda替代策略类简化代码? • 策略模式与模板方法模式的区别? |
| 避坑提醒 | ❌ 不要强行给只有一个实现就套策略模式;❌ 避免策略类包含多个职责;✅ 推荐结合工厂模式或注册表来管理策略选择。 |
📘 公司侧重点提示(可扩展性理解): 面试字节、美团等大厂时,回答后最好补充一句可扩展性实战:“在公司早期用继承快速迭代,当发现新需求导致继承层次爆炸时,我们重构为组合+策略模式,每个新行为只需新增一个模块或类,不修改现有代码,业务方半个月就能接入。” 这是面试官最想听到的“权衡落地”视角。