news 2026/10/2 7:55:22

停车场系统低层设计实战:基于 awesome-low-level-design 的 Java 多楼层停车管理系统

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
停车场系统低层设计实战:基于 awesome-low-level-design 的 Java 多楼层停车管理系统
  • 示例工程

【免费下载链接】awesome-low-level-design

Learn Low Level Design (LLD) and prepare for interviews using free resources.

项目地址:https://gitcode.com/GitHub_Trending/aw/awesome-low-level-design
点击查看免费下载

导读

本文以 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.java

1. 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); }

仓库内置三种分配算法,覆盖了最常见的停车需求:

  1. BestFitStrategy(默认):遍历所有楼层,在"能容纳该车辆"的车位中挑选尺寸 ordinal 最小者,即最贴合、最节省大车位的分配;仅在没有找到任何可用车位时返回空。
  2. NearestFirstStrategy:按楼层列表顺序从前向后查找,返回第一个能容纳的可用车位——模拟"离入口最近优先"。
  3. 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/模板方法解耦的价值所在:

  1. 新增车型:在 vehicle/ 下新增Vehicle子类,构造时声明对应的VehicleSize;若引入新尺寸,需同步扩展VehicleSize枚举与 ParkingSpot.canFitVehicle() 的匹配逻辑。
  2. 新增计费策略:实现 FeeStrategy 接口(如夜间优惠、时长阶梯价),再通过parkingLot.setFeeStrategy(...)注入即可,ParkingLot 与票据类零改动。
  3. 新增车位类型或楼层行为:扩展 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.

项目地址:https://gitcode.com/GitHub_Trending/aw/awesome-low-level-design
点击查看免费下载

相关推荐

上一篇:lua-resty-kafka安装与部署:从源码编译到生产环境配置
下一篇:RestfulToolkitX 开源项目安装与使用指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/2 7:53:37

Obsidian+WorkBuddy+Gitee:AI驱动的个人知识库实战方案

这两年我一直在折腾个人知识库这件事&#xff0c;从 Word 文档堆文件夹&#xff0c;到印象笔记&#xff0c;再到 Notion&#xff0c;工具换了不少&#xff0c;核心痛点始终没变&#xff1a;内容越记越多&#xff0c;用的时候根本找不到&#xff1b;就算找到了&#xff0c;碎片和…

作者头像 李华
网站建设 2026/10/2 7:52:22

openrig 编排方案:统一管理 Claude Code 与 Codex 的模型接入配置

1. openrig 到底想解决什么问题第一次看到openrig这个名字&#xff0c;我下意识把它拆成了 "open" "rig" 两个部分。rig 在工程语境里通常指"装配、搭台、把一堆零件组合成能跑的系统"&#xff0c;而 open 则暗示了开放、可插拔、不绑定单一供应…

作者头像 李华
网站建设 2026/10/2 7:52:06

AI智评等级提升方案:从评分到个性化提分建议的工程实现

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华