OfferForge 导师 Dr. W. — 博士毕业,前 FAANG Senior MLE。研究方向为大规模分布式机器学习系统,曾在顶级国际学术会议发表多篇论文。负责团队学术方向把控与 MLE 面试辅导。
OfferForge 导师 Dr. S. — 前大厂 Senior SDE,精通分布式系统与大规模数据处理。曾在顶级技术会议发表多篇技术报告,专注于系统可扩展性与可靠性。负责课程体系设计与 System Design 面试辅导。
OfferForge 导师 Dr. L. — 前大厂 Senior SDE,深耕分布式系统与微服务架构。在服务治理、事件驱动架构方面有丰富实战经验。负责求职策略制定与 SDE 面试辅导,已帮助多位候选人斩获 Senior 级别 Offer。
第二轮面试中,看到 “Pizza Price Calculator” 的第一眼,我就知道这不是算法题,而是一道考察代码设计和可扩展性的经典面试题。题目看起来很直白:不同的 pizza base、不同的 topping 各自有价格,不同 size 对应一个倍率系数,比如 crust base 是 3 美元、cheese topping 是 2 美元、medium size 要乘以 1.5 倍,最终价格公式是 (base + toppings) × multiplier。顾客下单时必须指定 size、选择一个 base,并可添加 0 个或多个 topping。光从功能角度实现一个计算器非常容易,但要写出一份能在面试中拿高分的代码,就得体现出我对对象建模、扩展性和设计模式的理解。
我首先想到的,是不能让 Pizza 类同时负责存储属性和计算价格。那样虽然短期内可行,但职责太重,后续如果要加入折扣、会员价、节日优惠,就得改核心类逻辑,这显然违反开闭原则。更好的方式是让 Pizza 类只承担“描述性”的角色,它只表示这个 pizza 有什么 base、多大尺寸、包含哪些 toppings,而不直接计算价格。
价格计算应该交给一个独立的模块,比如一个价格计算器(PriceCalculator)。这样一来,Pizza 类不会关心价格规则,而 PriceCalculator 可以灵活扩展。比如我可以创建一个基础计算器、一个带优惠的计算器、一个促销计算器,它们都基于同一接口实现不同逻辑,而不需要修改核心结构。这种“逻辑与数据分离”的设计体现了 策略模式(Strategy Pattern) 的思想,也是很多面试官非常看重的架构意识。
在 follow-up 阶段,题目会进一步要求支持顾客下多个订单,并且订单中可能包含 pizza、饮料(drink)、甜点(dessert)等不同类型的商品。这时如果系统仍然只围绕 pizza 设计,就会非常僵化。于是我换了个角度去看:pizza、drink、dessert 本质上都是“可以计算价格的商品”。既然如此,那我就不该让系统依赖某个具体商品,而该抽象出一个更通用的概念——订单项(OrderItem)。
所有商品都可以实现同样的接口,比如都有 getPrice() 的行为。这样,订单(Order)只需聚合若干个 OrderItem,然后计算总价即可。它不关心每个商品的内部细节,只要知道它们都能自己报出价格就行。这种结构正是 组合模式(Composite Pattern) 的体现:每个订单项既可以是独立对象,也可以是组合结构。未来要扩展新的商品类别,只需新增类实现相同接口,原有系统完全不用改。