**一、前言:**
在这3周的pta大作业里,分别写了有关电梯调度程序迭代3次大作业,分别考察了对于电梯运行逻辑的规划、类的设计、电梯调度算法的使用等等,总的代码量有400行左右。第一次大作业主要侧重于代码设计和实现,而第二次代码是在第一次代码的前提下进行迭代性类设计,要求必须包含但不限于设计电梯类、乘客请求类、队列类以及控制类,因此在第一次代码的类设计及其重要(不然在第二次代码的书写量要大幅度增加);而第三次代码则是改变输入模式和修改乘客请求类为乘客类,本质就是修改队列的输入储存。
在通过这几次大作业,个人感觉第一次大作业难度最大,需要在题目信息量及其少的情况下去推测题目可能的意思,包含的可能性,比如输入里既有 3 楼的上行请求,又有 5 楼、7 楼的内部请求,如果电梯从 1 楼出发,正确逻辑应该是先往上走,到 3 楼接人,再继续到 5 楼、7 楼送人,而不是先冲到 7 楼再下来处理 3 楼。这种 “顺路处理” 的逻辑和现实中的电梯运行完全一致,但我最开始根本没考虑到,导致写出来的代码只能处理部分简单情况,稍微复杂的请求组合就出错。而第二次代码则是在第一次代码的基础上除了分类外就是缝缝补补修改一些没有正确输出的结果、漏洞。
第三次代码就是换汤不换药了,相对容易一些。
在写第一次大作业的时候,我看题目给的输入我以为这不就是直接电梯运行到最顶层然后运行到最底层,如果有需要开门关门则输出开关门对应函数。实际情况这样写代码看似正确其实完全错误,仔细看题目要求:电梯运行规则如下:电梯默认停留在1层,状态为静止,当有乘客对电梯发起请求时(各楼层电梯外部乘客按下上行或者下行按钮或者电梯内部乘客按下想要到达的楼层数字按钮),电梯开始移动,当电梯向某个方向移动时,优先处理同方向的请求,当同方向的请求均被处理完毕然后再处理相反方向的请求。电梯运行过程中的状态包括停止、移动中、开门、关门等状态。当电梯停止时,如果有新的请求,就根据请求的方向或位置决定移动方向。电梯在运行到某一楼层时,检查当前是否有请求(访问电梯内请求队列和电梯外请求队列),然后据此决定移动方向。每次移动一个楼层,检查是否有需要停靠的请求,如果有,则开门,处理该楼层的请求,然后关门继续移动。电梯必须符合**是否优先处理同方向的请求,是否在移动过程中处理顺路的请求**,所以仔细审题还是很关键的。
**二、设计与分析**
**第一次大作业**
1. 在第一次大作业,输入:
1
20
< 3,UP>
<5>
<6,DOWN>
<7>
<3>
end,则分别将这些输入入队到电梯内外队列,结果:
电梯内: InnerQueue <5> <7> <3> 电梯外: OuterQueue < 3,UP> <6,DOWN>
对于这2段队列,如何处理寻找下一个应该寻找的元素是最主要的问题,我代码里采用了一个方法`private near_Req findNearestReq(Dir former_dir)`用来寻找下一个应该到底的目的楼层,其中Dir former_dir是当前楼层的方向。这边以上面的输入为例,寻找方法应该为从2段队列的队首进行比较,优先寻找方向相同,然后寻找距离较近的作为下一个目标楼层,这边具体分析了一下查找思路:
```
| 找下一个目标楼层 | 电梯内队列 | 电梯外队列 | 当前楼层 | 当前楼层方向 | | ----------------- | ------------ | ------------ | -------------| ------------ | | 3 | 5 | 3,up | 1 | IDLE | | 5 | 5 | 6,DOWN | 3 | UP | | 7 | 7 | 6,DOWN | 5 | UP | | 6 | 3 | NULL | 7 | DOWN | | 3 | NULL | NULL | 6 | DOWN | | NULL | NULL | NULL | 3 | DOWN |
```
SourceMontor的生成报表内容:

**分析:**根据报表内容可见,第一次大作业虽然成功了,但是我的代码存在一个很大问题,就是几乎所有职责都存放在方法`private near_Req findNearestReq(Dir former_dir)`,而且使用了较多的if else导致代码深度过大,平均复杂度 5.20,但被 findNearestReq() 这类 “极端复杂方法” 拉高,说明代码逻辑分布极不均衡,可能导致代码极其难以维护,所以第一次代码虽然通过了但是仍然有许多问题显现出来;比较好的一点就是类平均方法数 4.17 个,整体结构不算太糟,但职责分配不合理**(所以第二次代码改善了代码结构,重构了电梯类的方法)**
**第二次大作业:**
在第二次大作业,对该程序进行了分类,目的为解决电梯类职责过多的问题,类设计要求遵循单一职责原则(SRP),要求必须包含但不限于设计电梯类、乘客请求类、队列类以及控制类。
这次大作业除了对于一些结构的改动外,我觉得最大的改动就是将输出定义为方法,而不是单一的`system.out,println`,并且将一些稀碎化的方法像电梯上升定义为电梯类里的` public void moveUp()`,总的来说就是各司其职,互不侵犯,将一些可能重复用到的代码封装为对应的类的方法,便于使用也就是解决电梯类职责过多的问题,这边就不给代码了。
第二次作业相对于第一次作业除了重构外就是修改一些过不了的代码,第一次代码能通过第二次可能有些地方通过不了,这边需要自己去逐例测试。
SourceMontor的生成报表内容:

第二次大作业对比第一次大作业,代码经过一些结构调整后,代码整体指标有所提升,平均复杂度从 5.20 降至 1.77,整体代码逻辑清晰,无 “逻辑堆砌” 的方法;最大块深度从 9+ 降至 4,平均块深度从 5.19 降至 1.49;方法分布更均衡(平均每个类 5.43 个方法),代码质量有了显著提升。
**第三次大作业:**
第三次大作业相对于第二次大作业的区别就是修改了电梯请求类为电梯类
SourceMontor的生成报表内容:

(第三次大作业与第二次大作业各项指标变化不大,这边不做分析)
PowerDesigner的相应类图:

**(3)采坑心得**
第一次大作业:
对电梯执行过程不理解,最开始以为是单纯的电梯从下往上运行后在往下运行一遍。实际电梯运行是根据电梯队列(外部队列,内部队列)来判断,并且必须同方向优先、短距离优先。
若电梯方向与内部队列运行方向相同,且与外部队列运行方向相同,则比较短距离优先;若电梯方向与内部队列运行方向相同,且与外部队列运行方向不相同,则优先选取内部队列;若电梯方向与内部队列运行方向不相同,且与外部队列运行方向相同,则判断电梯到内部队列是UP还是DOWN方向,若电梯到内部队列方向与外部队列相同,则比较短距离优先,若电梯到内部队列方向与外部队列不相同,则优先选择外部队列;若电梯方向与内部队列运行方向不相同,且与外部队列运行方向不相同,则选取短距离优先;(若各种条件均相同就优先内部队列)
写第一次大作业的时候PTA一直通过不了,后来寻找相关资料,并通过以下图片的作者了解到了一些输入输出(输入参考这位作者里面的输入输出)
NCHU_OOP_单部电梯调度程序 - 白蓉 - 博客园
第二次大作业:
第二次大作业相对于第一次大作业的区别就是对代码进行重构分类,没有较大问题,除了在方法`private near_Req findNearestReq(Dir former_dir)`设计不好导致代码维护难度过大容易出现问题外其他均比较正常。
第三次大作业:
第三次大作业可以说是踩坑点最让我印象深刻的一次。在最开始的时候,由于题目要求对之前电梯调度程序再次进行迭代性设计,加入乘客类(Passenger),取消乘客请求类,类设计要求遵循单一职责原则(SRP),要求必须包含但不限于设计电梯类、乘客类、队列类以及控制类,并且输入改成了:
输入格式:
第一行输入最小电梯楼层数。
第二行输入最大电梯楼层数。
从第三行开始每行输入代表一个乘客请求。
电梯内乘客请求格式:<楼层数>
电梯外乘客请求格式:<请求源楼层,请求目的楼层>,其中,请求源楼层表示乘客发起请求所在的楼层,请求目的楼层表示乘客想要到达的楼层。
当输入“end”时代表输入结束(end不区分大小写)。
问题在于最开始我把乘客请求类改成了乘客类,并且在里面新添加一个目标楼层队列> `private Queue<Integer> outerTargetQueue = new LinkedList<>();`,将外部队列的<3,4>里面的4存储进去目标楼层队列。当所以数据都存储完全后将目标楼层队列完全入队内部队列队尾。但是一直通过不了PTA第一个问题(其他3个都通过了)。然后我寻找各种测试代码一一测试,最后在一个测试案例里找到了问题。
输入: `3 10 <10,7> <8,10> <7> end`
正确输出应该是:
``` `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 Current Floor: 6 Direction: UP Current Floor: 7 Direction: UP Open Door # Floor 7 Close Door Current Floor: 8 Direction: UP Current Floor: 9 Direction: UP Current Floor: 10 Direction: UP Open Door # Floor 10 Close Door Current Floor: 9 Direction: DOWN Current Floor: 8 Direction: DOWN Current Floor: 7 Direction: DOWN Open Door # Floor 7 Close Door Current Floor: 8 Direction: UP Open Door # Floor 8 Close Door Current Floor: 9 Direction: UP Current Floor: 10 Direction: UP Open Door # Floor 10 Close Door` ```
,
但是我的输出却是
`
`` `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 Current Floor: 6 Direction: UP Current Floor: 7 Direction: UP Open Door # Floor 7 Close Door Current Floor: 8 Direction: UP Open Door # Floor 8 Close Door Current Floor: 9 Direction: UP Current Floor: 10 Direction: UP Open Door # Floor 10 Close Door ```
后来我分析我的代码逻辑,一遇到相同就丢弃不输出,但是这与题目逻辑不相同,这边查看题目要求:**对于外部请求,当电梯处理该请求之后(该请求出队),要将<请求源楼层,请求目的楼层>中的请求目的楼层加入到请求内部队列(加到队尾)**。这时候应该猜到原因了,我的代码逻辑是将全部目标楼层队列入队到内部队列后按照原来的逻辑去寻找下一个需要开门的队列,但是对于上面那一个输入,如果按照我的逻辑,那么内部队列和外部队列分别为:
```
内部:<7>,<7>,<10>
外部:<10,down>,<8,up>
```
这时候明显<7>,<10>会被抹去。但是题目要求的是“电梯处理完外部逻辑后,将目标楼层队列队首入队内部队列”,而我原本代码就很可能导致外部队列还没被处理,对应的目标楼层队列就被处理了,不满足顺时要求。于是我在原来的代码基础上添加一个目标楼层队列入队内部队列的方法`public void addTi()`,在外部队列出队后使用入队内部队列方法,保证电梯顺时要求。
则正确的队列应该为:
```
内部:<7> 外部:null,<7>出队
内部:null 外部:<10,down>,<10,down>出队,同时<7>入队
内部:<7> 外部: <8,up>,<8,up>入队,比较后<7>出队
内部:null 外部: <8,up>
内部:<10> 外部: null,<8,up>出队,同时<10>入队
内部:null 外部: null,<10>出队,队列均为空,结束
```
findnextreq方法的流程图如下:

**(4)改进建议:**
在这3次大作业中,我重构了带一次大作业的类结构,也优化了代码逻辑,避免一写代码比重不均,导致维护及其困难,但是我的代码还是有一些问题需要修改改进。
1. 比如,`findNearestReq() `方法逻辑过于臃肿,可按功能拆分为判断当前楼层请求、选择同方向请求、处理反方向请求等独立方法等等;
2. 并且在一些方法或者类的命名比较模糊,需要添加注释,说明设计意图,让代码更加简洁易懂;
3. 代码中if-else的使用过于频繁,可能导致维护困难,可以通过提前 return 简化多层 if-else 结构,例如先处理空请求、无效请求等边缘情况,再处理核心逻辑;
4. 我的代码在处理内部队列和外部队列均用同一个方法,可能分开成2个方法,遵循单一职责原则。
**(5)总结:**
本阶段通过这三周的电梯调度大作业,是从基础实现到逐步优化的完整过程。第一次作业时,我重点搞定了电梯同方向、短距离优先的核心调度逻辑,但把大部分功能都堆在`findNearestReq()`这一个方法里,导致代码复杂,维护困难。第二次作业就围绕“一个类/方法只干一件事”来重构,拆分出电梯、请求、队列、控制这几个类,还把重复用的功能封装成独立方法,代码复杂度降了不少,结构也清晰多了。第三次作业主要适配新的输入格式和乘客类,中间因为没做好外部请求目标楼层的实时入队,卡了挺久,最后加了`addTi()`方法才解决,也让我更注意处理逻辑里的细节。
通过这三次作业,我对面向对象里的类职责划分、封装复用有了更实际的认识,也学会了怎么重构代码、优化性能,明白单一职责原则对代码维护有多重要。调试的时候,也慢慢能精准找到逻辑漏洞,尤其是处理队列交互这种复杂场景时。
不过还有不少要改进的地方:比如复杂方法得再拆细点,变量和方法名得更清楚,注释也得补全;调度算法可以试试用设计模式优化,让扩展更方便;另外异常处理和边界情况的测试也得加强,让代码更稳。之后会多关注这些点,把代码质量和可扩展性做得更好。