Amazon Locker OOD(Python)


这个设计题 / 设计点的总结

Amazon Locker 题最核心的不是包裹本身,而是“尺寸匹配 + 最优 locker 选择 + pickup code 生命周期管理”。

题目含义

设计一个 locker 取件系统,支持分配柜子、生成取件码、用户取件后释放柜子。

时间复杂度

  • 分配 locker:当前线性实现通常是 O(total_lockers)
  • 取件:当前实现最坏是 O(total_lockers)

空间复杂度

主要取决于 locker 数量、包裹数量和活动取件码数量。

怎么想到

这题最稳的拆法不是直接从 Package 开始,而是先问:尺寸如何匹配、最小可适配柜子怎么选、谁负责站点级别资源管理。

示例 case

  • 场景:一个 MEDIUM 包裹进入某站点
  • 过程:系统先筛出能容纳它的空 locker,再选最小可适配的那个
  • 结果:返回一个 pickup code,用户取件后 locker 重新变空

常见 follow-up

  • 如果要支持过期未取包裹,应该加什么状态或服务?
  • 如果要支持按距离优先选站点,逻辑应放在哪?
  • 如果要支持一次性验证码,PickupCode 应如何扩展?

1. 面试里先澄清

建议先讲清楚范围:

// 设计一个 Amazon Locker 取件系统
// 支持包裹分配柜子、取件、释放柜子
// 暂不考虑真正物流运输过程

2. 核心对象

  • Package
  • LockerSize
  • Locker
  • LockerLocation
  • PickupCode
  • LockerService

3. 核心建模点

这题的关键不是“包裹”本身,而是:

// 包裹尺寸如何匹配柜子
// 如何找到最合适的 locker
// 如何生成 pickup code
// 取件后如何释放 locker

4. 职责划分

Package

// 记录包裹信息和尺寸

Locker

// 表示单个柜子
// 管理占用状态

LockerLocation

// 表示一个站点
// 管理该站点下所有 locker

LockerService

// 负责分配 locker
// 生成取件码
// 处理取件逻辑

5. Python 代码骨架

from __future__ import annotations

from dataclasses import dataclass
from enum import Enum
from typing import Dict, List, Optional
import uuid


class LockerSize(Enum):
    SMALL = 1
    MEDIUM = 2
    LARGE = 3


@dataclass
class Package:
    package_id: str
    size: LockerSize
    customer_id: str


@dataclass
class PickupCode:
    code: str
    package_id: str
    locker_id: str


class Locker:
    def __init__(self, locker_id: str, size: LockerSize) -> None:
        self.locker_id = locker_id
        self.size = size
        self.package: Optional[Package] = None

    def is_free(self) -> bool:
        return self.package is None

    def can_fit(self, package: Package) -> bool:
        return self.is_free() and self.size.value >= package.size.value

    def assign_package(self, package: Package) -> bool:
        if not self.can_fit(package):
            return False
        self.package = package
        return True

    def release(self) -> Optional[Package]:
        package = self.package
        self.package = None
        return package


class LockerLocation:
    def __init__(self, location_id: str, lockers: List[Locker]) -> None:
        self.location_id = location_id
        self.lockers = lockers

    def find_best_locker(self, package: Package) -> Optional[Locker]:
        candidates = [locker for locker in self.lockers if locker.can_fit(package)]
        if not candidates:
            return None
        return min(candidates, key=lambda l: l.size.value)


class LockerService:
    def __init__(self, locations: List[LockerLocation]) -> None:
        self.locations = locations
        self.codes: Dict[str, PickupCode] = {}

    def assign_locker(self, package: Package) -> Optional[PickupCode]:
        for location in self.locations:
            locker = location.find_best_locker(package)
            if locker and locker.assign_package(package):
                code = PickupCode(
                    code=str(uuid.uuid4())[:8],
                    package_id=package.package_id,
                    locker_id=locker.locker_id,
                )
                self.codes[code.code] = code
                return code
        return None

    def pickup(self, pickup_code: str) -> Optional[Package]:
        code = self.codes.get(pickup_code)
        if code is None:
            return None

        for location in self.locations:
            for locker in location.lockers:
                if locker.locker_id == code.locker_id:
                    package = locker.release()
                    del self.codes[pickup_code]
                    return package
        return None

6. 面试时怎么讲

// 我把 locker 站点和单个 locker 分层管理
// 单个 locker 只负责占用状态
// LockerLocation 负责站点内的资源选择
// LockerService 负责全局分配和取件流程

7. 可以主动讲的扩展点

  • 支持就近站点分配
  • 支持过期未取包裹
  • 支持一次性验证码
  • 支持短信 / 邮件通知
  • 支持不同站点库存统计

8. 常见错误

  • 不做“最小可适配 locker”选择
  • 站点和单柜职责不分
  • pickup code 和 locker 占用状态绑定不清楚
Cassie Liang · Study Notes GitHub