在面向对象程序设计的学习过程中,电梯调度问题是一个经典的综合实践案例,能够有效考察类设计、状态管理、队列操作及调度算法等核心能力。本文将对三次电梯题目集的设计要求、知识点演进及难度变化进行总结,展现从单一类实现到遵循设计原则的迭代优化过程,以及如何通过职责拆分和类结构调整逐步构建更健壮的系统。
第一次题目:基础电梯类设计(单一类实现) 知识点: 面向对象基础:类的封装(属性、方法)、状态机设计(电梯状态:停止、移动、开门、关门)、队列数据结构(内部请求队列、外部上下行请求队列)。 调度逻辑:方向优先策略(同方向请求优先处理)、状态转换规则(静止→移动的触发条件)、输入验证(无效楼层过滤)。 模拟流程:键盘输入处理、输出格式控制(楼层移动与开关门事件的差异化输出)。
第二次题目:迭代设计与单一职责原则(类拆分) 知识点演进: 设计原则:单一职责原则(SRP),将原电梯类的职责拆分为 电梯类(状态与动作)、乘客请求类(请求属性与有效性校验)、队列类(请求去重与管理)、控制类(调度逻辑)。 输入处理增强:过滤无效请求(超界楼层)和重复请求(连续相同请求去重),确保请求队列的有效性。 类协作:通过控制类协调电梯与请求队列的交互,电梯类专注于状态变更和物理动作(移动、开关门),请求类封装请求属性及校验逻辑。
第三次题目:引入乘客类与请求流程重构 知识点深化: 领域建模:新增 乘客类,将外部请求从 “源楼层 + 方向” 改为 “源楼层 + 目的楼层”,外部请求处理后需将目的楼层加入内部队列,体现乘客从候梯到乘梯的完整流程。 职责进一步细化:乘客类封装请求的源与目的楼层,电梯类通过控制类接收处理后的内部请求,队列类统一管理不同阶段的请求(外部请求入队→处理后转换为内部目的楼层请求)。 边界处理:确保外部请求到内部请求的转换逻辑正确,避免因请求格式变更导致的调度错误。
题目难度总结:难度属于中上水平,题目给出了提示以及类的设计参考图,只要认真阅读题目的提示以及参考图还是可以写出来的。
二:设计与分析:
第一次电梯题目:
类的设计:
Main类:处理输入的数据,创建电梯类、电梯外请求队列和电梯内请求队列。用循环调用电梯运行方法,操控电梯。
Elevator类:成员变量( 最小楼层数、最大楼层数、当前楼层、运行方向、运行状态、电梯内请求队列和电梯外请求队列)。已及相应的构造方法和get\set方法。有着电梯相关信息包括请求队列,有电梯运行、开门、寻找下次目标楼层、转向等方法。
SourceMontor分析结果:


分析:该代码整体复杂度良好(平均2.39),但存在局部高复杂度问题,尤其Elevator.runElevatorForTheFirstTime()方法复杂度达13(含22条语句)和Main.main()方法30行过长,是需要特别关注并且优化。代码嵌套较深(20处4层嵌套),可读性差,不利于后期维护。注释率11.6%略低,可读性差,看懂代码花费时间长。应该拆分复杂方法,补充关键注释,并将Main逻辑抽取到独立类,可以提升代码的可维护性。控制流复杂度适中(25.5%),方法平均长度合理(8.05行),重点需解决少数高复杂度方法的逻辑集中问题。
第二次题目:
sourceMonter报表:


分析:在第二次的电梯作业中,因为题目中给出了参考类图,所以相对的工作量不会很大,也更有思路,相较于第一次的题目集,这次的代码基本符合了类的单一职责原则,有276行代码,对目前的我来说也算是多的,有五分之一的分支语句,方法的调用语句有118条,方法调用频繁,但是代码的注释依然还是没有,严重影响了代码的可读性和可维护性,平均每个类的方法较多,方法规模小,功能单一,整体代码复杂程度适中,但存在局部复杂点,以及存在一些逻辑错误,毕竟我也没有将此次的题目集电梯题做出来,代码存在局部复杂度高的地方,还有改进的地方。
第三次题目分析:

代码整体分析:
由于与第二次大作业相差不大,所以代码的行数和语句数还有分支语句也没有变化很大,依旧是没有注释,类和接口数多了一些,方法规模较小,平均深度还行,但是最大深度较大,局部逻辑嵌套较深,增加理解与调试难度,依然存在局部复杂度过高的问题,逻辑几乎与第二次相同,所以结果可想而知的是失败了,但也是满足了类的单一职责原则。
三、采坑实录:
1. 第二次迭代:职责拆分不彻底导致的逻辑混乱(PTA 通过率从 60% 到 90% 的血泪史)
在第二次作业中,错误地将请求校验逻辑放在
电梯类而非请求队列类,导致电梯类同时处理状态变更和请求过滤,违反 SRP 原则。例如,当输入无效楼层<0,UP>时,电梯类未正确忽略,仍尝试加入队列,引发后续空指针异常。源码证据(错误实现):
// 电梯类中错误包含请求校验
public void addExternalRequest(String input) {
if (input.contains("END")) return; // 错误!未校验楼层范围
// 直接解析输入并加入队列,未判断楼层是否在[min, max]之间
}
输入
<0,UP>后,电梯输出Current Floor: 1 Direction: UP后报错,因为队列中存在无效楼层,调度时计算方向出错。解决方案:
将请求校验逻辑移至
请求队列类,新增isValidRequest()方法:// 请求队列类中的校验逻辑
private boolean isValidRequest(String input, int minFloor, int maxFloor) {
// 解析楼层,判断是否在[min, max]之间,且方向合法
if (floor < minFloor || floor > maxFloor) {
return false; // 自动忽略无效请求
}
}
2. 第三次迭代:外部请求转换遗漏导致的 “乘客失踪” BUG
外部请求格式变更为
<源楼层,目的楼层>后,未正确将目的楼层加入内部队列。例如,外部请求<3,5>被处理后,电梯在 3 楼开门,但未将 5 楼加入内部请求,导致电梯关门后直接跳过 5 楼。调试日志:
Open Door # Floor 3 // 正确停靠源楼层
Close Door
// 应处理内部队列中的5楼,但此时队列为空,电梯直接反向
Current Floor: 2 Direction: DOWN // 错误!应前往5楼
在控制类处理外部请求时,仅移除了源楼层请求,未添加目的楼层:
// 错误:未将目的楼层加入内部队列
if (request instanceof ExternalRequest) {
queue.remove(request); // 移除外部请求
// 缺少:internalQueue.add(request.getDestinationFloor());
}
// 正确逻辑:处理外部请求后,将目的楼层加入内部队列
if (request instanceof ExternalRequest) {
ExternalRequest externalReq = (ExternalRequest) request;
internalQueue.add(externalReq.getDestinationFloor()); // 添加目的楼层
externalQueue.remove(externalReq); // 移除已处理的外部请求
}
<3,5>后,电梯在 3 楼开门,关门后输出Current Floor: 4 Direction: UP,最终在 5 楼正确停靠,BUG 修复。3. 通用坑点:重复请求去重的 “边界盲区”
<3><3><3>时,第一次实现仅过滤了完全相同的相邻请求,但未处理间隔重复(如<3><5><3>)。错误逻辑:
// 仅检查相邻请求是否相同(错误)
if (queue.isEmpty() || lastRequest != currentRequest) {
queue.add(currentRequest);
}
使用
Set存储请求去重,确保队列中无重复楼层(内部请求)或相同源楼层 + 方向(外部请求):// 内部请求队列使用LinkedHashSet保持顺序并去重
private Set<Integer> internalRequests = new LinkedHashSet<>();
四:改进建议:让代码更 “抗揍” 的可持续方案
1. 输入处理:封装成独立的 “请求解析器” 类
RequestParser类:public class RequestParser {
public static Request parse(String input, int minFloor, int maxFloor) {
if (input.startsWith("<") && input.endsWith(">")) {
// 解析内部/外部请求,校验楼层范围
if (isInternalRequest(input)) {
int floor = Integer.parseInt(input.substring(1, input.length()-1));
if (floor >= minFloor && floor <= maxFloor) {
return new InternalRequest(floor);
}
} else if (isExternalRequest(input)) {
// 解析源楼层和目的楼层,校验范围
}
}
return null; // 无效请求
}
}
2. 调度算法:策略模式分离方向决策逻辑
DirectionStrategy,不同调度策略(如经典 SCAN 算法、LOOK 算法)实现该接口:public interface DirectionStrategy {
Direction decideNextDirection(Elevator elevator);
}
// 实现类:同方向优先策略
public class SameDirectionFirstStrategy implements DirectionStrategy {
@Override
public Direction decideNextDirection(Elevator elevator) {
// 判断当前方向是否有未处理请求,否则反转方向
}
3. 日志与调试:结构化输出关键状态
// 电梯类移动方法
public void moveTo(int targetFloor, Direction direction) {
System.out.printf("Current Floor: %d Direction: %s%n", currentFloor, direction);
// 记录当前状态到日志文件(可选)
}
internalQueue.add()语句。五、阶段总结:从面向过程到面向对象的思维跃迁
1. 核心能力提升
- 类设计原则:从第一次的 “大杂烩” 电梯类,到第三次严格遵循 SRP 的四层架构(控制类 + 电梯类 + 乘客类 + 队列类),理解了 “每个类只做一件事” 的重要性 —— 例如
Elevator类只负责状态变更(移动、开关门),ControlClass专注调度逻辑,QueueManager处理请求去重与队列操作。 - 边界条件处理:学会预判无效输入(楼层超界、格式错误)、重复请求(连续 / 非连续重复)、状态异常(静止时无方向、开门时移动),并通过前置校验(如
RequestParser)和防御性编程(非空判断)避免崩溃。 - 调试方法论:掌握 “日志驱动调试”,通过输出关键变量(当前楼层、内外队列内容、运行方向)快速定位逻辑漏洞,例如第三次作业中通过打印
internalQueue发现目的楼层未入队的问题。
2. 待改进方向
- 并发处理:三次作业均假设串行请求,但实际电梯需处理并发请求(如多个请求同时到达)。后续可学习多线程编程,使用
BlockingQueue实现线程安全的请求队列。 - 性能优化:当前调度算法为 “同方向优先”,但未考虑最优路径(如电梯在上升时,优先处理上方所有同向请求,而非逐个处理)。可研究电梯调度经典算法(如 SCAN、LOOK),实现更高效的请求合并。
- 代码复用:队列类可泛型化(
Queue<T extends Request>),减少内部 / 外部请求的重复代码;控制类可通过依赖注入切换不同电梯配置(如载货电梯、住宅电梯的不同规则)。
六、结语:写代码如搭积木,职责清晰才能万丈高楼
浙公网安备 33010602011771号