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

无设计,代码所有功能均由Lift类实现。
代码分析


- 代码共 303 行,含 2 个类,平均每个类有 4.5 个方法,方法平均语句数为 17.11 ,分支语句占比 29.5% 。
- 平均复杂度 7.44* ,最复杂方法是Lift.getTarget(),位于 111 行,包含 85 条语句,复杂度以及达到了 40* 。
- 最大块深度为 8 ,平均块深度 4.09 ,依然是Lift.getTarget(),非常复杂。
- 不同块深度对应语句数有差异,深度为 2 时语句数较多有 36 条,深度 8 时有 32 条语句 ,可以看出代码结构复杂程度。
- 总之,代码的平均复杂度、最大复杂度以及最大深度指标表现欠佳,说明代码中部分方法或结构存在较高的逻辑复杂性,理解和维护难度较大。并且,在一些地方上语句数量明显较多,代码中存在较深嵌套和较大规模的代码块,结构不够简洁。这是由于代码逻辑设计较差,方法内部嵌套过多控制结构等原因导致。后续可通过重构代码,减少不必要的嵌套,优化算法逻辑,增加注释等方式,降低代码复杂度,提升代码可读性与可维护性。
总结与心得
第1次代码实现的过程很艰难。因为只有一个测试用例,刚上手的时候完全没搞清楚逻辑是什么。所以前前后后改了很多次,最后再仔细研究了老师给的电梯运行过程详解,听了老师的讲解之后,终于搞明白了题目是什么意思。花了很多时间后,最后还是做出来了。总的来说,这次代码是比较糟糕的。不过在完成这次题目集的过程中,我对电梯运行模拟有自己的理解:
- 根据电梯的运行方向,位置,和队列请求信息得到电梯与请求的相对位置,相对方向,和电梯的预期方向。
- 根据上述信息,基于方向优先原则,在内外请求中做出选择,得到电梯前往的目标位置。
- 前往目标位置,并删除队列头元素。
- 重复述过程直到完成所有请求。
这为后面两次题目的完成打下了良好的基础。
第二次电梯调度程序
题目:
对之前电梯调度程序进行迭代性设计,目的为解决电梯类职责过多的问题,类设计要求遵循单一职责原则(SRP),要求必须包含但不限于设计电梯类、乘客请求类、队列类以及控制类,具体设计可参考如下类图。

电梯运行规则与前阶段单类设计相同,但要处理如下情况:
- 乘客请求楼层数有误,具体为高于最高楼层数或低于最低楼层数,处理方法:程序自动忽略此类输入,继续执行
- 乘客请求不合理,具体为输入时出现连续的相同请求,例如<3><3><3>或者<5,DOWN><5,DOWN>,处理方法:程序自动忽略相同的多余输入,继续执行,例如<3><3><3>过滤为<3>
输入格式:
同一
输出格式:
同一
输入样例1:
1
20
<3,UP>
<5>
<6,DOWN>
<7>
<3>
end
输出样例1:
Current Floor: 1 Direction: UP
Current Floor: 2 Direction: UP
Current Floor: 3 Direction: UP
Open Door # Floor 3
Close Door
Current Floor: 4 Direction: UP
Current Floor: 5 Direction: UP
Open Door # Floor 5
Close Door
Current Floor: 6 Direction: UP
Current Floor: 7 Direction: UP
Open Door # Floor 7
Close Door
Current Floor: 6 Direction: DOWN
Open Door # Floor 6
Close Door
Current Floor: 5 Direction: DOWN
Current Floor: 4 Direction: DOWN
Current Floor: 3 Direction: DOWN
Open Door # Floor 3
Close Door
输入样例2:
1
20
<3,UP>
<3,UP>
<5>
<5>
<5>
<6,DOWN>
<7>
<7>
<3>
<22,DOWN>
<5,DOWN>
<30>
END
输出样例2:
Current Floor: 1 Direction: UP
Current Floor: 2 Direction: UP
Current Floor: 3 Direction: UP
Open Door # Floor 3
Close Door
Current Floor: 4 Direction: UP
Current Floor: 5 Direction: UP
Open Door # Floor 5
Close Door
Current Floor: 6 Direction: UP
Current Floor: 7 Direction: UP
Open Door # Floor 7
Close Door
Current Floor: 6 Direction: DOWN
Open Door # Floor 6
Close Door
Current Floor: 5 Direction: DOWN
Open Door # Floor 5
Close Door
Current Floor: 4 Direction: DOWN
Current Floor: 3 Direction: DOWN
Open Door # Floor 3
Close Door
类图设计

代码分析


- 本次代码增至 399 行,类和接口数量从 2 个提升到 7 个,平均每个类方法数从 4.5 变为 5.43 。
- 平均复杂度降至 2.24* ,最复杂的方法为 Controller.determineTarget()(即原Lift.getTarget()),复杂度达 14* ,相较于上次最复杂方法的 40* 有大幅降低。代码复杂度优化明显,最复杂的方法得到了很大改善。
- 最大块深度 6 ,平均块深度 2.34 ,深度 4 及以上语句数明显减少,深度 7 - 9 + 无语句分布 ,大部分代码逻辑集中在较浅深度。较上次进步明显。
总结与心得
相较于第一次的程序有明显改善,本次程序进行了遵循SRP的类设计,添加了排除错误输入的新功能,并且优化了算法。写代码的过程依旧很折磨,首次提交的代码基本上就是将第一次的程序由一个类分为多个类,算法没有任何修改。结果提交上去始终是部分正确,测试点1一直无法通过。在经过多次试错以及和同学的交流之后,终于确定是选择目标算法的问题。在重新设计了选择目标算法后测试点1终于通过。
第三次电梯调度程序
题目:
对之前电梯调度程序再次进行迭代性设计,加入乘客类(Passenger),取消乘客请求类,类设计要求遵循单一职责原则(SRP),要求必须包含但不限于设计电梯类、乘客类、队列类以及控制类,具体设计可参考如下类图。

-
电梯运行规则与前阶段相同,但有如下变动情况:
-
乘客请求输入变动情况:外部请求由之前的<请求楼层数,请求方向>修改为<请求源楼层,请求目的楼层>
对于外部请求,当电梯处理该请求之后(该请求出队),要将<请求源楼层,请求目的楼层>中的请求目的楼层加入到请求内部队列(加到队尾)
输入格式:
同一
输出格式:
同一
输入样例1:
1
20
<5,4>
<5>
<7>
end
输出样例1:
Current Floor: 1 Direction: UP
Current Floor: 2 Direction: UP
Current Floor: 3 Direction: UP
Current Floor: 4 Direction: UP
Current Floor: 5 Direction: UP
Open Door # Floor 5
Close Door
Current Floor: 6 Direction: UP
Current Floor: 7 Direction: UP
Open Door # Floor 7
Close Door
Current Floor: 6 Direction: DOWN
Current Floor: 5 Direction: DOWN
Open Door # Floor 5
Close Door
Current Floor: 4 Direction: DOWN
Open Door # Floor 4
Close Door
输入样例2:
1
20
<5,9>
<8>
<9,3>
<4>
<2>
end
输出样例2:
Current Floor: 1 Direction: UP
Current Floor: 2 Direction: UP
Current Floor: 3 Direction: UP
Current Floor: 4 Direction: UP
Current Floor: 5 Direction: UP
Open Door # Floor 5
Close Door
Current Floor: 6 Direction: UP
Current Floor: 7 Direction: UP
Current Floor: 8 Direction: UP
Open Door # Floor 8
Close Door
Current Floor: 9 Direction: UP
Open Door # Floor 9
Close Door
Current Floor: 8 Direction: DOWN
Current Floor: 7 Direction: DOWN
Current Floor: 6 Direction: DOWN
Current Floor: 5 Direction: DOWN
Current Floor: 4 Direction: DOWN
Open Door # Floor 4
Close Door
Current Floor: 3 Direction: DOWN
Current Floor: 2 Direction: DOWN
Open Door # Floor 2
Close Door
Current Floor: 3 Direction: UP
Current Floor: 4 Direction: UP
Current Floor: 5 Direction: UP
Current Floor: 6 Direction: UP
Current Floor: 7 Direction: UP
Current Floor: 8 Direction: UP
Current Floor: 9 Direction: UP
Open Door # Floor 9
Close Door
Current Floor: 8 Direction: DOWN
Current Floor: 7 Direction: DOWN
Current Floor: 6 Direction: DOWN
Current Floor: 5 Direction: DOWN
Current Floor: 4 Direction: DOWN
Current Floor: 3 Direction: DOWN
Open Door # Floor 3
Close Door
类图设计

代码分析


- 本次代码行数从 399 增至 420 ,类和接口数量不变仍为 7 个,但平均每个类方法数从 5.43 变为 5.86 ,方法平均语句数从 4.45 变为 4.34。
- 平均复杂度从 2.24* 微降至 2.17* ,最大复杂度维持在 14* (仍是 Controller.determineTarget() ),整体复杂度基本稳定,未出现大幅波动。
- 最大块深度和平均块深度变化不大,分别从 6 和 2.34 变为 6 和 2.48 。
总结与心得
对于涉及队列请求的类和算法都进行了重新设计,再次改进算法。提交后发现还是有测试点未通过,居然还是非零返回。然后一个一个地排查问题,结果发现是程序在开始运行是会尝试读取内外两个队列(没有考虑只输入一个队列的情况)结果发生了非零返回,修改后顺利通过。
踩坑心得
最大的坑
最大的坑就是算法的问题,对于如何模拟电梯运行,我实际上有两种构想,第一种:电梯每运行一层,就根据当前的位置与队列请求的信息判断运行的方向和是否停止。第二种:即前文所述。但由于最开始写的时候对题目理解不清晰,导致并没有正确的实现一个合理的思路,后续老师讲解题目的时候又已经开始使用第二种思路。思来想去,最后还是懒得改了。错误的思路带来的结果就是这三次题目集的电梯运行模拟实现,给我的体验都极其糟糕。就好比盲人摸象,一次又一次的修改算法,得到的反馈却只有正确和错误。在无数错误的反馈中,像无头苍蝇一样乱转,最终侥幸蒙对。



其他的问题
- 比较明显的问题,程序处理输入数据的时候使用字符串数组,有需求的时候再解析成数据。,主打一个凑合就行,不仅提高了程序的复杂度,降低了健壮性。还导致程序可扩展性比较差,最后还是得改用LinkedList。
- 其次,还是思路的问题,因为采用了第二种思路,导致程序选择目标与前往目标的过程不一致。而这两者又共用Lift类的方向属性。使得程序的输出一些问题。
如图:
![]()
解决方案是程序前往目标输出时不使用Lift类的方向属性,额外添加一个临时方向属性专供输出时使用。
杂项:
- end不区分大小写,在浏览讨论区发现。
- 输入时判断内外请求直接根据长度判断,导致误判。参考老师的的源码后改进。
- 图方便有很多应该get的数据直接被构造作为属性,导致类间关系非常复杂。后续改进了一部分。
改进建议
- 选择目标算法依旧非常复杂,仍有优化空间。
- 所有代码均无注释(唯一的注释还是该老师给的参考源码忘记删了),使得阅读代码非常困难(自己都看不懂……)。
- 代码一路迭代,很多地方还是在第一次代码的基础是进行删改的,存在不安全的操作。
- 个别方法存在重复操作的问题,实际上存在优化空间,如:
private boolean determineTarget()
{
if (!Queue.getInnerQueue().isEmpty() && !Queue.getOutQueue().isEmpty())
{
if (getInToDirection() == getOutToDirection())
{
if (getOutToDirection() == Queue.getOutQueue().getFirst().getDirection())
{
if (Math.abs(Queue.getInnerQueue().getFirst().getDirectionFloor() - lift.getCurrentFloor()) >= Math.abs(Queue.getOutQueue().getFirst().getSourceFloor() - lift.getCurrentFloor()))
{
target.setFloor(Queue.getOutQueue().getFirst().getSourceFloor());
target.setDirection(getOutToDirection());
lift.setDirection(Queue.getOutQueue().getFirst().getDirection());
deleteOutQueueHead();
return true;
}
else
{
target.setFloor(Queue.getInnerQueue().getFirst().getDirectionFloor());
target.setDirection(getInToDirection());
lift.setDirection(getInToDirection());
deleteInnerQueueHead();
return true;
}
}
else
{
target.setFloor(Queue.getInnerQueue().getFirst().getDirectionFloor());
target.setDirection(getInToDirection());
lift.setDirection(getInToDirection());
deleteInnerQueueHead();
return true;
}
}
else
{
if (lift.getDirection() == getInToDirection())
{
target.setFloor(Queue.getInnerQueue().getFirst().getDirectionFloor());
target.setDirection(getInToDirection());
lift.setDirection(getInToDirection());
deleteInnerQueueHead();
return true;
}
else
{
target.setFloor(Queue.getOutQueue().getFirst().getSourceFloor());
target.setDirection(getOutToDirection());
lift.setDirection(Queue.getOutQueue().getFirst().getDirection());
deleteOutQueueHead();
return true;
}
}
}
else if (!Queue.getInnerQueue().isEmpty())
{
getInToDirection();
target.setFloor(Queue.getInnerQueue().getFirst().getDirectionFloor());
target.setDirection(getInToDirection());
lift.setDirection(getInToDirection());
deleteInnerQueueHead();
return true;
}
else if (!Queue.getOutQueue().isEmpty())
{
getOutToDirection();
target.setFloor(Queue.getOutQueue().getFirst().getSourceFloor());
target.setDirection(getOutToDirection());
lift.setDirection(Queue.getOutQueue().getFirst().getDirection());
deleteOutQueueHead();
return true;
}
else
{
return false;
}
}
总结
通过这3次的题目集,我学习了很多知识,比如:LinkedList类以及一些常用方法,还有泛型的使用等等。在完成题目的过程,我也意识到需求分析能够掌握正确方向,避免浪费时间。还有类设计的重要性,合理的类设计不仅能够,还能清晰的把握从需求分析到代码实现的过程,提高编码效率,事半功倍。
这次阶段性作业对我来说是一次充分的历练,给我的逻辑思维能力和编码能力带来了巨大的提升,使我获益匪浅。
最后,线下课老师讲课的知识点很密集,但是一些地方很不连贯,希望老师能够给出更多的时间让学生思考。以及pta的题目可以给出更多的测试点,让学生更好地理解题意。


浙公网安备 33010602011771号