Java电梯题目集5~7总结
一、前言
1.题目集 5 至 7 围绕单部电梯调度问题展开,一次次地迭代,不断完善电梯功能,逐步深入地考察了面向对象编程的相关知识和技能。这三次题目集涵盖的知识点主要包括类的设计与封装、对象的创建与使用、队列数据结构的应用、逻辑判断与流程控制以及输入输出的处理等。
2.从题量上看,每次题目集均聚焦于单部电梯调度这一核心问题,虽仅有一道题目,但题目要求不断迭代更新优化,对代码的设计和实现提出了更高的挑战。
3.在难度方面,题目集 5 作为基础,要求实现基本的电梯调度功能,包括电梯状态管理、请求队列管理以及简单的调度算法,难度较高。题目集 6 在此基础上,强调遵循单一职责原则(SRP)进行类的设计,将电梯调度的不同功能模块拆分到多个类中,同时需要处理如无效楼层请求、连续相同请求等特殊情况,难度提升,着重考察对类设计原则的应用能力。题目集 7 进一步迭代,引入乘客类并修改请求输入格式,要求对电梯调度程序进行更深入的优化,不仅要遵循 SRP 原则,还需处理新的请求逻辑和数据流转,难度再次升级,对综合编程能力和逻辑分析能力要求较高。
二、设计与分析
题目集 5 单部电梯调度分析
题目需求:编写 Java 程序,包含电梯状态管理、请求队列管理和调度算法,采用串行处理请求方式。
分析问题:电梯初始在一楼状态静止,在收到乘客请求后,电梯必然向上运行,因此电梯会优先处理向上的请求(优先最近),当同方向的请求均被处理完毕然后再处理相反方向的请求。若外部请求楼层与当前楼层匹配且方向相符,或内部请求楼层为当前楼层,就移除对应请求、开关门并将电梯状态设为停止,当电梯停止时,会根据新请求的方向或位置决定移动方向,同时,电梯每到一层会检查一次是否有新请求。
设计类图

ElevatorSimulation类承担了电梯模拟的主要功能。其包含了电梯的各种属性,如max(最大楼层数)、min(最小楼层数)、cur(当前楼层)、dir(运行方向)、state(运行状态),以及内部请求队列intReqs和外部请求队列extReqs。
addIntReq和addExtReq方法分别用于向内部请求队列和外部请求队列添加请求。
run方法是电梯模拟的核心,通过循环不断处理请求,根据电梯的当前状态和请求情况决定下一次运动。
nextAction方法用于确定电梯的下一次运动,根据当前的请求队列和电梯方向来决定是否改变方向或继续移动。
checkStops方法检查电梯是否需要在当前楼层停靠,若有匹配的请求则进行开门、处理请求和关门的操作。
代码分析

通过 SourceMonitor 生成的报表可以看出,代码行数为177行,其中语句130条 。分支语句占比为30.8% ,说明代码中分支逻辑(如if - else、switch等)占比较大;方法调用语句有43条,注释仅占比仅2.3% ,表明代码注释严重不足,可维护性较差。每个类平均有5.50个方法 ,平均每个方法有9.64条语句 。最复杂方法的行号是69,该方法为ElevatorSimulation.nextAction() ,其最大复杂度为16,说明此方法逻辑较为复杂。最深块的行号是80 ,最大块深度为5,反映了代码中嵌套结构的深度情况。平均块深度为2.68,平均复杂度为 6.00,体现代码整体结构和逻辑复杂程度处于一定水平。
在题目集 5 中,电梯调度功能主要集中在一个电梯类中实现。从报表中可以看出,该类的复杂度较高,方法和属性集中在一个类中导致代码耦合度较高。电梯类包含电梯的基本属性,如最大楼层数、最小楼层数、当前楼层、运行方向、运行状态,以及电梯内部和外部的请求队列。
题目集 6 单部电梯调度分析
题目需求:题目集 6 按照单一职责原则对代码进行重构,设计了电梯类、乘客请求类、队列类以及控制类。
分析问题:电梯的运行核心逻辑与题目集5相同,但多了对不合理请求的处理,对于不合理请求应直接忽略。
设计类图

Elevator类负责管理电梯的基本状态,如当前楼层、运行方向和运行状态等。
ExternalRequest类用于处理外部请求,包含请求的楼层和方向信息。
RequestQueue类负责管理请求队列,包括内部请求队列和外部请求队列,提供了添加请求、获取请求以及移除请求等方法。
Controller类则是整个电梯调度的控制中心,它协调各个类之间的交互,处理请求并控制电梯的运行。
代码分析


通过 SourceMonitor 生成的报表可以看出,代码共 311 行,其中语句有 192 条 。分支语句占比 20.3% ,代码逻辑有一定分支判断;方法调用语句有 88 条 ,反映了方法间的调用频繁程度。代码没有注释,使可读性和可维护性差。项目中有 7 个类 ,平均每个类有 5.14 个方法 ,每个方法平均含 3.69 条语句 。最复杂方法是Controller.determineDirection() ,在 202 行,复杂度为 16 ,最大块深度所在行号为 298 ,最大块深度是 6 ,平均块深度 2.28 ,平均复杂度 2.56 ,体现代码整体结构和逻辑复杂程度适中。Controller.determineDirection() 复杂度 16 ,有 26 条语句 ,最大深度 5 ,调用 27 次 ,是重点关注对象,可考虑优化。大部分方法复杂度较低,但部分方法如Controller.checkStops() 、Main.main() 复杂度相对较高还有,有很大改善空间。
在题目集6中,通过单一职责的类的设计原则,使电梯各个类的职责明确,降低了代码的耦合度,提高了代码的可维护性和可扩展性。当需要修改请求队列的管理逻辑时,只需在RequestQueue类中进行修改,以及在电梯核心逻辑出现问题时,只需在Controller类中修改,而不会影响其他类的功能。
题目集 7 单部电梯调度分析
题目需求:题目集 7 在题目集 6 的基础上进一步迭代,引入了Passenger类,并对请求处理逻辑进行了调整。
分析问题:电梯大体逻辑是不变的,但需注意外部请求楼层会被添加到内部队列队尾。
设计类图

Passenger类用于表示乘客,包含乘客的出发楼层和目的楼层信息,并提供了生成外部请求的方法。
ExternalRequest类的定义进行了相应修改,不再区分请求方向,而是直接包含出发楼层和目的楼层。
RequestQueue类进行了更新,以适应新的请求格式,包括添加外部请求时的处理逻辑。
Controller类的processRequests和determineDirection等方法根据新的请求逻辑进行了调整,在处理外部请求时,将请求目的楼层加入内部队列。
代码分析


通过 SourceMonitor 生成的报表可以看出,代码共 318 行,其中语句有 207 条 。分支语句占比 19.8% ,代码中存在一定逻辑判断;方法调用语句达 115 条 ,表明方法间调用较为频繁。代码没有注释,使可读性和可维护性差。项目中有 8 个类,平均每个类有 3.75 个方法 ,每个方法平均含 5.03 条语句 。最复杂方法是Controller.determineDirection() ,位于 167 行,复杂度高达 28 ,最大块深度所在行号为 185 ,最大块深度是 6 ,平均块深度 2.62 ,平均复杂度 3.13 ,说明代码整体结构和部分方法逻辑有一定复杂度。Controller.determineDirection() 复杂度为 28 ,有 21 条语句 ,最大深度 6 ,调用 48 次 ,是整个代码中最复杂且被频繁调用的方法,还有很大的改善空间;Controller.checkStops() 复杂度为 14 ,也相对复杂,但多数方法复杂度较低。
在题目集7中,乘客类的引入使程序的抽象层次更加清晰,但同时也增加了代码复杂度,对各个类之间关系的处理以及数据处理提出了更高要求。
三、踩坑心得
1.题意理解困难:第一次看题目的时候,如何正确理解题目就成了一大难关,一开始本不懂电梯运行规则,看着长篇大论的题目要求,确实让人头痛,再看测试样例,也是让人一头雾水,刚开始几天根部不知道怎么下手,因此只完成电梯类的基本定义,将类的成员给出,关于电梯的核心运行逻辑方法还是无从下笔。在接下来几天老师不断在群里给我们提示,慢慢的对题目理解更加深刻,大致弄懂了电梯运行核心,即LOOK算法(若电梯在同一运行方向上无请求,则切换方向)。最终在与不懈努力和与他人交流下在最后一天通过了测试点。
题目集5代码思路
点击查看代码思路
一.整体运行循环判断:
在 run() 方法中设置一个循环 while (!(intReqCnt == 0 && extReqCnt == 0))。这个循环的目的是持续运行电梯模拟系统,只要内部请求队列(intReqs)和外部请求队列(extReqs)中还有未处理的请求,就会一直循环下去。一旦两个队列都为空,说明所有请求都已处理完毕,此时循环结束,模拟程序也就结束。
二.单个队列空时的处理:
在 nextAction() 方法中,当电梯处于停止状态(state.equals("STOP"))且需要确定下一步动作时,会先检查队列的情况。如果其中一个队列(以外部请求队列 extReqCnt == 0 为例)为空,而另一个队列(内部请求队列intReqCnt!=0)不为空。此时,根据内部请求队列的第一个请求(intReqs[0])与当前楼层(cur)的关系来确定电梯的运行方向。如果 intReqs[0] > cur,则将电梯运行方向设置为 "UP";如果 intReqs[0] < cur,则设置为 "DOWN"。这样,即使有一个队列为空,电梯也能根据另一个队列的请求继续运行。
三.电梯方向与外部队列首个元素方向关系的处理:
同样在 nextAction() 方法中,当两个队列都不为空时,根据电梯当前的运行方向(dir 为 "UP" 或 "DOWN")和外部请求队列的首个元素(通过 getExtTarget() 方法获取目标楼层)来确定下一步动作。
1.同向情况(以电梯方向为 "UP" 为例):
(1)当外部请求与内部请求的目标楼层均大于当前楼层时,比较外部请求目标楼层和内部请求队列第一个元素(intReqs[0])的大小,选择较小的楼层作为下一个目标。这是因为不能跳过较小楼层的请求而直接去更高的楼层,符合先处理近处请求的逻辑。
(2)若外部请求目标楼层小于当前楼层,而内部请求目标楼层大于当前楼层,此时优先处理内部请求,即电梯继续向上运行去内部请求的楼层。因为电梯当前方向是向上,在有向上的内部请求时,优先以电梯当前方向的请求为主。
(3)当外部请求目标楼层大于当前楼层,内部请求目标楼层小于当前楼层时,去外部请求的楼层。这是因为在这种一上一下的情况下,以在上方的请求为准,符合电梯运行的常理。
(4)若外部请求与内部请求的目标楼层均小于当前楼层,此时电梯不移动,而是改变运行方向为 "DOWN"。因为当前方向上没有需要处理的请求,改变方向后,在后续循环中会按照新方向的逻辑去处理请求,且由于没有删除队列元素,不会出现死循环。
2.反向情况(以电梯方向为 "DOWN" 为例):
(1)当外部请求与内部请求的目标楼层均大于当前楼层,或者外部请求目标楼层小于当前楼层且内部请求目标楼层大于当前楼层时,都优先去内部请求的楼层。这是因为在方向相反的情况下,如果去处理外部请求可能需要先转向,而此时电梯上方还有内部请求可以处理,所以先处理内部请求,直到不满足这种情况为止。
(2)若外部请求目标楼层大于当前楼层,内部请求目标楼层小于当前楼层,此时去外部请求的楼层,并将电梯运行方向改为 "UP"。这是因为电梯上方没有内部请求可处理,但有外部请求,所以处理完外部请求后改变方向。
(3)当外部请求与内部请求的目标楼层均小于当前楼层时,电梯不移动,改变运行方向为 "UP"。和同向情况中都小于当前楼层的逻辑类似,改变方向后进入同向的逻辑处理流程。
四.检查是否停止及处理:
在 checkStops() 方法中,会检查当前楼层是否满足外部请求或内部请求的目标楼层。如果外部请求的目标楼层等于当前楼层,且请求方向与电梯运行方向相同(通过 extReqs[0].indexOf(dir) >= 0 判断),则移除该外部请求(调用 remExtReq() 方法),并标记需要停止(设置 stop = 1)。如果内部请求的目标楼层等于当前楼层,则移除该内部请求(调用 remIntReq() 方法),同样标记需要停止。当标记为需要停止时,输出开门和关门信息,并将电梯状态设置为"STOP",表示电梯在当前楼层停留并处理请求。
2.代码逻辑错误:敲代码时最痛苦的两个错误就是非零返回和运行超时了,非零返回基本上都是请求录入时出错的,因为主方法在处理请求楼层录入时,由于包含<>,无法直接读取,而我使用的是字符串录入,常常会出现字符串长度超过限制的问题,或者读取错误,没有准确读取到数字的错误,这个错误比较好改正,只需利用索引及charAt()正确读取字符串的每位字符即可。而运行超时便最折磨人了,这个问题大概率就是电梯运行的核心逻辑出了问题,循环在处理部分请求时,无法正确停止。这个问题在三次题目集中都会遇到,修改时也很困难,必须仔细检查电梯运行的逻辑,有时修改又会导致其他逻辑错误,即便修改到运行不超时,也有可能导致答案错误。记得修改的最久的运行超时是题目集7,在 题目集7中的Controller 类的 determineDirection 方法中的一个检查电梯内部请求队列中是否存在向上或向下的请求,以此来辅助决定电梯的运行方向的for循环中,错误地将所有内部请求都考虑进去,而不是第一个请求,正确逻辑应该是一次判断一个内部请求,看其是否大于或小于当前楼层来决定电梯是否运行,如果将所有内部请求都考虑,那么电梯便会出现无法停止的错误,导致运行超时,而修改只需将for循环执行一次即可,我足足找了两天才发现问题。
3.缺乏单一职责思维:在题目集5中,我只用了一个电梯类将电梯的所有逻辑包括,导致代码不利于修改,而且也不方便查看,有时候自己找半天也没找到想找的代码部分,在题目集6、7中便要求单一职责原则,因此题目集5的代码基本上没用,除了核心逻辑可以继承,需要根据类图重写代码,即使过程复杂,写了300来行代码,但从观赏性和可维护性角度都有提高,甚至修改错误时也更加方便,由于复用的提高,题目集7写起来就比较轻松。
四、改进建议
1.单一职责:即便题目集6、7已经很大程度遵循单一职责,通过 SourceMonitor 生成的报表可以看出,电梯的核心Controller类复杂度还是较高的,应分化其功能降低耦合度,这样不仅降低代码复杂度,还可以提高代码的可复用性。
2.命名规范:这几次的题目集完整类名、变量名都是非常长且多的,有时候为了偷懒用一个字母代替,虽然能获一时之便利,但过了几天,自己都看不懂自己的代码了,所以应严格执行驼峰命名法,保证命名的规范性,利于后续的修改。
3.增添注释:除了命名规范注释也十分重要,而我的三次代码中基本上是没有注释的,不写注释久而久之真成"垃圾代码"了。
五、总结
通过完成题目集 5 至 7 的练习,在面向对象编程方面有了更深入的理解和实践。学会了如何运用单一职责原则进行类的设计,将复杂的功能模块拆分成多个职责明确的类,提高了代码的质量和可维护性。在处理电梯调度问题的过程中,锻炼了逻辑思维能力和问题解决能力,学会了如何分析问题、设计算法并通过编程实现。
这几次中最难的应该算第一次了,因为后两次基本上是迭代核心逻辑没变,第一次做的时候真是很折磨感觉真是做不出来,在投入大量时间后,慢慢变得得心应手了些,到最后测试点通过的喜悦。
在学习过程中,也发现了自己的不足之处。对于算法的掌握不足在碰到这种难题时很难独自处理,这几次题目集真的是一次很大的挑战,也反映了上学期C语言基础功不扎实,在今后的学习中需多加练习和培养这方面的思维,提高动手能力。
建议:
1.对于像这种难题,希望能多给些测试点,以便有修改的思路。
2.每次题目集提交结束后,老师能给些提示,这样大家才会更愿意尝试,投入更多时间。

浙公网安备 33010602011771号