代表性面试主题

编码面试:设计一个餐厅桌位预约模块

编程题困难
Offer.cc 编辑团队发布 更新

题干

请设计一个餐厅桌位预约模块:顾客按人数和时间查询可用桌位、创建或取消预约;前台可以安排临时到店顾客,系统不能让同一张桌子在重叠时段被重复分配。如何组织类、接口和并发边界?

题干与适用场景

这是一道低层设计与编码题,重点是把预约、桌位、时间区间和等候队列转化为职责清晰的对象。公开面经中出现过餐厅预约设计,OOD 方法也强调先澄清范围、列出需求,再确定对象与接口。题目假设单店、固定营业时间和内存内模块;若面试官要求持久化,再补充存储和并发策略。

面试官考察点

  • 能否把 RestaurantTableReservationWaitlist 和分配策略分离。
  • 能否正确处理半开时间区间、桌位容量、拼桌规则和取消后的释放。
  • 能否设计状态机、幂等请求和并发下的防重复分配。
  • 能否用测试覆盖边界条件,并解释复杂度与可扩展点。

回答前需要澄清的问题

先确认预约是指定桌位还是只指定人数,是否允许拼桌;预约时长和清桌缓冲是多少;是否支持修改、迟到、爽约和临时到店;等候队列按先到先得还是按人数与时间匹配;一个请求是否可能重试。还要确认时间精度、时区、单店还是多店,以及是否要求跨进程持久化。

30 秒回答框架

我会先定义不可变的 TimeRange 和预约状态,再让 AvailabilityService 负责冲突查询,让 TableAllocator 负责按容量和策略选桌,ReservationService 负责编排创建、取消和通知,Waitlist 单独管理候补。创建时用幂等键和同一临界区检查“可用再占用”,取消只允许合法状态转换并释放桌位。最后用边界测试验证相邻时段、并发请求、重复取消和候补晋级。

分步骤深入解答

1. 建模时间、桌位和预约状态

用半开区间 [start, end) 表示预约,只有 start < end 才有效;两个区间在 a.start < b.end && b.start < a.end 时冲突。Table 保存容量、编号和可用状态,Reservation 保存顾客、人数、区间、桌位和状态。状态可为 HELDCONFIRMEDSEATEDCANCELLEDNO_SHOW,非法转换直接拒绝。

2. 让分配策略可替换

TableAllocator 接收候选桌位和预约需求,默认选择“容量最小但足够”的桌,减少大桌浪费;也可以注入“优先连桌”或“无障碍桌优先”策略。分配器不负责写预约,避免把搜索、决策和状态持久化耦合到一个巨大类中。

3. 实现冲突查询和创建

查询按餐厅、时间区间和桌位索引过滤,再由分配器选择。创建流程先校验输入、计算缓冲后的区间、生成幂等键,然后在同一锁或事务边界内重新读取可用桌并写入预约;不能依赖查询阶段的旧结果。示意代码应把核心不变量写出来:同一桌的重叠 CONFIRMED 预约数量必须为零。

text
create(request, key):
  if idempotency.exists(key): return idempotency.result(key)
  range = TimeRange(request.start, request.end + cleanupBuffer)
  lock(restaurantId, range):
    table = allocator.choose(availableTables(range), request.partySize)
    if table is null: return WAITLISTED
    reservation = Reservation.confirm(request, table, range)
    store(reservation)
    idempotency.save(key, reservation.id)
    return reservation

4. 处理取消、迟到和候补晋级

取消只允许 HELDCONFIRMED 进入 CANCELLED,重复取消返回同一个结果,不重复触发通知。到店后转为 SEATED,超过规则时间可转 NO_SHOW 并释放桌位。释放事件交给 WaitlistMatcher,按等待时间、人数适配和优先级挑选候补,再走同一创建临界区,防止候补与新预约竞争时双重分配。

5. 测试并说明复杂度

测试相邻区间 [19:00,20:00)[20:00,21:00) 不冲突,反向输入被拒绝,缓冲时间会扩大冲突范围,重复幂等键不新增预约,取消后候补最多晋级一次,并发创建只能成功一个。若每张桌按时间排序保存预约,单桌冲突查询可为 O(log n + k);候选桌为 m 张时,选择和写入约为 O(m log n),可再按容量分桶或使用区间索引。

高质量示范回答

我会用半开 TimeRange、明确的预约状态机和可替换分配策略建模。AvailabilityService 负责按餐厅、时间和桌位过滤,TableAllocator 选择容量最小但足够的桌,ReservationService 在同一锁或事务边界中重新检查并写入,使用幂等键防止重试重复创建。取消、迟到和爽约通过合法状态转换释放桌位,候补匹配随后复用同一临界区。测试覆盖相邻区间、清桌缓冲、重复取消、并发创建和候补竞争,并说明按桌排序索引后的复杂度。

常见错误

  • 把所有逻辑塞进 Restaurant,对象职责和替换策略不清楚。
  • 用闭区间判断时间,导致相邻预约被错误判为冲突。
  • 先查询可用桌,再异步写入,没有在写入边界重新检查。
  • 取消接口不是幂等的,重试会重复释放桌位或发送通知。
  • 只实现顾客预约,忽略前台 walk-in、迟到、爽约和候补。
  • 只给类图,不写不变量、边界测试和复杂度。

追问及应对

如果允许拼桌,怎么扩展?

把分配结果从单个 tableId 改成有序桌位集合,并增加“组合后容量足够、桌间可连接、组合不与其他预约冲突”的约束。策略接口保持不变,新增 ComposableTableAllocator

跨进程部署时如何防止重复预订?

把冲突约束下沉到持久化层,例如按桌位和时间建立可验证的锁或事务约束;应用层锁只作为减少竞争的优化,不能作为唯一正确性保障。

用户连续点击创建怎么办?

要求客户端提供幂等键,服务端按餐厅和用户维度保存结果。相同键返回原预约,不同键仍需经过统一冲突检查,不能用用户限流替代业务幂等。

如何优化热门晚餐时段?

按餐厅和时间分片,缓存只用于查询提示,最终创建仍走强一致临界区。可以预计算容量桶和候补候选,但必须在写入时验证版本,避免缓存过期造成超卖。

预约取消后通知失败怎么办?

状态变更与 outbox 事件在同一事务中提交,通知消费者幂等重试。桌位释放不应等待短信成功,通知失败进入重试和人工可见的死信队列。

公开来源

同类题目

相关面试工具

用 Screenshot 处理算法题

截图题目后,按顺序看约束、解法、代码、边界条件和复杂度。

查看工具