- 示例工程
【免费下载链接】awesome-low-level-design
Learn Low Level Design (LLD) and prepare for interviews using free resources.
导读
本文以 awesome-low-level-design 仓库中 Java 版停车场系统实现 为主体,完整讲解一个支持多楼层、多种车型、多种车位类型、停车出票、自动计费与车位分配策略的停车场管理系统(Parking Lot System)的低层设计(LLD)全过程。读完本文,你将掌握:核心实体(ParkingLot / ParkingFloor / ParkingSpot / ParkingTicket)的职责划分、Singleton 与 Strategy 两种设计模式在真实业务中的落地方式,以及如何通过策略接口低成本扩展新的计费规则与车位分配算法。
问题陈述与需求分析
仓库内的问题描述文档 problems/parking-lot.md 给出了该系统的需求基线,而 Java 实现目录下的 README 对其做了细化,综合可归纳为:
- 多楼层:停车场可以包含多个楼层(floor)。
- 多车位类型:每个楼层拥有多个停车位,车位按尺寸分为 SMALL / MEDIUM / LARGE(对应代码中的
VehicleSize枚举)。 - 多车型支持:系统需支持轿车(Car)、摩托车(Bike)、卡车(Truck)等不同车型(见
vehicle/包)。 - 出票:车辆入场停放后生成一张停车票据(ParkingTicket)。
- 离场计费:车辆离场时释放车位并计算停车费用。
- 灵活的计费策略:支持不同的费用计算策略(见
strategy/fee/)。 - 车位分配:为车辆分配正确类型且"最近/最合适"的车位。
- 可扩展性:易于新增车型、车位类型或计费策略,且不修改既有核心代码。
该实现还额外关注了并发安全:停车场、楼层、车位、活跃票据集合均使用并发容器或synchronized关键区保护,满足"多入口多出口并发访问"的需求(problems/parking-lot.md 第 6 条)。
核心实体与源码解析
系统按职责划分为四个核心实体类与两个支撑包,全部位于 solutions/java/src/parkinglot/:
parkinglot/ ├── ParkingLot.java # 门面/单例:管理整体流程 ├── ParkingLotDemo.java # 可运行的演示程序 ├── entities/ │ ├── ParkingFloor.java # 楼层 │ ├── ParkingSpot.java # 车位 │ └── ParkingTicket.java # 停车票据 ├── strategy/ │ ├── fee/ # 计费策略(Strategy) │ └── parking/ # 车位分配策略(Strategy) └── vehicle/ # 车型抽象与实现 ├── Vehicle.java VehicleSize.java ├── Bike.java Car.java Truck.java1. ParkingLot:全局门面与单例
ParkingLot.java 是整个系统的入口类,同时演示了懒加载 + 双重检查的单例模式:
private static ParkingLot instance; public static synchronized ParkingLot getInstance() { if (instance == null) { instance = new ParkingLot(); } return instance; }私有构造函数中设置了两套默认策略,并把活跃票据放在ConcurrentHashMap中以支持并发访问:
private ParkingLot() { this.feeStrategy = new FlatRateFeeStrategy(); this.parkingStrategy = new BestFitStrategy(); this.activeTickets = new ConcurrentHashMap<>(); }对外暴露的核心方法(与 README 中列出的方法一一对应):
addFloor(ParkingFloor floor):添加楼层。parkVehicle(Vehicle vehicle):委托给当前ParkingStrategy查找车位,找到则占位、出票并登记到activeTickets(以车牌号为 key),返回Optional<ParkingTicket>;找不到返回Optional.empty()。unparkVehicle(String licenseNumber):按车牌号(README 中写为 ticketId,实际实现以车牌号为索引)取出票据,记录离场时间、释放车位,并调用FeeStrategy.calculateFee(ticket)计算费用。setFeeStrategy / setParkingStrategy:运行时热切换计费与分配策略,这正是策略模式的用武之地。
从源码结构看,README 中
unparkVehicle(String ticketId)的签名与实现存在差异:当前实现以licenseNumber作为活跃票据的查找键,这是阅读与使用源码时需要注意的细节。
2. ParkingFloor:楼层内的车位管理
ParkingFloor.java 持有floorNumber与一个ConcurrentHashMap<String, ParkingSpot>车位集合,提供:
addSpot(ParkingSpot spot):以spotId为 key 登记车位。findAvailableSpot(Vehicle vehicle):过滤出"空闲且能容纳该车辆"的车位,并按车位尺寸 ordinal 升序排序取第一个,即楼层内天然执行"最贴合"分配:
public synchronized Optional<ParkingSpot> findAvailableSpot(Vehicle vehicle) { return spots.values().stream() .filter(spot -> !spot.isOccupied() && spot.canFitVehicle(vehicle)) .sorted(Comparator.comparing(ParkingSpot::getSpotSize)) .findFirst(); }displayAvailability():按VehicleSize分组统计各尺寸空闲车位数,便于演示与实时信息展示。
3. ParkingSpot:车位状态机
ParkingSpot.java 记录spotId、spotSize、占用标志与当前停放车辆。关键的车型-车位匹配规则canFitVehicle如下:
switch (vehicle.getSize()) { case SMALL: return spotSize == VehicleSize.SMALL; case MEDIUM: return spotSize == VehicleSize.MEDIUM || spotSize == VehicleSize.LARGE; case LARGE: return spotSize == VehicleSize.LARGE; }即:小型车只能停小型位;中型车可停中型或大型位(向下兼容);大型车只能停大型位。所有状态变更方法(parkVehicle/unparkVehicle/isAvailable)均加synchronized,保证同一车位的并发互斥。
4. ParkingTicket:停车凭证
ParkingTicket.java 在车辆成功停放时生成,字段包括:
ticketId:由UUID.randomUUID().toString()生成的全局唯一票据号。vehicle/spot:车辆信息与所停车位。entryTimestamp:入场时间戳(new Date().getTime()),票据创建时固化。exitTimestamp:由setExitTimestamp()在离场时写入,作为计费时长依据。
5. 车型体系:Vehicle 抽象基类 + VehicleSize 枚举
Vehicle.java 是抽象基类,持有不可变的licenseNumber与size;三个子类只负责在构造时声明自身尺寸:
public class Bike extends Vehicle { public Bike(String licenseNumber) { super(licenseNumber, VehicleSize.SMALL); } } public class Car extends Vehicle { public Car(String licenseNumber) { super(licenseNumber, VehicleSize.MEDIUM); } } public class Truck extends Vehicle { public Truck(String licenseNumber) { super(licenseNumber, VehicleSize.LARGE); } }VehicleSize.java 定义SMALL / MEDIUM / LARGE三个枚举值,其ordinal()还被BestFitStrategy用于比较车位贴合程度。
策略模式(一):计费策略
计费模块遵循策略模式,接口与实现位于 strategy/fee/:
public interface FeeStrategy { double calculateFee(ParkingTicket parkingTicket); }仓库内置两种实现,可通过parkingLot.setFeeStrategy(...)随时切换:
| 策略 | 计费规则 | 说明 |
|---|---|---|
| FlatRateFeeStrategy | 统一费率RATE_PER_HOUR = 10.0,计费小时 =(时长/3600000) + 1,费用 = 小时 × 10 | 简单统一价,按整小时向上取整 |
| VehicleBasedFeeStrategy | 按车型分档:SMALL 10 / MEDIUM 20 / LARGE 30 元每小时 | 计费小时同上,费率取HOURLY_RATES.get(vehicle.getSize()) |
两种实现均使用(duration / (1000 * 60 * 60)) + 1将毫秒时长折算为"至少 1 小时"的整小时数——即使停车不足 1 分钟也按 1 小时计费,这是实际停车系统中常见的取整口径,也是扩展自定义策略时可直接复用的模板。
策略模式(二):车位分配策略
车位分配同样抽象为策略接口 ParkingStrategy.java:
public interface ParkingStrategy { Optional<ParkingSpot> findSpot(List<ParkingFloor> floors, Vehicle vehicle); }仓库内置三种分配算法,覆盖了最常见的停车需求:
- BestFitStrategy(默认):遍历所有楼层,在"能容纳该车辆"的车位中挑选尺寸 ordinal 最小者,即最贴合、最节省大车位的分配;仅在没有找到任何可用车位时返回空。
- NearestFirstStrategy:按楼层列表顺序从前向后查找,返回第一个能容纳的可用车位——模拟"离入口最近优先"。
- FarthestFirstStrategy:反转楼层列表,从最远楼层开始找——常用于引导车辆优先停满远端、为入口留位。
注意:三种策略最终都会调用
ParkingFloor.findAvailableSpot(),因此楼层内部始终按"尺寸最贴合"过滤;策略之间的差异体现在楼层遍历顺序(从头 / 从尾 / 全局最优)上。
设计模式总结
| 模式 | 落点 | 源码依据 |
|---|---|---|
| Singleton | 全局唯一的ParkingLot实例 | ParkingLot.java#L16-L33,synchronized getInstance()懒加载 |
| Strategy | 计费策略与车位分配策略双维度解耦 | FeeStrategy.java、ParkingStrategy.java |
| Template Method(轻量) | Vehicle抽象基类固化构造流程,子类仅声明尺寸 | Vehicle.java |
| Factory(可选扩展) | 可依据输入创建对应车型 | 问题文档中列为可选扩展,当前实现由 Demo 直接new Car(...) |
README 提到的Observer 可选扩展(车位空闲通知客户)在问题文档 problems/parking-lot.md 中亦有提及,可作为面试时的加分演进方向,当前代码未实现。
完整示例:从初始化到收费的端到端流程
ParkingLotDemo.java 是官方演示程序,其流程完整覆盖"初始化 → 入场 → 分配 → 离场计费",可与上文各实体一一对应:
ParkingLot parkingLot = ParkingLot.getInstance(); // 1. 楼层与车位初始化 ParkingFloor floor1 = new ParkingFloor(1); floor1.addSpot(new ParkingSpot("F1-S1", VehicleSize.SMALL)); floor1.addSpot(new ParkingSpot("F1-M1", VehicleSize.MEDIUM)); floor1.addSpot(new ParkingSpot("F1-L1", VehicleSize.LARGE)); ParkingFloor floor2 = new ParkingFloor(2); floor2.addSpot(new ParkingSpot("F2-M1", VehicleSize.MEDIUM)); floor2.addSpot(new ParkingSpot("F2-M2", VehicleSize.MEDIUM)); parkingLot.addFloor(floor1); parkingLot.addFloor(floor2); // 2. 切换到按车型计费 parkingLot.setFeeStrategy(new VehicleBasedFeeStrategy()); // 3. 入场:自行车 / 轿车 / 卡车 parkingLot.parkVehicle(new Bike("B-123")); parkingLot.parkVehicle(new Car("C-456")); parkingLot.parkVehicle(new Truck("T-789")); // 4. 离场并计费 parkingLot.unparkVehicle("C-456");结合源码可以推演其运行结果:
Bike("B-123")(SMALL)→ 命中F1-S1;Car("C-456")(MEDIUM)→ 命中F1-M1;Truck("T-789")(LARGE)→ 命中F1-L1。Car("C-999")(MEDIUM)在默认BestFitStrategy下,一楼 MEDIUM 位已被占用、无其他可容纳位,于是落到二楼的F2-M1——验证了"跨楼层全局分配"。Bike("B-000")(SMALL)入场时一楼 SMALL 位已占用、二楼根本没有 SMALL 车位,分配失败,输出No available spot for B-000——验证了Optional.empty()的优雅降级。unparkVehicle("C-456")后按VehicleBasedFeeStrategy计费:Demo 中车辆即时出入,时长不足 1 小时按 1 小时计,MEDIUM 费率 20.0,费用为$20.00;随后F1-M1恢复空闲。
如何运行
在仓库根目录执行以下命令即可编译并运行该 Java 实现(要求已安装 JDK 8+):
cd solutions/java/src/parkinglot javac ParkingLotDemo.java parkinglot/entities/*.java parkinglot/strategy/fee/*.java parkinglot/strategy/parking/*.java parkinglot/vehicle/*.java java parkinglot.ParkingLotDemo程序会依次打印入场日志、各楼层可用性统计(按 SMALL/MEDIUM/LARGE 分组计数)以及离场费用,读者可据此对照验证每种策略的行为差异。
扩展框架指南
README 给出了三条清晰的扩展路径,均以"新增类而非改动既有类"为原则,正是 Strategy/模板方法解耦的价值所在:
- 新增车型:在 vehicle/ 下新增
Vehicle子类,构造时声明对应的VehicleSize;若引入新尺寸,需同步扩展VehicleSize枚举与 ParkingSpot.canFitVehicle() 的匹配逻辑。 - 新增计费策略:实现 FeeStrategy 接口(如夜间优惠、时长阶梯价),再通过
parkingLot.setFeeStrategy(...)注入即可,ParkingLot 与票据类零改动。 - 新增车位类型或楼层行为:扩展 ParkingSpot(如加电动车充电位)或 ParkingFloor;若要引入新的分配语义,只需实现 ParkingStrategy 并注入。
小结
该 Java 版停车场系统是一个教科书级的 LLD 范例:Singleton 保证全局唯一实例,Strategy 双维度(计费 + 分配)解耦算法与业务,并发容器与 synchronized 支撑多入口场景,Optional 返回值让"无位可停"成为可预期的业务结果。读者可以对照 class-diagrams/parkinglot-class-diagram.png 中的 UML 类图梳理类间关系,再结合 ParkingLotDemo.java 实际运行,即可完整掌握从需求分析到可扩展代码实现的全链路方法。
- 示例工程
【免费下载链接】awesome-low-level-design
Learn Low Level Design (LLD) and prepare for interviews using free resources.
相关推荐
停车管理系统(Parking Lot)低层设计实战指南:基于 awesome-low-level-design 的面向对象设计解析
停车管理系统(Parking Lot)低层设计实战指南:基于 awesome low level design 的面向对象设计解析 导读 本文围绕 proble
示例工程停车场系统低层设计(LLD)实战:C 源码解析与多线程扩展(awesome-low-level-design)
停车场系统低层设计(LLD)实战:C 源码解析与多线程扩展(awesome low level design) 导读:本文以 awesome low level
示例工程用 Go 实现停车场系统:面向面试的低层设计(Low-Level Design)实战指南
用 Go 实现停车场系统:面向面试的低层设计(Low Level Design)实战指南 本篇文章以本仓库 solutions/golang/parkinglo
示例工程
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考