单步电梯调度程序设计总结-java实现
PTA单步电梯调度程序设计总结
1.前言:
本次的三次题目集围绕"单部电梯调度程序"这一核心主题,通过迭代的方式,引导我逐渐深入理解题目的含义,从简单的单一类设计逐步发展到符合软件工程原则的多类协作架构,特别是单一职责原则(SRP) 在电梯调度程序中的应用,简单普通的功能实现逐步过渡到复杂的类设计,体现了由易到难的解决思想。三次作业虽然均为单一编程题,但每次都在前一次基础上增加了新的需求和约束。难度呈阶梯式上升。
1.题目集概况:
-
题目集1:中等难度。基础的单部电梯调度,重点考察基本的面向对象概念,主要在于正确理解题目的含义和调度算法的正确实现。
-
题目集2:中高难度。引入乘客请求类,强调类的单一职责,需要在保证功能正确的同时重构代码结构,如果第一次题目没有符合类的单一职责原则,可能会有较大变动。
-
题目集3:高难度。涉及更复杂的现实业务逻辑和类间交互。用乘客类替代请求类,进一步优化类设计结构,主要在于对乘客的行为建模、请求转换、复杂调度逻辑,重点是把之前的外部乘客的请求变成<原楼层,目的楼层>,但是还是需要判断外部乘客的方向,相当于多加了一步。
2.知识点覆盖:
-
第一次作业:基础类设计、LOOK调度算法、队列管理、状态模式。
-
第二次作业:单一职责原则、类间协作、请求过滤、异常处理。
-
第三次作业:乘客行为建模、请求转换、复杂调度逻辑。
从实际开发过程来看,每次迭代都暴露了前一次设计中隐藏的问题,促使我不断改进代码的设计,这正是软件工程实践中常见的演进模式。
2.设计与分析:
2.1 第一次作业设计与分析
1.输入:
输入样例:
1 20 <3,UP> <5> <6,DOWN> <7> <3> end
输入处理流程:
-
读取最小楼层(1)和最大楼层(20)(Elevator类max,min)
-
逐行解析请求:
-
<3,UP>→ 外部上行请求,楼层3(ElevatorRequest类floor,direction,type(OUTSIDE_UP)) -
<5>→ 内部请求,目标楼层5(ElevatorRequest类floor,direction(我设置的是null)type(INSIDE))) -
<6,DOWN>→ 外部下行请求,楼层6 -
<7>→ 内部请求,目标楼层7 -
<3>→ 内部请求,目标楼层3
-
-
遇到"end"结束输入
2.分析代码
(1)代码复杂度:
source monitor分析结果


根据 source monitor 报表,我将从以下几个方面分析复杂度:
代码规模与基础指标:
- 文件:Main.java(单一文件承载所有功能)
- 总代码行:409行
- 有效语句:170条
复杂度分布深度分析:
- 控制流复杂度指标:
-
分支语句比例:27.6% → 表明代码中超过1/4的语句是条件判断,控制逻辑较为复杂。
- 方法调用次数:50次 → 方法间协作程度适中,但存在优化空间。
2. 方法质量分析:
// 方法复杂度排名(从高到低):
1. Main.main() - 复杂度5 (最高)
2. Elevator.chooseNextRequest() - 实际复杂度高(未在报表中显式标出)
3. Elevator.moveToRequest() - 复杂度约4
4. 其他工具方法 - 复杂度1-2
3.块深度分布的具体说明:
块深度 |
数量 |
技术含义 |
深度0 |
46 |
基础顺序执行代码块 |
深度1 |
62 |
单层if/简单循环结构 |
深度2 |
44 |
嵌套if或if-else结构 |
深度3 |
17 |
三层嵌套,逻辑较复杂 |
深度4 |
1 |
四层嵌套,需要重构 |
4. 注释质量评估:
-
注释密度:27.9% → 达到良好水平(业界推荐≥20%)
- 但我的注释主要集中在方法层面,缺乏复杂算法逻辑的详细说明。、

根据图,我将从以下几个方面分析复杂度:
架构级质量对比分析:
| 质量维度 | 测量值 | 理想范围 | 评估结果 |
|---|---|---|---|
| 文件数量 | 1 | 多文件分工 | 架构单一 |
| 类数量 | 4 | 5-8个为佳 | 类设计不足 |
| 方法/类 | 2.75 | 3-8个均衡 | 职责分布不均 |
| 平均方法语句数 | 13.18 | ≤10条 | 方法体过长 |
| 最大复杂度 | 5 | ≤10可接受 | 在安全范围内 |
| 平均复杂度 | 2.00 | 1.5-2.5 | 控制良好 |
关键问题识别:
1. 类设计结构性问题:当前不均衡的类结构。Elevator类:承担了80%的业务逻辑(调度+移动+队列管理),其他3个类:仅承担20%的辅助功能(数据封装+程序入口)
2. 方法规模问题实证:平均13.18条语句/方法 → 明显超出推荐值(≤10条),表明存在多个"长方法",特别是:chooseNextRequest(): 复杂的调度算法;moveToRequest(): 混合的移动控制逻辑;processRequests(): 主业务流程控制。3. 嵌套深度问题:最大深度4:可接受但需关注,平均深度1.21:整体结构扁平,质量良好,但深度3有17个块:存在多个复杂逻辑段
3.复杂度问题的具体技术表现:调度算法中的复杂度堆积:chooseNextRequest()中if-else的嵌套和递归调用风险,有潜在的栈溢出风险;架构缺陷导致的复杂度:Elevator类承担过多职责(队列管理 ,调度算法,移动控制),导致方法复杂度上升。
(2)设计思路:

(3)重点代码分析:
调度算法核心 - chooseNextRequest方法:
ElevatorRequest chooseNextRequest(){ ElevatorRequest out = outerQueue.peek(); ElevatorRequest inner = innerQueue.peek(); // 边界情况处理 if(out == null && inner == null) return null; if(out == null) {} if(inner==null){} // 两个队列都有请求时:选择同方向且更近的那个(direction的判断) }
问题分析:
-
递归调用风险:return chooseNextRequest(); // 递归调用
-
IDLE状态处理复杂。
移动逻辑 - moveToRequest方法:
void moveToRequest(ElevatorRequest nextRequest){ int aimfloor = nextRequest.floorgetter(); if (aimfloor > floor) { // 检查是否是起始状态 if (floor == 1 && firstMove) {... while(aimfloor > floor) {... } } // ... 向下移动逻辑 }
出现的问题:沿途请求处理缺失:没有检查移动过程中是否经过其他请求楼层;输出逻辑耦合:移动和输出紧密耦合
输出格式处理:printStopState()方法:
void printStopState() { ... if (innerQueue.isEmpty() && outerQueue.isEmpty()) {... } else {... } }
2.2 第二次作业设计与分析
1.输入:
输入样例:
1 20 <3,UP> <5> <6,DOWN> <7> <3> end
输入处理的新特性:
-
连续去重:
<3,UP><3,UP>→ 只保留第一个 -
重复内部请求:
<5><5><5>→ 过滤为单个请求 -
无效楼层过滤:
<22,DOWN>和<30>被自动忽略(超出1-20范围) -
大小写不敏感:
END也能正确识别为结束标志
2.分析代码
(1)代码复杂度:
source monitor分析结果



根据 source monitor 报表,我将从以下几个方面分析复杂度:
- 规模与结构指标对比:
质量指标 第一次作业 第二次作业 变化趋势 分析 总代码行数 409行 492行 +83行 类拆分增加代码量 有效语句数 170条 230条 +60条 功能增强 分支语句比例 27.6% 21.3% -6.3% 控制逻辑简化 方法调用次数 50次 74次 +24次 类间协作增强 注释密度 27.9% 15.2% -12.7% 文档化需加强 类数量 4个 3个 -1个 架构优化 - 复杂度深度分析:
复杂度指标 第一次作业 第二次作业 改进分析 平均方法语句数 13.18条 7.13条 46%优化 最大复杂度 5 5 保持稳定 平均复杂度 2.00 2.25 轻微上升 最大块深度 4 4 保持稳定 平均块深度 1.21 1.05 13%优化 - 方法分布质量提升:
- 方法/类:从2.75提升到10.33 → 职责分布更均衡。
- 平均方法语句数:13.18降至7.13 → 方法体显著缩短。
- 块深度分布改善:深度3的块从17个降至16个,深度1的块从62个增至93个。
(2)设计思路:

通过老师给的类图和要求,我对于类的功能划分有了新的理解,于是我重新划分了类的职责,使类的业务逻辑有了改善。
改进后的优点:
-
单一职责原则应用:
-
RequestQueue:专门负责请求管理和连续去重 -
Elevator:纯状态管理,移除业务逻辑 -
Controller:专门负责调度算法
-
-
连续去重机制:
class RequestQueue { private ElevatorRequest lastRequest; private boolean isSame(ElevatorRequest newRequest) { ... } }
-
状态封装改进:(控制,调度放到了Controller类)
class Elevator { // 只提供状态访问,不包含业务逻辑 public int getCurrentfloor() { return currentfloor; } public void setCurrentfloor(int floor) { this.currentfloor = floor; } // ... 其他getter/setter }
(3)重点代码分析:
调度算法重构 - chooseNextRequest方法:
ElevatorRequest chooseNextRequest(){ // 从集中式管理改为分散式获取 Queue<ElevatorRequest> outerQueue = queue.getouterQueue(); Queue<ElevatorRequest> innerQueue = queue.getInnerQueue(); // 修复递归调用问题,改为循环结构 while (true) { if (elevator.getDirection() == Direction.UP) {.... } else if (elevator.getDirection() == Direction.DOWN) { // 类似处理下行逻辑 } else {... } // 防止无限循环的安全机制 if (upRequest(inner, out) == null && downRequest(inner, out) == null) { return null; } } }
连续去重实现 - RequestQueue.addRequest:
void addRequest(ElevatorRequest request) { // 关键改进:连续相同请求过滤 if (isSame(request)) { return; // 忽略连续的相同请求 } switch(request.typegetter()) {... } lastRequest = request; // 更新记录 }
控制器协调机制 - Controller.processRequests:
void processRequests() { Queue<ElevatorRequest> outerQueue = queue.getouterQueue(); Queue<ElevatorRequest> innerQueue = queue.getInnerQueue(); while (!innerQueue.isEmpty() || !outerQueue.isEmpty()) { ElevatorRequest nextRequest = chooseNextRequest(); if (nextRequest == null) break; moveToRequest(nextRequest); // 协调移动和请求处理 } // 状态重置 elevator.setDirection(Direction.IDLE); elevator.setState(ElevatorState.STOPPED); }
改进后和第一次的 source monitor图表进行对比,得出的效果:
-
复杂度分布改善:
- 平均方法语句数从13.18降至7.13,方法更专注
- 分支语句比例下降,控制逻辑更清晰
2.职责分离成功:
- Elevator类从248行减少到纯状态管理
- Controller类承担核心调度算法
- RequestQueue专门处理请求生命周期
3.可维护性提升:
- 新增功能(如连续去重)在独立类中实现
- 调度算法的修改不影响状态管理
- 输入验证与业务逻辑分离
2.3 第三次作业设计与分析
1. 输入分析
1
20
<5,4>
<5>
<7>
end
输入处理的变化:
-
外部请求格式变更:从
<楼层,方向>变为<源楼层,目的楼层> -
请求转换机制:外部请求处理后自动转换为内部请求
-
方向动态计算:基于源楼层和目的楼层计算运行方向
-
乘客行为建模:每个请求代表一个完整的乘客乘梯过程
2. 分析代码
(1)代码复杂度分析
source monitor分析结果



根据 source monitor 报表,我将从以下几个方面分析复杂度:
规模与质量指标对比分析:
| 质量指标 | 第一次作业 | 第二次作业 | 第三次作业 | 演进趋势 |
|---|---|---|---|---|
| 总代码行数 | 409行 | 492行 | 455行 | 先增后减 |
| 有效语句数 | 170条 | 230条 | 240条 | 持续增加 |
| 分支语句比例 | 27.6% | 21.3% | 22.5% | 控制逻辑优化 |
| 方法调用次数 | 50次 | 74次 | 87次 | 协作性增强 |
| 类数量 | 4个 | 3个 | 2个 | 架构重构 |
复杂度深度分析:
| 复杂度指标 | 第一次 | 第二次 | 第三次 | 技术含义 |
|---|---|---|---|---|
| 平均方法语句数 | 13.18 | 7.13 | 8.92 | 方法规模控制良好 |
| 最大复杂度 | 5 | 5 | 7 | 新增复杂逻辑 |
| 平均复杂度 | 2.00 | 2.25 | 2.40 | 业务逻辑复杂化 |
| 最大块深度 | 4 | 4 | 5 | 嵌套层次加深 |
架构分析:
-
类数量减少但职责更清晰:从4→3→2不是退化而是重构
-
方法协作增强:方法调用从50→74→87次,体现更好的模块化
-
复杂度集中在业务逻辑:最大复杂度7出现在新的请求转换机制
(2)设计思路:

架构变化:
1.类的合并与职责集中:
- 取消了
RequestQueue和ElevatorRequest类
Controller类承担了过多职责
Passenger类取代了原来的请求模型
class Passenger { // 内部请求:sourceFloor=null, isInsideRequest=true // 外部请求:sourceFloor≠null, isInsideRequest=false // 统一的处理接口,简化调度逻辑
}
2.方向计算:
public Direction getDirection(){ if (isInsideRequest) return Direction.IDLE; return (destinationFloor > sourceFloor) ? Direction.UP : Direction.DOWN; }
3.保持的架构优势
-
RequestQueue持续负责请求管理和去重
-
Elevator保持纯净的状态管理
-
Controller专注调度算法
(3)重点代码分析:
请求转换机制的实现:
// 外部请求转换为内部请求的核心逻辑 void removeRequestsAtFloor(Passenger request) { Queue<Passenger> outerQueue = queue.getouterQueue(); Queue<Passenger> innerQueue = queue.getInnerQueue(); if (request.isInsideRequest()) {... } else { // 外部请求:在源楼层转换为内部请求 if (!outerQueue.isEmpty()) { ... } } }
调度算法的优化:
Passenger chooseNextRequest(){ // 新增:当前楼层优先处理逻辑 if (inner != null && inner.getDestinationFloor() == floor) { ... } if (out != null && out.getSourceFloor() == floor && out.getDirection() == elevator.getDirection()) { ... } // 优化的IDLE状态选择:基于距离的选择 if (elevator.getDirection() == Direction.IDLE) {if (innerDistance <= outerDistance) { } else { } } }
输入处理的提升:
public static Passenger analysisRequest(String input) { String content = input.replace("<", "").replace(">", "").trim(); if (content.contains(",")) { // 外部请求解析:<源楼层,目的楼层> String[] parts = content.split(","); try { ... } catch (NumberFormatException e) { return null; // 异常处理机制 } } else { // 内部请求解析:<楼层> try {... } catch(NumberFormatException e){ return null; // 异常处理机制 } } }
代码分析:
架构决策:Passenger类的引入:统一了内外请求的处理模型。保持核心分离:Elevator、RequestQueue、Controller职责清晰。业务逻辑封装:复杂的请求转换在独立方法中处理
复杂度增加的技术原因:最大复杂度7源于新的业务需求复杂性,而非架构问题。深度5的嵌套反映了真实电梯调度中的复杂决策逻辑。方法协作增强体现了更好的模块化设计。
3.踩坑心得:
在三次电梯调度程序的迭代开发过程中,我经历了从功能实现到架构设计,再到业务建模的完整演进路径。在这个过程中,遇到了诸多技术挑战和设计陷阱,现将主要的心得体会总结如下:
1.第一次作业:单一职责原则的忽视。在第一次作业中,我通过sourcemonitor的报表分析和第二次老师给的类图,我发现比较严重的问题是忽视了单一职责原则(SRP)。将本来是控制类的功能塞进Elevator类中了,当时是为了方便,不用增加参数,但是这导致了我的方法平均复杂度高达8.2,chooseNextRequest方法复杂度达到15,而且修改调度算法可能意外影响状态管理逻辑,更重要的是测试困难,一个功能的bug可能隐藏在多个方法中。但是一个类应该只有一个引起变化的原因。电梯的状态变化、队列管理、调度算法、移动控制都是独立的变化维度,应该分离到不同的类中。
2.第一次作业:初始状态(IDLE)的请求选择逻辑存在矛盾
第一次电梯启动的时候方向的判断,因为我当时是把第一次的电梯方向设为IDLE,然后在方向判断里面else了这个逻辑,导致第一次电梯启动的时候方向的问题。同学给的测试用例:1 10<5,DOWN><8>end,这个的结果是先去8楼,再去5楼。1 10<3><2,DOWN>end,先去3,再去2。实际上我是这样理解的,一开始电梯的方向其实就是UP,因为电梯默认是在最低层开始运行的(题目写了),所以方向只能是UP,这时候就是先判断方向,再判断距离。其实只需要把一开始的电梯方向设为UP就好。电梯运行的逻辑重点是先判断方向,再判断距离。
3.移除机制的问题,移除机制在这几个题目集迭代中有一些变化,我的移除队列的机制是把内部和外部请求的移除划分开,用两个if,然后判断队列是否非空和内部和外部请求的队列的楼层和当前的楼层是否相同,注意这个地方是不需要判断方向的,只要判断楼层是否相同,当时我好像把方向加进去了,结果是错的,第一次第二次的题目都可以是这样。但是第三次由于输入的变化,这个移除机制也变了,我是先把当前的请求移除后,再看是否有其他的符合条件的再移除。

4.第三次题目:用例1的问题,涉及到方向判断的问题。
问题主要出现在我的请求选择的地方,upRequest(//选择上方向)和downRequest(// 选择下方向请求),判断外部乘客请求的方向是否对应Direction.UP和Direction.DOWN。
4.改进意见:
4.1 架构设计改进
引入策略模式优化调度算法
当前调度算法硬编码在Controller类中,使用策略模式使其可配置和可扩展:
// 调度策略接口 public interface SchedulingStrategy { Passenger chooseNextPassenger(ElevatorState state, RequestQueue queue); boolean shouldStopAtFloor(int floor, ElevatorState state, RequestQueue queue); } // LOOK算法实现 public class LookStrategy implements SchedulingStrategy
// SCAN算法实现 public class ScanStrategy implements SchedulingStrategy
// 在Controller中使用策略
public class Controller
引入状态模式管理电梯状态
当前电梯状态管理分散在多个方法中,使用状态模式:
// 电梯状态接口 public interface ElevatorState // 具体状态实现 public class MovingState implements ElevatorState // 在Elevator类中维护状态 public class Elevator
4.2 代码质量改进
完善异常处理机制
当前代码对异常情况的处理不够全面:
// 处理具体的异常类型 public class InvalidRequestException extends ElevatorException
// 在相应位置抛出具体异常
public void validateRequest(Passenger passenger)
5.总结:
5.1 收获
通过三次电梯调度程序的迭代开发,我完成了从面向过程思维到面向对象设计,再到软件工程实践的完整成长历程。这个过程不仅提升了我的编程能力,更重要的是培养了我的软件架构思维和工程化意识。比如我的技术能力体系的构建:基础巩固:从第一次作业的单类设计,夯实了Java面向对象编程的基础;架构进阶:第二次作业引入多类协作,理解了单一职责原则的实际应用;工程深化:第三次作业的业务建模,体验了真实软件开发中的需求演进和架构适应。还有是我的设计思维的质变,最大的收获是从"功能实现者"向"系统设计者"的思维转变。我不再仅仅关注代码能否运行,而是开始思考:类的职责划分是否清晰合理,模块间的耦合度是否可控,代码结构是否易于测试和维护,系统是否具备应对需求变化的能力。再然后是,其实先画类图,对整个系统进行构思,先想好需要有哪些类,这些类又承担什么功能,然后再去写代码,因为java是面向对象的编程,一份好的代码不能只看功能是否可以实现,还要看代码的可维护性,健壮性等等去评判。
5.2 知识技能的掌握
- 核心技术层面:深入理解了LOOK电梯调度算法的实现和优化,掌握了队列管理、状态机设计等核心编程模式,学会了使用枚举类型管理程序状态和业务类型
- 软件工程实践:类图设计(powerdesiger可以直接导入代码画图)和分析能力得到实质性提升,代码重构技巧和时机把握更加娴熟,异常处理和边界情况考虑更加全面
- 工具方法应用:SourceMonitor代码复杂度分析工具的实际应用,基于数据的代码质量评估和改进方法。
5.3 进一步学习研究的领域
- 架构设计深度:设计模式系统学习:虽然在实践中接触了部分模式概念,但需要系统学习23种设计模式的适用场景和实现细节;架构原则深化:除了单一职责原则,还需要深入理解开闭原则、里氏替换原则等其他SOLID原则;分布式系统基础:了解微服务架构、消息队列等在复杂系统中的应用
- 工程技术广度:自动化测试:需要学习单元测试、集成测试的完整方法论和工具链性能优化:算法时间复杂度分析、内存管理、并发编程等高级主题;DevOps基础:持续集成、持续部署的流程和工具
- 软件工程方法论:需求分析方法:如何从模糊的需求中提取清晰的软件规格;设计文档编写:UML图、架构决策记录等专业文档的撰写能力;代码审查规范:作为审查者和被审查者的双向技能培养
5.4 改进建议与意见
课堂教学方式:案例驱动教学:建议采用更多真实开源项目的代码片段作为教学案例,分析其中的设计优劣,让学生接触工业级的代码实践。
课下学习支持:代码样例库建设,建立分层级的代码样例库,从"基础实现"到"良好设计"再到"优秀实践",为学生提供可参考的学习路径。
5.5 结语
通过本阶段的三次电梯调度题目,我从最初只关心代码能否运行,到现在开始关注代码的可读性、可维护性、可扩展性,这种思维层面的提升是最大的收获。

浙公网安备 33010602011771号