第3章:面向对象设计与设计模式 — 高频问题

核心命题:继承 vs 组合、单例模式的正确使用、策略模式的业务实战。面试官关注你对“可扩展性”和“权衡”的理解,回答框架应为 “UML + 代码 + 权衡”。
① 理解问题场景 ② 选择设计模式/原则 继承 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替代策略类简化代码?
• 策略模式与模板方法模式的区别?
避坑提醒❌ 不要强行给只有一个实现就套策略模式;❌ 避免策略类包含多个职责;✅ 推荐结合工厂模式或注册表来管理策略选择。
📘 公司侧重点提示(可扩展性理解): 面试字节、美团等大厂时,回答后最好补充一句可扩展性实战:“在公司早期用继承快速迭代,当发现新需求导致继承层次爆炸时,我们重构为组合+策略模式,每个新行为只需新增一个模块或类,不修改现有代码,业务方半个月就能接入。” 这是面试官最想听到的“权衡落地”视角。