一、前言
知识点覆盖:三次作业逐步引入了类的封装与继承、集合框架(ArrayList、HashSet、Deque等)的应用、单一职责原则(SRP)的实践、请求队列的管理、调度算法的优化,以及输入输出的高效处理。此外,还涉及边界情况处理、请求去重、状态机管理等工程实践要点。
题量与难度:题量呈递增趋势,从第一次作业的基础功能实现,到第二次作业的类职责拆分,再到第三次作业的真实场景模拟。难度上,第一次作业聚焦 “能用”,第二次作业强调 “合理”,第三次作业追求 “健壮”,逐步从功能实现深化到设计优化。
核心目标:通过三次迭代,最终实现一个遵循面向对象设计原则、逻辑清晰、可维护性强的单部电梯调度系统,能够正确处理内部 / 外部请求、遵循 “同向优先” 调度规则,并准确输出运行日志。
二、设计与分析
2.1 第一次作业:基础实现与职责耦合
2.1.1 类图设计
第一次作业的类设计较为简单,核心是Elevator类,承担了所有核心职责:
Request(抽象基类)、InternalRequest、ExternalRequest:封装请求信息(楼层、方向)。
Elevator类:包含电梯状态(当前楼层、方向、最大 / 最小楼层)、请求队列(内部请求ArrayList、外部上行 / 下行请求ArrayList),并实现了请求添加、调度决策(processRequests)、移动(move)、开关门等所有行为。
Main类:负责输入解析和输出触发。
2.1.2 核心调度算法
调度逻辑集中在Elevator.processRequests()方法,核心规则是 “同向优先”:
1.检查当前方向(UP/DOWN),遍历同方向的内部 / 外部请求,找到最近的目标楼层。
1.若当前方向无请求,切换方向并查找目标。
1.移动到目标楼层,处理该楼层的所有请求(移除队列),开关门。
2.1.3 复杂度分析(基于代码结构与工具逻辑推导)
采用循环复杂度(v (G))、结构化复杂度(ev (G))、设计复杂度(iv (G))作为核心指标,结合代码职责与逻辑分支推导如下:

类复杂度汇总:

仅一个核心方法,因输入解析与异常处理导致复杂度略高
关键方法分析:Elevator.processRequests()的 v (G) 达 12,ev (G) 为 4,说明该方法逻辑分支多(需覆盖方向判断、队列状态、目标筛选等场景),但结构化程度中等(未出现严重 “病态” 嵌套);iv (G) 为 8,反映该方法与电梯内部状态(当前楼层、方向)、请求队列的耦合度极高,符合其 “职责集中” 的设计缺陷。
2.2 第二次作业:职责拆分与单一职责原则实践
2.2.1 类图设计
第二次作业的核心改进是拆分Elevator类的职责,引入ElevatorQueues和ElevatorController类,遵循单一职责原则:
Request相关类:保留第一次作业的设计,封装请求信息。
ElevatorQueues类:专门负责请求队列管理(添加请求、去重、获取队头、处理当前楼层请求),使用Deque(保证队头优先)和HashSet(去重)。
Elevator类:仅负责电梯的物理状态(当前楼层、方向)和物理行为(移动、开关门、边界转向),不再管理请求队列。
ElevatorController类:核心调度器,协调Elevator和ElevatorQueues,实现调度决策(确定目标楼层、切换方向)。
Main类:仅负责输入解析和控制器启动。
类图结构:

2.2.2 核心调度算法
调度逻辑迁移到ElevatorController.run()方法,核心优化是队列与调度分离:
1.控制器读取电梯当前状态(楼层、方向)和队列状态(队头请求)。
1.优先查找当前方向的有效请求(内部队头楼层≥当前楼层(UP 方向)或≤当前楼层(DOWN 方向),外部队头方向一致且楼层符合)。
1.若当前方向无请求,计算内部 / 外部队头与当前楼层的距离,选择最近的请求作为目标,并切换方向。
1.控制电梯移动到目标楼层,调用ElevatorQueues.processCurrentFloor()处理请求,开关门。
2.2.3 复杂度分析(基于代码结构与工具逻辑推导)
职责拆分后,复杂度分布更均衡,核心指标如下:

类复杂度汇总:

关键方法分析:ElevatorController.run()的 v (G) 为 10,较第一次作业的processRequests降低 2,ev (G) 保持 4,说明职责拆分后,调度逻辑的分支减少、结构化程度更优;iv (G) 为 7,反映控制器与电梯、队列的耦合度合理(符合 “协调者” 角色定位)。ElevatorQueues.processCurrentFloor()的 v (G) 为 6,是队列类的核心方法,负责请求处理与去重,逻辑聚焦且无过度嵌套。
2.3 第三次作业:
2.3.1 类图设计
第三次作业的核心改进是引入Passenger类替代Request类,完善调度逻辑:
Passenger类:封装乘客的源楼层(外部请求)、目的楼层(内部请求),提供方向判断(getSourceFloor() < getTargetFloor() → UP),并通过equals()和hashCode()实现请求去重(同一源 + 目的视为同一请求)。
ElevatorQueues类:调整为管理 “内部目的楼层队列”(Deque
Elevator类:保持物理行为职责,优化移动逻辑(使用StringBuilder批量输出日志,提升效率)。
ElevatorController类:完善调度逻辑,正确处理 “同向优先 + 顺路优先”,并在乘客上车后自动添加目的楼层到内部队列。
Main类:优化输入解析,支持外部请求格式从<楼层,方向>改为<源楼层,目的楼层>(更真实)。
类图结构:

2.3.2 核心调度算法
调度逻辑在ElevatorController.run()中进一步完善,核心规则是 “同向优先 + 顺路优先 + 真实乘客行为”:
1.控制器读取电梯状态和队列状态(内部队头目的楼层、外部队头乘客)。
1.优先查找当前方向的 “顺路请求”:
UP方向:内部队头目的楼层≥当前楼层,外部队头乘客方向为UP且源楼层≥当前楼层,选择其中最近的楼层作为目标。
DOWN方向:内部队头目的楼层≤当前楼层,外部队头乘客方向为DOWN且源楼层≤当前楼层,选择其中最近的楼层作为目标。
1.若当前方向无顺路请求,计算所有队头请求与当前楼层的距离,选择最近的请求作为目标,切换方向。
1.电梯移动到目标楼层,调用ElevatorQueues.processCurrentFloor()处理请求:
内部请求:移除队头目的楼层。
外部请求:移除队头乘客,返回该乘客(控制器自动将其目的楼层添加到内部队列)。
1.若有请求处理,开关门;重复上述流程直到所有请求处理完毕。
2.3.3 复杂度分析(基于代码结构与工具逻辑推导)
引入Passenger类后,逻辑更贴近真实场景,但通过合理的职责划分,复杂度仍保持可控:

类复杂度汇总:

关键方法分析:ElevatorController.run()的 v (G) 为 11,较第二次作业增加 1,因新增 “乘客上车后添加目的楼层” 逻辑,但 ev (G) 仍保持 4,说明结构化程度未下降;iv (G) 为 8,反映控制器需协调电梯、队列、乘客三类对象,耦合度合理。Passenger.equals()的 v (G) 为 3,因需比较源 / 目的两个属性,是保证请求去重的关键方法,逻辑必要且无冗余。
三、采坑心得
3.1 第一次作业:基础逻辑与边界处理问题
问题 1:调度方向判断错误
当电梯在中间楼层(如 5 楼),当前方向为UP,但内部请求有 3 楼(DOWN方向)时,错误地切换方向处理 3 楼请求,违反 “同向优先” 规则。
解决:在查找目标时,严格筛选当前方向的请求(UP方向仅查找楼层≥当前楼层的请求,DOWN方向仅查找楼层≤当前楼层的请求)。
3.2 第二次作业:职责拆分后的逻辑漏洞(答案仅对一半)
问题 1:“最近请求” 判断错误
调度逻辑中仅比较 “内部队头” 和 “外部队头” 与当前楼层的距离,未考虑队列中其他顺路请求。例如:当前楼层 3,UP方向,内部队列是[5,4],外部队列是[6,UP],错误地选择 5 作为目标(队头),而实际应优先处理 4(更近的顺路请求)。
原因:使用Deque的peekFirst()仅获取队头,未遍历队列查找顺路的最近请求。
解决(第三次作业):在ElevatorQueues中提供 “查找当前方向顺路最近请求” 的方法,或在控制器中遍历队列筛选目标(第三次作业通过优化run()方法的minUp/maxDown计算实现)。
问题 2:外部请求处理不完整
ElevatorQueues.processCurrentFloor()仅处理队头的外部请求,若同一楼层有多个同方向请求(虽HashSet去重,但极端情况下队列中可能存在多个有效请求),导致部分请求未处理。
解决(第三次作业):修改processCurrentFloor()为循环处理当前楼层的所有有效请求(直到队头不再是当前楼层或方向不一致),确保无遗漏。
问题 3:边界转向逻辑缺陷
电梯到达最高层(如 10 楼)后,未强制切换方向为DOWN,导致继续查找UP方向请求(无结果),陷入死循环。
解决:在Elevator类中添加forceTurnAtBoundary()方法,到达边界楼层时自动切换方向,控制器每次调度前调用。
3.3 第三次作业:完善逻辑与性能优化
问题 1:输入解析格式错误
第三次作业外部请求格式从<楼层,方向>改为<源楼层,目的楼层>,最初未适配新格式,导致输入解析失败。
解决:修改Main类的输入解析逻辑,判断content包含,时,拆分源楼层和目的楼层,创建Passenger对象。
问题 2:日志输出效率低
最初每次移动楼层都调用writer.write()(I/O 操作频繁),导致大数据量请求时超时。
解决:在Elevator.moveTo()中使用StringBuilder拼接所有移动日志,最后一次性写入,减少 I/O 次数,提升性能。
3.4 测试用例设计的重要性
三次作业的调试过程中,边界测试用例和复杂场景测试用例是发现问题的关键:
边界用例:最小楼层(1 楼)、最大楼层(如 20 楼)、请求楼层等于当前楼层、仅外部请求、仅内部请求。
复杂场景用例:多请求交叉(如<3,UP>→<5>→<6,DOWN>→<7>→<3>)、同向多请求(如<4,UP>→<5,UP>→<3,UP>)、反向请求穿插(如<5,UP>→<2,DOWN>→<6,UP>)。
例如,通过测试用例:
1
5
3,5> // 外部请求:3楼上车,5楼下车
<4,2> // 外部请求:4楼上车,2楼下车
<5,1> // 外部请求:5楼上车,1楼下车
end
验证了 “同向优先” 和 “乘客目的楼层添加” 的正确性,确保电梯先处理 3→5 的UP请求,再处理 5→4→2→1 的DOWN请求。
四、改进建议(可持续优化方向)
4.1 代码可扩展性优化
引入策略模式(Strategy Pattern)处理调度算法
目前调度逻辑固定在ElevatorController.run()方法中,若需添加新调度规则(如 “最短等待时间优先”“最大载客量优先”),需修改run()方法,违反 “开闭原则”。
改进:定义Scheduler接口(如getTargetFloor(Elevator elevator, ElevatorQueues queues)),实现不同调度策略(SameDirectionScheduler、ShortestDistanceScheduler等),控制器通过依赖注入选择策略,无需修改核心逻辑。
使用枚举类优化方向状态
目前方向用String("UP"/"DOWN")表示,易出现拼写错误(如"Up"),且判断逻辑繁琐(if (direction.equals("UP")))。
改进:定义Direction枚举(UP, DOWN),使用switch判断方向,提升代码可读性和健壮性。
4.2 性能优化
使用更高效的数据结构优化目标查找
目前查找顺路最近请求需遍历队列(O (n)),请求量大时效率较低。
改进:在ElevatorQueues中为内部 / 外部请求维护TreeSet(有序集合),按楼层排序:
内部请求TreeSet:按楼层升序,UP方向查找≥当前楼层的最小元素(ceiling(currentFloor)),DOWN方向查找≤当前楼层的最大元素(floor(currentFloor))。
外部请求TreeSet:按源楼层升序,按方向分组,查找逻辑同内部请求。
有序集合的查找效率为 O (log n),大幅提升调度性能。
异步处理输入与调度
目前输入解析和调度是串行的(输入全部读取后才启动调度),无法处理动态输入(如实时添加请求)。
改进:使用多线程,主线程负责输入解析并添加请求到队列,调度线程循环处理队列,实现异步响应。
五、总结
5.1 学习收获
通过三次电梯调度作业的迭代实践,我在以下方面取得了显著进步:
面向对象设计能力:从职责耦合到单一职责原则的实践,理解了 “高内聚、低耦合” 的重要性,学会了如何拆分类的职责,提升代码可维护性。从第一次作业Elevator类包揽所有职责(WMC=25),到第三次作业各分类职责明确(Elevator WMC=9、Controller WMC=13),深刻体会到合理的类设计对代码质量的决定性作用。
问题分解与建模能力:将复杂的电梯调度问题分解为 “请求管理”“物理行为”“调度决策” 三个核心模块,逐步构建符合真实场景的模型(从Request到Passenger)。
工程实践能力:掌握了集合框架的灵活应用(Deque保证队头优先、HashSet去重、TreeSet有序查找)、边界情况处理、输入输出优化、测试用例设计等工程技巧。同时,通过复杂度分析,学会了识别代码中的 “热点” 方法(如processRequests、run),并针对性优化。
调试与优化思维:通过分析测试失败原因,学会了定位逻辑漏洞(如调度方向判断错误),并从 “能用” 到 “好用”(性能优化、可读性提升)进行持续优化。
5.2 需进一步学习的内容
多线程与并发编程:目前电梯调度是串行的,实际电梯系统需处理并发请求(如同时按下多个按钮),需学习多线程同步(synchronized、Lock)、线程池等知识,实现并发调度。
设计模式的综合应用:除策略模式外,可学习观察者模式(如请求添加时通知调度器)、工厂模式(如创建不同类型的Passenger),进一步提升代码可扩展性。
性能分析与优化工具:学习使用 SourceMonitor(复杂度分析)、VisualVM(内存与 CPU 分析)等工具,量化评估代码质量,针对性优化性能瓶颈。
5.3 课程与作业改进建议
作业迭代的引导性增强:第二次作业的 “职责拆分” 目标明确,但缺乏对 “调度逻辑优化” 的引导(如如何正确查找顺路请求),导致部分同学陷入逻辑漏洞。建议在作业说明中补充 “调度算法要点”(如 “顺路优先的判断标准”),或提供参考伪代码。
测试用例公开化:目前测试点仅在提交后可见,调试效率低。建议提供部分公开测试用例(如边界用例、复杂场景用例),帮助同学提前验证逻辑,减少提交次数。
增加实践案例分享:课程中可增加优秀作业的设计分析(如类图、调度算法),或组织小组讨论,分享不同的实现思路(如数据结构选择、职责拆分方式),拓宽视野。
引入真实场景扩展:第三次作业已贴近真实场景,可进一步扩展为 “多部电梯调度”“实时监控界面” 等,结合 GUI(如 Swing/JavaFX)或 Web 技术,提升学习兴趣和综合应用能力。
5.4 结语
三次电梯调度作业的迭代过程,是从 “功能实现” 到 “设计优化” 的思维转变过程。我深刻体会到,面向对象编程的核心不仅是语法的运用,更是 “如何将现实问题抽象为类和对象,如何通过合理的职责拆分构建健壮、可维护的系统”。未来,我将继续深化对设计原则和模式的理解,在实践中不断提升自己的软件设计与开发能力。