NCHU_单部电梯调度程序

前言
在三次大作业中,单部电梯调度程序的设计不断升级、完善,从最初的单个类设计到后面的多个类设计,从最初的未解决电梯类职责过多的问题到后来的类设计要求遵循单一职责原则(SRP),要求必须包含但不限于设计电梯类、乘客请求类、队列类以及控制类,以及最后的加入乘客类(Passenger),取消乘客请求类,类设计要求遵循单一职责原则(SRP)。题目的设计要求不断清晰,同时也要求我们不断完善最初的代码。以下是我对三次大作业的分析与总结。

题目涉及的知识点
数据结构相关:
队列(Queue)管理:电梯需要维护内部请求队列和外部请求队列(分上行/下行)
集合(Set)去重:使用Set自动处理重复请求
枚举(Enum)类型:定义电梯运行状态和方向
字符串处理:解析输入格式如<5,UP>和<3>
算法相关:
调度算法:电梯运行优先级策略(同方向优先、距离优先)
状态机管理:电梯状态转换(停止、移动、开门、关门)
边界检查:楼层有效性验证
循环控制:电梯逐层移动的模拟
面向对象设计:
类职责分离:Elevator、ExternalRequest等类的单一职责
封装性:私有属性和公共方法的设计
枚举使用:Direction和ElevatorState的状态管理

题目难点分析
调度逻辑复杂度:
同方向优先与距离优先的平衡
内部请求与外部请求的优先级处理
电梯空闲状态的方向决策
顺路请求的实时判断
状态管理挑战:
多种状态(停止、移动、开门、关门)的正确转换
方向改变时机的准确判断
请求完成后的状态恢复
输入输出处理:
复杂输入格式的解析(带逗号和不带逗号)
无效输入和重复请求的过滤
逐层输出的同步控制
边界情况处理:
最高层和最低层的特殊处理
连续相同请求的去重逻辑
电梯运行完毕的终止条件

编程思路分析
自上而下的设计方法:
1.需求分析:理解电梯运行规则和输入输出要求
2.类设计:根据功能划分Elevator、ExternalRequest等类
3.核心算法:实现调度策略和状态转换逻辑
4.输入输出:处理数据读取和结果输出
5.测试验证:通过样例验证程序正确性

  • 第一次大作业

题目:设计一个电梯类,具体包含电梯的最大楼层数、最小楼层数(默认为1层)当前楼层、运行方向、运行状态,以及电梯内部乘客的请求队列和电梯外部楼层乘客的请求队列,其中,电梯外部请求队列需要区分上行和下行。
电梯运行规则如下:电梯默认停留在1层,状态为静止,当有乘客对电梯发起请求时(各楼层电梯外部乘客按下上行或者下行按钮或者电梯内部乘客按下想要到达的楼层数字按钮),电梯开始移动,当电梯向某个方向移动时,优先处理同方向的请求,当同方向的请求均被处理完毕然后再处理相反方向的请求。电梯运行过程中的状态包括停止、移动中、开门、关门等状态。当电梯停止时,如果有新的请求,就根据请求的方向或位置决定移动方向。电梯在运行到某一楼层时,检查当前是否有请求(访问电梯内请求队列和电梯外请求队列),然后据此决定移动方向。每次移动一个楼层,检查是否有需要停靠的请求,如果有,则开门,处理该楼层的请求,然后关门继续移动。
使用键盘模拟输入乘客的请求,此时要注意处理无效请求情况,例如无效楼层请求,比如超过大楼的最高或最低楼层。还需要考虑电梯的空闲状态,当没有请求时,电梯停留在当前楼层。
请编写一个Java程序,设计一个电梯类,包含状态管理、请求队列管理以及调度算法,并使用一些测试用例,模拟不同的请求顺序,观察电梯的行为是否符合预期,比如是否优先处理同方向的请求,是否在移动过程中处理顺路的请求等。为了降低编程难度,不考虑同时有多个乘客请求同时发生的情况,即采用串行处理乘客的请求方式(电梯只按照规则响应请求队列中当前的乘客请求,响应结束后再响应下一个请求),具体运行规则详见输入输出样例。
输入格式:
第一行输入最小电梯楼层数。
第二行输入最大电梯楼层数。
从第三行开始每行输入代表一个乘客请求。
电梯内乘客请求格式:<楼层数>
电梯外乘客请求格式:<乘客所在楼层数,乘梯方向>,其中,乘梯方向用UP代表上行,用DOWN代表下行(UP、DOWN必须大写)。
当输入“end”时代表输入结束(end不区分大小写)。
输出格式:
模拟电梯的运行过程,输出方式如下:
运行到某一楼层(不需要停留开门),输出一行文本:
Current Floor: 楼层数 Direction: 方向
运行到某一楼层(需要停留开门)输出两行文本:
Open Door # Floor 楼层数
Close Door

  1. 使用SouceMonitor生成报表

屏幕截图 2025-11-22 115825

  1. 使用PowerDesigner生成类图

屏幕截图 2025-11-21 201215

  1. 优点
    结构清晰:类图显示良好的职责分离
    枚举使用合理:Direction和ElevatorState使代码更易读
    去重机制:使用Set自动处理重复请求
    边界处理:有楼层有效性检查

  2. 缺点
    数据结构问题:使用Set存储请求,无法保证先来先服务
    复杂度高:报表显示determineNextDirection()复杂度达11
    状态管理不完善:ElevatorState枚举使用不充分
    输入处理薄弱:输入验证不够严格
    注释率低:仅5.5%
    方法复杂度高:平均4.20,建议控制在3以下
    块深度大:最大深度6,说明嵌套过深

  • 第二次大作业

题目:对之前电梯调度程序进行迭代性设计,目的为解决电梯类职责过多的问题,类设计要求遵循单一职责原则(SRP),要求必须包含但不限于设计电梯类、乘客请求类、队列类以及控制类。电梯运行规则与前阶段单类设计相同,但要处理如下情况:
乘客请求楼层数有误,具体为高于最高楼层数或低于最低楼层数,处理方法:程序自动忽略此类输入,继续执行
乘客请求不合理,具体为输入时出现连续的相同请求,例如<3><3><3>或者<5,DOWN><5,DOWN>,处理方法:程序自动忽略相同的多余输入,继续执行,例如<3><3><3>过滤为<3>
输入格式:
第一行输入最小电梯楼层数。
第二行输入最大电梯楼层数。
从第三行开始每行输入代表一个乘客请求。

电梯内乘客请求格式:<楼层数>
电梯外乘客请求格式:<乘客所在楼层数,乘梯方向>,其中,乘梯方向用UP代表上行,用DOWN代表下行(UP、DOWN必须大写)。
当输入“end”时代表输入结束(end不区分大小写)。
输出格式:
模拟电梯的运行过程,输出方式如下:

运行到某一楼层(不需要停留开门),输出一行文本:
Current Floor: 楼层数 Direction: 方向
运行到某一楼层(需要停留开门)输出两行文本:
Open Door # Floor 楼层数
Close Door

  1. 使用SouceMonitor生成报表

屏幕截图 2025-11-21 204046

  1. 使用PowerDesigner生成类图

屏幕截图 2025-11-21 201637

  1. 优点
    架构清晰:类图显示良好的职责分离,每个类功能单一
    去重机制高效:使用HashSet实现O(1)复杂度的去重检查
    性能优化:StringBuilder预分配容量,减少内存分配
    健壮性增强:完善的输入验证和异常处理

  2. 缺点
    复杂度问题:
    Controller.getNextStop()复杂度4,Controller.processRequests()复杂度5,方法过于复杂
    RequestQueue.removeExternalHeadIfAtFloorAndDir()复杂度5,嵌套过深
    平均复杂度2.22,高于理想值(应<2.0)
    代码质量指标:
    最大块深度4,存在深层嵌套
    方法平均语句数4.61,部分方法过长
    架构问题:
    Controller类承担过多职责,既是调度器又是输出管理器
    电梯移动逻辑与业务逻辑耦合过紧
    缺乏接口抽象,扩展性有限

  3. 与第一次代码相比
    架构重构:从单类设计变为多类协作,符合面向对象原则
    去重机制:新增智能去重,避免重复请求处理
    调度算法优化:引入更科学的方向优先策略
    状态管理完善:明确的状态转换逻辑
    性能提升:使用更高效的数据结构(ArrayDeque、HashSet)

  4. 小结
    第二次大作业代码在架构设计和功能实现上相比第一次有显著提升,主要成功点在于合理的类职责分离和高效的去重机制。然而,复杂度控制和方法设计仍有优化空间,特别是调度算法的可读性和可维护性需要进一步改进。

  • 第三次大作业

题目:对之前电梯调度程序再次进行迭代性设计,加入乘客类(Passenger),取消乘客请求类,类设计要求遵循单一职责原则(SRP),要求必须包含但不限于设计电梯类、乘客类、队列类以及控制类,电梯运行规则与前阶段相同,但有如下变动情况:

乘客请求输入变动情况:外部请求由之前的<请求楼层数,请求方向>修改为<请求源楼层,请求目的楼层>
对于外部请求,当电梯处理该请求之后(该请求出队),要将<请求源楼层,请求目的楼层>中的请求目的楼层加入到请求内部队列(加到队尾)
输入格式:
第一行输入最小电梯楼层数。
第二行输入最大电梯楼层数。
从第三行开始每行输入代表一个乘客请求。

电梯内乘客请求格式:<楼层数>
电梯外乘客请求格式:<请求源楼层,请求目的楼层>,其中,请求源楼层表示乘客发起请求所在的楼层,请求目的楼层表示乘客想要到达的楼层。
当输入“end”时代表输入结束(end不区分大小写)。
输出格式:
模拟电梯的运行过程,输出方式如下:

运行到某一楼层(不需要停留开门),输出一行文本:
Current Floor: 楼层数 Direction: 方向
运行到某一楼层(需要停留开门)输出两行文本:
Open Door # Floor 楼层数
Close Door

  1. 使用SouceMonitor生成报表

屏幕截图 2025-11-21 203547

  1. 使用PowerDesigner生成类图

屏幕截图 2025-11-21 200029

  1. 优点
    架构设计优秀:
    类图显示完美的职责分离,每个类功能单一明确
    依赖关系清晰,符合面向对象设计原则
    枚举使用合理,提高代码可读性
    功能实现完整:
    支持新的外部请求格式<源楼层,目标楼层>
    实现外部到内部请求的自动转换
    完善的输入验证和重复请求过滤
    性能优化良好:
    StringBuilder预分配,减少IO开销
    实时请求处理,避免全量扫描

  2. 缺点
    极端高复杂度
    Controller.run()方法复杂度59​ - 这是灾难性的代码质量
    Controller.run()语句数88​ - 方法过长,违反单一职责
    最大块深度7​ - 嵌套过深,难以理解和维护
    性能问题
    去重机制使用O(n)线性搜索,而非O(1)的HashSet
    深层嵌套导致性能下降

  3. 与第二次代码相比
    业务逻辑更加完善
    请求生命周期管理
    引入更智能的方向一致性判断
    增加距离相等时的 tie-breaker 机制
    实现真正的"顺路捎带"逻辑

具体改进对比表:

屏幕截图 2025-11-22 153628

问题总结

架构设计陷阱:
问题:第一次作业采用"上帝类"设计,单个类承担过多职责
教训:虽然快速实现了功能,但导致:
代码复杂度高达59,维护困难;
添加新功能风险大,牵一发而动全身;
无法进行单元测试
解决方案:第三次作业采用SRP原则,每个类职责单一:
Passenger:数据模型
RequestQueue:队列管理
Elevator:状态管理
Controller:调度算法

数据一致性挑战:
问题:外部请求转内部请求时的状态同步
教训:最初没有处理好请求状态转换,导致重复处理同一请求,请求丢失或重复添加
解决方案:建立清晰的请求生命周期管理:外部请求 → 电梯响应 → 转为内部请求 → 请求完成

总结
通过三次迭代,我最大的收获不是学会了某个具体技术,而是建立了工程化思维的框架。我从最开始只关注功能的实现,到逐渐关注代码的可维护性、可扩展性。从局部优化到全局设计,从战术编码到战略规划,从主观感觉到量化分析,这次经历让我明白代码设计的成熟度体现在对复杂度的控制能力上。实现一个工程首先要做到的就是弄懂工程的需求是什么,务必做到方方面面详实全面,一个类的复杂度过高可能导致整个程序的全面崩盘。这个项目让我认识到自己的不足,也看到了成长的方向。软件之路漫长,但每次迭代都是进步的机会。

posted @ 2025-11-22 15:58  曹家豪  阅读(10)  评论(0)    收藏  举报