题目集5~7的总结性Blog
一、前言
经过了几周的Java学习,我感受到了Java与上学期C语言的不同之处。Java更加注重面向对象的过程,也更加抽象。Java更高的抽象层次,以面向对象编程为核心,将现实世界的事物抽象为类与对象,通过封装、继承和多态构建出层次分明的程序结构。这种思维方式的转变,让我刚开始的时候难以适应。在完成题目集 5 - 7 的作业时,我遇到了较大挑战,但是这也让我从中学到了很多新的知识,比如枚举类型的使用(在电梯调度系统中,电梯运行方向和状态固定有限,所以定义了 Direction 枚举,包含 UP、DOWN 和 NONE,代码中判断电梯方向时清晰明了,且具有类型安全特性,避免了混淆和错误。)、使用 Queue 接口和 LinkedList 类来实现请求队列的方法(通过创建 Queue
二、设计与分析
第一次迭代
题目:
设计一个电梯类,具体包含电梯的最大楼层数、最小楼层数(默认为1层)当前楼层、运行方向、运行状态,以及电梯内部乘客的请求队列和电梯外部楼层乘客的请求队列,其中,电梯外部请求队列需要区分上行和下行。
电梯运行规则如下:电梯默认停留在1层,状态为静止,当有乘客对电梯发起请求时(各楼层电梯外部乘客按下上行或者下行按钮或者电梯内部乘客按下想要到达的楼层数字按钮),电梯开始移动,当电梯向某个方向移动时,优先处理同方向的请求,当同方向的请求均被处理完毕然后再处理相反方向的请求。电梯运行过程中的状态包括停止、移动中、开门、关门等状态。当电梯停止时,如果有新的请求,就根据请求的方向或位置决定移动方向。电梯在运行到某一楼层时,检查当前是否有请求(访问电梯内请求队列和电梯外请求队列),然后据此决定移动方向。每次移动一个楼层,检查是否有需要停靠的请求,如果有,则开门,处理该楼层的请求,然后关门继续移动。
使用键盘模拟输入乘客的请求,此时要注意处理无效请求情况,例如无效楼层请求,比如超过大楼的最高或最低楼层。还需要考虑电梯的空闲状态,当没有请求时,电梯停留在当前楼层。
请编写一个Java程序,设计一个电梯类,包含状态管理、请求队列管理以及调度算法,并使用一些测试用例,模拟不同的请求顺序,观察电梯的行为是否符合预期,比如是否优先处理同方向的请求,是否在移动过程中处理顺路的请求等。为了降低编程难度,不考虑同时有多个乘客请求同时发生的情况,即采用串行处理乘客的请求方式(电梯只按照规则响应请求队列中当前的乘客请求,响应结束后再响应下一个请求),具体运行规则详见输入输出样例。
输入格式:
第一行输入最小电梯楼层数。
第二行输入最大电梯楼层数。
从第三行开始每行输入代表一个乘客请求。
电梯内乘客请求格式:<楼层数>
电梯外乘客请求格式:<乘客所在楼层数,乘梯方向>,其中,乘梯方向用UP代表上行,用DOWN代表下行(UP、DOWN必须大写)。
当输入“end”时代表输入结束(end不区分大小写)。
输出格式:
模拟电梯的运行过程,输出方式如下:
运行到某一楼层(不需要停留开门),输出一行文本:
Current Floor: 楼层数 Direction: 方向
运行到某一楼层(需要停留开门)输出两行文本:
Open Door # Floor 楼层数
Close Door
SourceMonitor分析

根据分析可以看出
平均方法/类:2.00
每个方法的平均语句:20.50
平均复杂度:4.50
最大复杂度:8
设计类图

分析
在深入了解题目的意思之后我发现电梯运行的规则中需要注意的一个重要内部逻辑是判断电梯方向是要遵循方向优先的原则,电梯向某个方向开始移动后,会优先处理内部和外部的两个第一个请求中同方向的那一个。
代码的设计:Elevator类
属性有minFloor,maxFloor,currentFloor(记录当前楼层),direction(记录电梯当前运行的方向),status(记录电梯当前运行状态)。
主要方法:
addInternalRequest(int floor):储存内部指令
addExternalRequest(int floor, Direction dir):储存外部指令
getNextFloor():根据请求判断下一个到达的楼层
determineDirection():根据请求判断电梯的方向
shouldStop():判断再该层是否需要开门
历程与心得
一开始看到这个题目,我并没有深入去了解这个题目的核心要求,而是根据自己的经验(外部有乘客做出与电梯同方向的请求时,会开门),但是在上课老师的讲解下才知道题目是一次性按序接收所有的请求,然后再执行两个队列的第一个请求。然后题目明确给出要设计电梯类,理解了这些之后我开始根据这些写代码。最后的时候一个样例运行出来是对的了,这也是让我看到了希望,但是当我把老师发的测试用例进行输入的时候,发现结果跟预期的不一样。
1
20
❤️,DOWN>
<2>
<5,UP>
<1>
<4,DOWN>
<3>
<2,DOWN>
<8>
<4>
END
预期输出:
Current Floor: 1 Direction: UP
Current Floor: 2 Direction: UP
Open Door # Floor 2
Close Door
Current Floor: 3 Direction: UP
Open Door # Floor 3
Close Door
Current Floor: 2 Direction: DOWN
Current Floor: 1 Direction: DOWN
Open Door # Floor 1
Close Door
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
Current Floor: 8 Direction: UP
Open Door # Floor 8
Close Door
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: 1 Direction: UP
Current Floor: 2 Direction: UP
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: 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
Current Floor: 2 Direction: DOWN
Current Floor: 1 Direction: DOWN
Open Door # Floor 1
Close Door
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
Current Floor: 8 Direction: UP
Open Door # Floor 8
Close Door
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
我发现我的代码还是没有只处理两个队列的头指令,然后我就继续修改。将电梯运行的主逻辑进行修改。最后通过了老师发的第二个测试样例。然后根据SourceMonitor分析发现不合理的部分有几处,代码整体只写了一个Elevator类,它承担了较多的职责,违反了老师强调的单一职责原则。然后就是每个方法的平均语句为20.50较高,说明一个方法里面可能干了不止一件事,这对方法的后续使用不好。然后就是平均复杂的为4.50,偏高但是还好,下次写的时候也需要注意。从代码块直方图可以看出深度在0到3的语句较多,表明深度过高的复杂代码并不多。
第二次迭代
题目
对之前电梯调度程序进行迭代性设计,目的为解决电梯类职责过多的问题,类设计要求遵循单一职责原则(SRP),要求必须包含但不限于设计电梯类、乘客请求类、队列类以及控制类,具体设计可参考如下类图。
电梯运行规则与前阶段单类设计相同,但要处理如下情况:
乘客请求楼层数有误,具体为高于最高楼层数或低于最低楼层数,处理方法:程序自动忽略此类输入,继续执行
乘客请求不合理,具体为输入时出现连续的相同请求,例如<3><3><3>或者<5,DOWN><5,DOWN>,处理方法:程序自动忽略相同的多余输入,继续执行,例如<3><3><3>过滤为<3>
注意:本次作业类设计必须符合如上要求(包含但不限于乘客请求类、电梯类、请求队列类及控制类,其中控制类专门负责电梯调度过程),凡是不符合类设计要求此题不得分,另外,PTA得分代码界定为第一次提交的最高分代码(因此千万不要把第一次电梯程序提交到本次题目中测试)。
输入格式:
第一行输入最小电梯楼层数。
第二行输入最大电梯楼层数。
从第三行开始每行输入代表一个乘客请求。
电梯内乘客请求格式:<楼层数>
电梯外乘客请求格式:<乘客所在楼层数,乘梯方向>,其中,乘梯方向用UP代表上行,用DOWN代表下行(UP、DOWN必须大写)。
当输入“end”时代表输入结束(end不区分大小写)。
输出格式:
模拟电梯的运行过程,输出方式如下:
运行到某一楼层(不需要停留开门),输出一行文本:
Current Floor: 楼层数 Direction: 方向
运行到某一楼层(需要停留开门)输出两行文本:
Open Door # Floor 楼层数
Close Door
SourceMonitor分析

根据分析可以看出
最大复杂度:1
平均复杂度:1.00
设计类图

分析
第二次迭代中电梯的运行规则与上一次相同,对电梯运行逻辑的理解和实现仍然适用。所有这一次的作业要更加简单一点。就是要解决原代码中电梯类职责过多的情况,要遵循单一职责原则,也就是说每个类应该只负责一项明确的职责,从而提高代码的可读性、可维护性和可扩展性。
电梯类:实现电梯的基本功能,如上升、下降、开门、关门等操作,以及维护电梯的状态信息,如当前楼层、运行方向等。
乘客请求类:表示乘客的请求信息,包括请求楼层、请求方向等属性,并提供相应的方法来验证请求的合理性。
队列类:管理乘客请求队列,提供添加请求、获取请求、去重等方法。
控制类:负责电梯的调度过程,根据请求队列中的请求,决定电梯的运行方向和停靠楼层。
历程与心得
一开始做这个题目,发现它把类图给了出来,然后我就看着类图,一步一步把类写出来,然后把电梯里面的方法分离出来。虽然说一开始看到这个题目时感觉比较轻松,但是把职责分开后提交显示部分错误,但是我把pta上面有的测试样例都试了一下之后发现得到的结果跟预期的都是一样的,所有我也不知道应该往哪里改我的代码,后来问了同学,发现了一个测试样例用我的代码运行出来的结果是与预期不同的,分析了一下发现是内部逻辑还是有问题,但是具体又没发现错在哪。然后根据SourceMonitor分析,从代码块直方图可以看出深度在0到3的语句较多,表明深度过高的复杂代码也不多。
第二次迭代
题目
对之前电梯调度程序再次进行迭代性设计,加入乘客类(Passenger),取消乘客请求类,类设计要求遵循单一职责原则(SRP),要求必须包含但不限于设计电梯类、乘客类、队列类以及控制类,具体设计可参考如下类图。
类图.png
电梯运行规则与前阶段相同,但有如下变动情况:
乘客请求输入变动情况:外部请求由之前的<请求楼层数,请求方向>修改为<请求源楼层,请求目的楼层>
对于外部请求,当电梯处理该请求之后(该请求出队),要将<请求源楼层,请求目的楼层>中的请求目的楼层加入到请求内部队列(加到队尾)
注意:本次作业类设计必须符合如上要求(包含但不限于设计电梯类、乘客类、队列类以及控制类),凡是不符合类设计要求此题不得分,另外,PTA得分代码界定为第一次提交的最高分代码(因此千万不要把第一次及第二次电梯程序提交到本次题目中测试)。
输入格式:
第一行输入最小电梯楼层数。
第二行输入最大电梯楼层数。
从第三行开始每行输入代表一个乘客请求。
电梯内乘客请求格式:<楼层数>
电梯外乘客请求格式:<请求源楼层,请求目的楼层>,其中,请求源楼层表示乘客发起请求所在的楼层,请求目的楼层表示乘客想要到达的楼层。
当输入“end”时代表输入结束(end不区分大小写)。
输出格式:
模拟电梯的运行过程,输出方式如下:
运行到某一楼层(不需要停留开门),输出一行文本:
Current Floor: 楼层数 Direction: 方向
运行到某一楼层(需要停留开门)输出两行文本:
Open Door # Floor 楼层数
Close Door
SourceMonitor分析

设计类图

分析
这一次迭代添加了乘客类:表示乘客的信息,包括乘客的请求源楼层和请求目的楼层。可以考虑添加一些方法来表示乘客的行为,如乘客进入电梯、乘客离开电梯等。外部请求的格式从原来的 <请求楼层数,请求方向> 修改为 < 请求源楼层,请求目的楼层 >。这一变化要求在代码实现中,对输入的解析和处理方式进行相应调整。需要准确提取请求源楼层和请求目的楼层信息,并进行合理的存储和使用。这一次的迭代要合理设计各个类的方法,然后准确处理新的请求格式和请求处理后的操作。
历程与心得
因为这一题也是给了具体的类图,所以我就是根据类图添加了乘客这个类,然后将输入读取的形式进行了修改。根据SourceMonitor分析得到代码的最大复杂度为9,虽然不是太高,但是还是需要注意,因为这表明着代码中存在大量的条件判断和循环结构。这会很难全面考虑到所有可能的执行路径。一个小的修改可能会影响到其他看似不相关的部分,从而引入新的 bug。进一步分析可知,这一复杂度主要源于大量的条件判断和循环结构交织。在Controller类的processRequests、determineDirection等核心方法中,多层嵌套的if-else语句用于处理不同方向的请求优先级判断、请求队列的动态处理逻辑。所以下次写的时候需要注意复杂度不能过高,将复杂的逻辑拆分成多个简单的方法或类,提高代码的可读性和可维护性。
三、踩坑心得
1.刚开始接触这个题目的时候,没有特别理解这个代码的主要逻辑,只是想着根据自己的生活经验来写这个。但是真正的算法是先处理两个队伍队首的数据然后再根据现在的电梯方向来判断下一个要到的楼层。一开始对题目的错误理解导致我在设计算法时浪费了一段时间,还是在老师的讲解下才明白过来。
2.第一次迭代的时候,把代码完整写出来后一直显示运行超时,后来发现我的移除请求方法没有正确的移除当前楼层请求,还有就是边界条件处理的问题。在某些边界条件下,例如电梯到达最高或最低楼层时,没有正确处理请求,导致电梯无法继续前进或停止。
3.在pta上使用了上面给的测试样例运行出来是对的之后,但是提交还是显示部分错误,找不到问题所在,没有修改的方向。所以还是思考的不够全面,有很多测试点难以想到。

四、改进建议
1.要降低方法的复杂度,对于一些复杂度较高的方法,应该将这个方法差分为多个小方法,从而减少单一方法的圈复杂度。
2.避免过度嵌套,可以用switch方法来替代多层if-else的嵌套,从而防止代码难以全面考虑到所有可能的执行路径。
3.如果一个方法的参数过多,会增加方法的复杂度和调用的难度。可以将相关的参数封装成一个对象,作为方法的参数传递。例如,在电梯调度方法中,如果需要传递电梯的当前楼层、运行方向、请求队列等多个参数,可以将这些参数封装成一个 ElevatorContext 对象,将该对象作为方法的参数,这样不仅能减少方法的参数数量,还能提高代码的可读性和可维护性。
五、总结
这一次的大作业让我收获了很多知识点。
1)在代码迭代过程中,我更好的理解了类与对象的关系并深刻体会到单一职责原则的重要性。从最初将所有功能集中在一个类,到逐步拆分出多个职责明确的类,有效降低了类的复杂度,让每个类能够只完成属于自己的功能,使代码结构更加清晰。
2)在老师给的主函数里面我学会了try-catch的使用,在像电梯这种复杂的系统中会有一些难以预见的错误,try-catch就可以很好的捕获这些异常,从而更好地处理。
3)学到了Queue 接口中的 peek() 方法(查看队列的头部元素,也就是最早进入队列的元素)、poll() 方法(用于移除并返回队列的头部元素)等方法。
最后,我觉得刚拿到题目后应该先分析有哪些类,每个类的职责和类与类之间的关系,再设计好算法,然后再去写代码,而不是刚拿到题目就直接写代码。还有就是在写代码的过程中写上注释,而不是因为老师要求写注释而在完成代码后再一步一步加上注释。这次大作业是一次宝贵的学习机会,让我在实践中发现问题、解决问题,在未来的学习和项目开发中,我将以此次作业为基础,持续提升自己的编程能力和软件工程素养。
建议:
希望下次的题目能给更多的测试点,然后题目的主要逻辑更符合常识,容易理解。

浙公网安备 33010602011771号