题目集 1-3 单部电梯调度程序总结 Blog
一、前言
本阶段三次题目集围绕单部电梯调度程序展开迭代式开发,逐步深化对面向对象设计原则、队列管理、调度算法等核心知识点的应用。
- 知识点覆盖
三次题目集核心知识点高度聚焦且层层递进:
基础层面:Java 语法规范、输入输出处理、正则表达式匹配、异常请求过滤(无效楼层、重复请求);
数据结构:队列(LinkedList)的应用,包括请求的入队、出队、去重等操作;
面向对象:类的设计、封装、继承思想,尤其是单一职责原则(SRP)的逐步落地,从单类实现到多类分工协作;
业务逻辑:电梯调度算法(同方向优先处理、顺路停靠)、状态与方向的动态决策。 - 题量与难度
题量:三次题目集均围绕 “单部电梯调度” 单一核心题目展开,输入输出格式保持一致性,在类设计要求和业务规则上迭代变更,题量集中且针对性强;
难度梯度:呈明显上升趋势。第一次题目仅要求实现基础调度逻辑,类设计无强制要求(单类即可完成);第二次题目新增单一职责原则约束,需拆分电梯类、请求类、队列类、控制类,同时增加重复请求去重处理;第三次题目取消请求类,新增乘客类,变更外部请求格式并要求将目的楼层自动入内部队列,业务逻辑关联性和类协作复杂度进一步提升。
二、设计与分析
三次题目集的核心演进逻辑是 “从单一职责混乱到职责清晰、从简单功能实现到复杂业务协作”,以下结合源码、SourceMonitor 报表及类设计进行详细分析。
(一)第一次题目:单类实现基础调度
-
类设计结构
核心设计为Main类 +Elevator类,其中Elevator类承担了所有核心职责:
存储电梯属性(最大 / 最小楼层、当前楼层、运行方向、状态);
管理请求队列(内部 + 外部请求队列);
实现请求解析(判断请求类型、提取楼层、方向);
调度算法(方向决策、停靠判断);
状态输出。
类图如下:
![image]()
-
源码核心逻辑分析
// 核心调度逻辑在ans()方法中,顺序为:输出状态→检查停靠→决策方向→移动楼层
public static void ans(Elevator elevator) {
String nextDir;
while (!elevator.ISqueue_empty()) {
// 输出当前状态
System.out.println("Current Floor: " + elevator.GetCurrently_Floor() + " Direction: " + elevator.GetDirection_Operation());
// 检查内部请求停靠
if (elevator.Get_Number(elevator.GetInside_elevator()) == elevator.GetCurrently_Floor()) {
System.out.println("Open Door # Floor " + elevator.GetCurrently_Floor());
System.out.println("Close Door");
elevator.removeInside_elevator();
}
// 检查外部请求停靠
if (elevator.Get_Number(elevator.GetOutside_elevator()) == elevator.GetCurrently_Floor()
&& (elevator.GetInside_elevator() == null
|| elevator.GetDirection_Operation().equals(elevator.Get_Outside_Direction(elevator.GetOutside_elevator())))) {
System.out.println("Open Door # Floor " + elevator.GetCurrently_Floor());
System.out.println("Close Door");
elevator.removeOutside_elevator();
}
// 决策下一个方向
nextDir = elevator.getnextDirection();
if (nextDir != null) {
elevator.SetDirection_Operation(nextDir);
}
// 移动楼层
if (elevator.GetDirection_Operation().equals("UP")) {
elevator.SetCurrently_Floor(elevator.GetCurrently_Floor() + 1);
} else if (elevator.GetDirection_Operation().equals("DOWN")) {
elevator.SetCurrently_Floor(elevator.GetCurrently_Floor() - 1);
}
}
}
- SourceMonitor 报表分析

| 关键指标 | 数值 | 分析 |
|---|---|---|
| 类与接口数 | 2 | 仅Main和Elevator两个类,设计极简 |
| 每个类的方法数 | 5.50 | Elevator类承担所有职责,方法数较多(含 get/set、队列操作、调度逻辑),Main类仅 2 个方法 |
| 平均每个方法的语句数 | 10.91 | 核心方法ans()和getnextDirection()语句密集,逻辑耦合度高 |
| 分支语句占比 | 29.7% | 调度算法中包含方向判断、停靠判断等多分支逻辑,分支占比较合理 |
| 注释行占比 | 17.7% | 注释覆盖率中等,核心逻辑无详细说明,可读性一般 |
- 设计心得
优点:实现简单,无需考虑类间协作,适合快速完成基础功能;
缺点:Elevator类职责臃肿(属性存储、队列管理、调度算法、输入解析集于一身),后续维护和扩展难度极大,若新增功能(如多电梯协作)需大幅修改源码。
(二)第二次题目:多类拆分 + 去重处理
-
类设计结构(遵循单一职责原则)
本次设计拆分出 4 个核心类,职责分工明确:
Elevator类:仅管理电梯自身属性(当前楼层、方向、状态、楼层范围校验),不参与请求管理和调度;
ExternalRequest类:封装外部请求(楼层 + 方向),统一请求数据结构;
RequestQueue类:管理内部 / 外部请求队列,负责请求的入队(含去重逻辑)和存储;
Controller类:核心调度类,负责方向决策、停靠判断、请求移除、状态输出;
Main类:仅处理输入解析和对象初始化。
类图如下:
![image]()
-
源码核心改进分析
(1)请求去重逻辑(RequestQueue类)
// 内部请求去重:避免连续相同楼层请求
public void addInternalRequest(int floor) {
if (internalRequests.isEmpty() || internalRequests.getLast() != floor) {
internalRequests.add(floor);
}
}
// 外部请求去重:避免连续相同楼层+相同方向的请求
public void addExternalRequest(int floor, Direction direction) {
if (externalRequests.isEmpty() ||
(externalRequests.getLast().getFloor() != floor || externalRequests.getLast().getDirection() != direction)) {
externalRequests.add(new ExternalRequest(floor, direction));
}
}
(2)调度逻辑解耦(Controller类)
将调度逻辑拆分到多个独立方法,可读性和维护性提升:
public void processRequests() {
// 初始状态输出
System.out.println("Current Floor: " + elevator.getCurrentFloor() + " Direction: UP");
while (true) {
if (shouldStop(elevator.getCurrentFloor())) { // 判断停靠
openDoors(); // 开关门
removeRequests(elevator.getCurrentFloor()); // 移除请求
}
determineDirection(); // 决策方向
if (queue.getInternalRequests().isEmpty() && queue.getExternalRequests().isEmpty()) {
break;
}
getNextFloor(); // 移动并输出
}
}
- SourceMonitor 报表分析

| 关键指标 | 数值 | 分析 |
|---|---|---|
| 类与接口数 | 1 | 实际源码包含 5 个类(含枚举),推测报表未统计枚举类 |
| 每个类的方法数 | 22.00 | 方法分布更均匀,Controller类承担核心调度方法,RequestQueue类专注队列操作,职责拆分后单个类方法数更合理 |
| 平均每个方法的语句数 | 5.45 | 方法粒度细化,单个方法逻辑更简洁,可读性提升 |
| 分支语句占比 | 17.2% | 分支占比下降,因逻辑拆分后每个方法的分支判断更集中,减少了嵌套分支 |
| 注释行占比 | 9.1% | 注释占比下降,需补充核心调度逻辑注释以提升可读性 |
- 设计心得
单一职责原则的落地显著提升了代码的可维护性,新增功能(如去重)仅需修改RequestQueue类,无需影响电梯属性管理和调度逻辑;
类间协作通过依赖注入实现(Controller依赖Elevator和RequestQueue),降低了耦合度;
枚举类(Direction、State)的使用规范了方向和状态的取值,避免了字符串硬编码导致的错误。
(三)第三次题目:新增乘客类 + 业务逻辑扩展
-
类设计结构调整
取消ExternalRequest类,新增Passenger类:封装乘客的源楼层和目的楼层,自动计算乘梯方向(getDirection()方法);
RequestQueue类调整:外部请求队列改为存储Passenger对象,内部请求队列保持不变;
Controller类增强:处理外部请求时,需将乘客目的楼层自动加入内部请求队列。
类图如下:
![image]()
-
源码核心变更分析
(1)乘客类设计(Passenger类)
class Passenger {
private Integer sourceFloor = null;
private Integer destinationFloor = null;
// 自动根据源楼层和目的楼层计算方向
public Direction getDirection() {
if (sourceFloor == null || destinationFloor == null) {
return Direction.IDLE;
}
return sourceFloor < destinationFloor ? Direction.UP : Direction.DOWN;
}
// get/set方法省略
}
(2)外部请求处理增强(Controller类)
private void removeRequests(int currentFloor) {
// 处理外部请求:移除后将目的楼层加入内部队列
if (!queue.getExternalRequests().isEmpty()) {
Passenger req = queue.getExternalRequests().peek();
if (req.getSourceFloor() == currentFloor && (elevator.getDirection() == req.getDirection() || queue.getInternalRequests().isEmpty())) {
queue.getExternalRequests().remove();
// 目的楼层入内部队列(自动去重)
if (elevator.isValidFloor(req.getDestinationFloor())) {
queue.addInternalRequest(req.getDestinationFloor());
}
}
}
}
- SourceMonitor 报表分析

| 关键指标 | 数值 | 分析 |
|---|---|---|
| 类与接口数 | 6 | 类数量进一步增加,但职责更单一 |
| 每个类的方法数 | 27.00 | Passenger类仅含属性访问和方向计算方法,Controller类新增乘客目的楼层入队逻辑,方法粒度保持精细 |
| 平均每个方法的语句数 | 5.45 | 方法粒度细化,单个方法逻辑更简洁,可读性提升 |
| 分支语句占比 | 17.2% | 分支占比下降,因逻辑拆分后每个方法的分支判断更集中,减少了嵌套分支 |
| 注释行占比 | 9.1% | 注释占比下降,需补充核心调度逻辑注释以提升可读性 |
- 设计心得
新增Passenger类后,请求数据结构更贴合业务本质,外部请求不再是孤立的 “楼层 + 方向”,而是完整的 “乘客出行需求”;
目的楼层自动入队逻辑的实现,体现了业务逻辑的连贯性,减少了手动输入内部请求的冗余,同时通过RequestQueue的去重机制避免重复请求;
类间协作更紧密但不耦合,Controller通过Passenger类获取源楼层、目的楼层和方向,无需直接操作原始请求字符串,降低了解析错误风险。
(四)三次设计演进对比
| 设计维度 | 第一次题目 | 第二次题目 | 第三次题目 |
|---|---|---|---|
| 类数量 | 2 个 | 5 个(含枚举) | 6 个(含枚举) |
| 核心职责分布 | 单类承担所有职责 | 多类分工(电梯、请求、队列、控制) | 职责进一步细化(乘客类封装出行需求) |
| 请求处理 | 无去重,直接入队 | 连续重复请求去重 | 去重 + 目的楼层自动入队 |
| 业务贴合度 | 低(仅实现基础调度) | 中(符合请求处理规范) | 高(模拟乘客完整出行流程) |
| 可维护性 | 差 | 良好 | 优秀 |
三、采坑心得
(一)第一次题目:逻辑顺序错误导致输出异常
- 问题描述
初始编写ans()方法时,将 “移动楼层” 放在 “输出状态” 之前,导致输出的楼层和方向与实际运行状态不一致。例如:电梯初始在 1 层,移动到 2 层后才输出 1 层的状态,与样例输出顺序不符。 - 数据与测试结果
错误代码顺序:移动楼层→输出状态→检查停靠;
测试输入:1 20 <7,UP> end;
错误输出:先输出 “Current Floor: 2 Direction: UP”,再检查 1 层是否停靠,完全不符合预期;
修正后顺序:输出状态→检查停靠→决策方向→移动楼层,与样例输出一致。 - 心得
核心业务逻辑的执行顺序直接影响输出结果,需严格遵循 “先展示当前状态,再处理当前状态下的操作(停靠、开关门),最后进行下一步动作(移动)” 的逻辑,避免因顺序颠倒导致状态与行为不一致。
(二)第二次题目:请求去重逻辑考虑不全
- 问题描述
初始实现addExternalRequest方法时,仅判断楼层是否相同,未考虑方向差异,导致<5,DOWN>和<5,UP>被误判为重复请求,过滤掉其中一个。 - 代码对比
错误代码:
public void addExternalRequest(int floor, Direction direction) {
if (externalRequests.isEmpty() || externalRequests.getLast().getFloor() != floor) {
externalRequests.add(new ExternalRequest(floor, direction));
}
}
修正代码:
public void addExternalRequest(int floor, Direction direction) {
if (externalRequests.isEmpty() ||
(externalRequests.getLast().getFloor() != floor || externalRequests.getLast().getDirection() != direction)) {
externalRequests.add(new ExternalRequest(floor, direction));
}
}
- 测试结果
输入:1 20 <5,DOWN> <5,UP> end;
错误输出:仅处理<5,DOWN>,<5,UP>被过滤;
修正后输出:两个请求均被处理,符合 “不同方向的请求不属于重复请求” 的业务规则。 - 心得
去重逻辑需基于 “请求的唯一标识” 设计,外部请求的唯一标识是 “楼层 + 方向”,内部请求的唯一标识是 “楼层”,需明确区分不同请求的去重维度,避免因考虑不全导致功能异常。
(三)第三次题目:外部请求方向判断错误
- 问题描述
初始实现Passenger类的getDirection()方法时,误将 “源楼层> 目的楼层” 判断为UP,导致电梯方向决策错误,无法正确停靠外部请求楼层。 - 代码对比
错误代码:
public Direction getDirection() {
if (sourceFloor == null || destinationFloor == null) {
return Direction.IDLE;
}
return sourceFloor < destinationFloor ? Direction.DOWN : Direction.UP;
}
修正代码:
public Direction getDirection() {
if (sourceFloor == null || destinationFloor == null) {
return Direction.IDLE;
}
return sourceFloor < destinationFloor ? Direction.UP : Direction.DOWN;
}
- 测试结果
输入:1 20 <5,4> end(源楼层 5,目的楼层 4,方向应为 DOWN);
错误输出:电梯方向决策为 UP,持续向上运行,无法停靠 5 层;
修正后输出:电梯从 1 层 UP 运行到 5 层,停靠后方向转为 DOWN,运行到 4 层停靠,符合预期。 - 心得
Passenger类的方向计算是外部请求调度的核心依据,方向判断错误会导致整个调度逻辑失效。开发过程中需结合实际场景验证核心方法的正确性,尤其是涉及方向、楼层等关键属性的计算逻辑。
(四)共性问题:无效楼层请求处理
- 问题描述
三次题目均要求过滤超出最大 / 最小楼层的无效请求,初始实现时未在输入解析阶段进行校验,导致无效请求入队后影响调度逻辑。 - 解决方案
在Main类的输入处理阶段,通过elevator.isValidFloor()方法校验楼层有效性,仅将有效请求加入队列:
// 内部请求校验
if (line.matches("^<\\d+>$")) {
int floor = Integer.parseInt(line.replaceAll("<|>", ""));
if (elevator.isValidFloor(floor)) {
queue.addInternalRequest(floor);
}
}
// 外部请求校验(第三次题目)
else if (line.matches("^<\\d+,\\d+>$")) {
String[] parts = line.replaceAll("<|>", "").split(",");
int source = Integer.parseInt(parts[0]);
int dest = Integer.parseInt(parts[1]);
if (elevator.isValidFloor(source) && elevator.isValidFloor(dest)) {
Passenger passenger = new Passenger(source, dest);
queue.addExternalRequest(passenger);
}
}
- 心得
无效输入的过滤应在 “请求入队前” 完成,避免无效数据进入队列后导致调度算法判断失误,同时减少队列操作的冗余开销。
四、改进建议
(一)代码层面改进
- 增加日志输出,便于调试
当前代码仅输出电梯运行状态,无调试信息。可引入日志框架(如 SLF4J)或简单的日志输出语句,记录请求入队、请求移除、方向变更等关键操作,例如:
// RequestQueue类添加日志
public void addInternalRequest(int floor) {
if (internalRequests.isEmpty() || internalRequests.getLast() != floor) {
internalRequests.add(floor);
System.out.println("[日志] 内部请求入队:" + floor);
} else {
System.out.println("[日志] 重复内部请求,已过滤:" + floor);
}
}
- 优化方向决策算法,支持多请求优先级
当前调度算法仅处理队列头部请求,未考虑同方向后续请求的顺路处理。可优化determineDirection()方法,遍历队列中所有同方向请求,优先处理顺路请求,减少电梯往返次数。例如:
// Controller类优化方向决策
private void determineDirection() {
// 遍历内部请求,寻找同方向顺路请求
for (int floor : queue.getInternalRequests()) {
int diff = floor - elevator.getCurrentFloor();
if ((diff > 0 && elevator.getDirection() == Direction.UP) || (diff < 0 && elevator.getDirection() == Direction.DOWN)) {
return; // 保持当前方向,处理顺路请求
}
}
// 遍历外部请求,寻找同方向顺路请求
for (Passenger passenger : queue.getExternalRequests()) {
int diff = passenger.getSourceFloor() - elevator.getCurrentFloor();
if ((diff > 0 && elevator.getDirection() == Direction.UP) || (diff < 0 && elevator.getDirection() == Direction.DOWN)) {
return; // 保持当前方向,处理顺路请求
}
}
// 无顺路请求,再切换方向
// 原有方向决策逻辑...
}
- 封装输入解析逻辑,减少 Main 类职责
当前Main类承担输入解析和对象初始化职责,可新增InputParser类,专门处理输入字符串的解析、校验和请求转换,进一步遵循单一职责原则:
class InputParser {
private Elevator elevator;
public InputParser(Elevator elevator) {
this.elevator = elevator;
}
// 解析一行输入,返回对应的请求类型(内部/外部/无效/end)
public Request parse(String line) {
// 解析逻辑实现...
}
}
(二)功能扩展建议
- 支持并发请求处理
当前题目要求串行处理请求,可扩展为支持并发请求(多个请求同时入队),通过线程安全的队列(如ConcurrentLinkedQueue)替代LinkedList,确保多线程环境下请求处理的安全性。 - 新增电梯负载限制
实际电梯存在载客量限制,可新增maxPassengers属性和currentPassengers计数器,当电梯满载时,拒绝新的外部请求,直至有乘客下梯后释放负载。 - 可视化界面展示
可结合 Java Swing 或 JavaFX 实现电梯运行可视化界面,直观展示电梯当前楼层、方向、状态及请求队列,提升程序的交互性和可读性。
五、总结
(一)学习收获
面向对象设计能力显著提升:从最初的单类实现,到逐步拆分出电梯类、请求类、队列类、控制类、乘客类,深刻理解了单一职责原则的重要性,学会了通过类的分工协作降低耦合度;
业务逻辑梳理能力增强:三次迭代中,逐步掌握了 “需求分析→类设计→逻辑实现→测试验证” 的完整开发流程,尤其是对电梯调度算法的核心(方向决策、停靠判断、请求优先级)有了清晰的认识;
问题排查能力提升:通过解决输出顺序错误、去重逻辑漏洞、方向判断错误等问题,学会了结合测试用例和日志输出定位问题,培养了 “代码→测试→修正” 的闭环思维。
(二)需进一步学习的方向
数据结构与算法优化:当前调度算法较为基础,需学习更高效的电梯调度算法(如 SCAN 算法、LOOK 算法),提升电梯运行效率;
设计模式应用:可尝试引入单例模式(确保电梯实例唯一)、策略模式(不同调度算法可切换)等设计模式,进一步优化类设计;
多线程编程:当前程序为串行处理,需学习多线程编程,实现并发请求处理和电梯状态的实时更新。
(三)改进建议
作业设计:建议在题目中提供更详细的类图说明,尤其是第二次和第三次题目中类间的依赖关系,帮助更好地理解单一职责原则的落地方式;
测试用例:可提供更多边界测试用例(如最小楼层请求、最大楼层请求、反向请求连续输入等),帮助全面验证代码的正确性;
反馈机制:建议增加代码评审环节,通过同学间的代码互评,发现类设计和逻辑实现中的问题,相互学习提升;
扩展练习:可在三次题目基础上,增加多电梯调度的扩展练习,进一步巩固面向对象设计和调度算法知识,提升复杂系统的设计能力。



浙公网安备 33010602011771号