一、前言
经过前几周的课堂讲解与视频学习,已初步掌握 Java 的基础用法。相较于 C 语言,Java 最核心的差异在于引入了 “类” 的概念 —— 课堂上老师对此概念花了大力气进行了重点讲解。在PTA完成了前三次 Java 迭代作业,从最初面对电梯调度需求时的手足无措,到逐步理清类结构与逻辑脉络,每一次迭代都像是一次 “打怪升级”。这三次题目集围绕单部电梯调度的核心场景展开,层层递进地优化程序设计,环环相扣。
1. 题量与知识点分布
题量设计:三次题目集均以 1 道核心大题为核心,代码量从初始的 200 余行逐步扩展至 400 余行。虽题量不多,但每一次迭代都不是简单的功能叠加,而是对原有代码的结构性重构,需要重新梳理类与类之间的关系、逻辑流程的衔接,思考成本远大于单纯的代码编写。
重点覆盖:三次作业形成了完整的知识应用链条。从 Java 基础语法(输入输出处理、正则表达式校验、异常捕获),到面向对象核心思想(类的封装、对象的创建与交互、单一职责原则落地),再到数据结构(队列的增删查改、请求的有序管理)和算法逻辑(电梯运行方向决策、请求优先级判断、顺路请求处理),每一个知识点都在实践中得到了充分巩固。
2. 难度梯度与核心挑战
题目集 1(单类实现):难度偏简单,核心挑战在于理解电梯运行的基本规则 ——“同向优先”“逐层移动”“停靠判断”,并将这些自然语言描述的规则转化为可执行的代码逻辑。重点考察对需求的拆解能力和基础语法的应用熟练度。
题目集 2(类职责拆分):难度中等,核心挑战在于遵循单一职责原则拆分原有过于臃肿的电梯类,新增请求队列类和控制类,同时实现 “重复请求过滤” 和 “无效楼层过滤” 功能。重点考察类设计能力、模块间的交互逻辑设计。
题目集 3(新增乘客类):难度偏难,核心挑战在于取消原有请求类,新增乘客类封装完整行程信息,调整外部请求输入格式,并实现 “外部请求处理后自动将目的楼层加入内部队列” 的联动逻辑。重点考察迭代设计能力、复杂场景下的逻辑衔接。
二、设计与分析
7-5 NCHU_单部电梯调度程序
作者 段喜龙
单位 南昌航空大学
设计一个电梯类,具体包含电梯的最大楼层数、最小楼层数(默认为1层)当前楼层、运行方向、运行状态,以及电梯内部乘客的请求队列和电梯外部楼层乘客的请求队列,其中,电梯外部请求队列需要区分上行和下行。
电梯运行规则如下:电梯默认停留在1层,状态为静止,当有乘客对电梯发起请求时(各楼层电梯外部乘客按下上行或者下行按钮或者电梯内部乘客按下想要到达的楼层数字按钮),电梯开始移动,当电梯向某个方向移动时,优先处理同方向的请求,当同方向的请求均被处理完毕然后再处理相反方向的请求。电梯运行过程中的状态包括停止、移动中、开门、关门等状态。当电梯停止时,如果有新的请求,就根据请求的方向或位置决定移动方向。电梯在运行到某一楼层时,检查当前是否有请求(访问电梯内请求队列和电梯外请求队列),然后据此决定移动方向。每次移动一个楼层,检查是否有需要停靠的请求,如果有,则开门,处理该楼层的请求,然后关门继续移动。
使用键盘模拟输入乘客的请求,此时要注意处理无效请求情况,例如无效楼层请求,比如超过大楼的最高或最低楼层。还需要考虑电梯的空闲状态,当没有请求时,电梯停留在当前楼层。
请编写一个Java程序,设计一个电梯类,包含状态管理、请求队列管理以及调度算法,并使用一些测试用例,模拟不同的请求顺序,观察电梯的行为是否符合预期,比如是否优先处理同方向的请求,是否在移动过程中处理顺路的请求等。为了降低编程难度,不考虑同时有多个乘客请求同时发生的情况,即采用串行处理乘客的请求方式(电梯只按照规则响应请求队列中当前的乘客请求,响应结束后再响应下一个请求),具体运行规则详见输入输出样例。
输入格式:
第一行输入最小电梯楼层数。
第二行输入最大电梯楼层数。
从第三行开始每行输入代表一个乘客请求。
电梯内乘客请求格式:<楼层数>
电梯外乘客请求格式:<乘客所在楼层数,乘梯方向>,其中,乘梯方向用UP代表上行,用DOWN代表下行(UP、DOWN必须大写)。
当输入“end”时代表输入结束(end不区分大小写)。
输出格式:
模拟电梯的运行过程,输出方式如下:
运行到某一楼层(不需要停留开门),输出一行文本:
Current Floor: 楼层数 Direction: 方向
运行到某一楼层(需要停留开门)输出两行文本:
Open Door # Floor 楼层数
Close Door
(一)题目集 1:单类实现 —— 搭建基础框架
1. 设计思路
题目集 1 的核心需求是实现电梯的基本运行逻辑,因此初始设计采用了 “紧凑式” 结构,仅定义了三个核心类。外部请求类负责封装楼层和方向信息,电梯类作为核心,集中管理电梯的所有状态(最小楼层、最大楼层、当前楼层、运行方向)和请求队列(内部请求队列、外部请求队列),同时承担请求处理、方向决策、移动控制、停靠判断等所有核心逻辑;主类则负责读取用户输入,解析请求类型并添加到电梯的对应队列中。
首先特别重要的就是看懂这个电梯运行过程详解(LOOK 算法)

特别注意这里 分两个队列,但是每次只比较每个队列的第一个。,一开始没有搞懂这个,查了很多资料,从周一到周三每天一坐下就是干,结果就是无尽的运行超时和答案错误(题目都读了好多遍看题目说设计一个电梯类,一开始还以为是自己类分的太多了,所以一直超时超时,然而并不是,后面试了很多方法减少运行时长都没用,只能改核心逻辑)

干了几天毫无进展,只是搭了个框架,虽然已经和示例的结果一模一样,但是仍然答案错误,因为还有隐藏的测试点没过。看到只有这个说了和没说的测试点真的难绷啊啊啊,后面参考前辈和同学们的经验,终于有了大概思路。在此还要感谢[NCHU_单部电梯调度程序](https://blog.csdn.net/2401_84910555/article/details/154123441 "NCHU_单部电梯调度程序和[https://www.cnblogs.com/Unjx/p/18824685#单部电梯调度程序 这两篇blog的作者给了我不少思路启发。

2. 核心逻辑分析




3. 复杂度分析
从功能实现来看,核心复杂点集中在方向决策模块。该模块需要综合内部请求队列、外部请求队列的首个请求位置,以及当前电梯运行方向,通过多重条件判断确定下一步移动方向,逻辑分支较多,容易出现判断疏漏。
4. 采坑心得
忽略 “end 不区分大小写” 的要求:初始代码中仅判断输入是否等于 “end”,导致输入 “END”“End” 等形式时无法终止程序,直到测试时才发现该问题。心得:涉及字符串对比的需求,需提前确认是否区分大小写,关键判断条件要覆盖所有可能场景,避免因细节疏漏导致功能异常。
方向决策逻辑不严谨:未充分理解 “同向优先” 原则,仅简单判断队列中首个请求的方向,导致同方向后续存在顺路请求时,电梯未停靠直接跳过。心得:复杂逻辑需先梳理清楚所有场景,可通过绘制流程图列出所有可能的请求组合,再逐一对应到代码逻辑,避免因逻辑不全导致功能偏差。
无效请求过滤不完整:仅在添加内部请求时校验楼层有效性,外部请求未进行楼层校验,导致超出楼层范围的外部请求仍被电梯处理。心得:功能模块设计时要保持一致性,同类校验逻辑(如楼层有效性)应在所有相关入口统一处理,避免出现 “遗漏校验” 的情况。
输出格式与样例不一致:未注意到 “停靠时仅输出开门关门信息,无需重复输出当前楼层和方向” 的要求,初始代码中停靠时格式混乱。
5. SourceMonitor度量分析


图表特征与问题关联
** Block Histogram(语句数 vs 代码深度**):深度 3~5 层的语句块占比超 65%,集中在determineDirection()和exStop()等核心方法。如determineDirection()中 “内外部请求是否为空→方向匹配→楼层对比” 的三重嵌套,导致代码深度达 5 层,语句数 21 条,远超方法平均语句数。
** Kiviat Graph(多维度雷达图**):“圈复杂度”“嵌套深度” 维度呈红色预警,“注释覆盖率” 维度几乎为 0。反映出代码在 “复杂性控制” 和 “可理解性” 上的双重缺陷,属于 “高风险、低维护” 的典型单类设计。
类图:

(二)题目集 2:类职责拆分 —— 践行单一职责原则
1. 设计思路迭代
题目集 2 的核心要求是解决电梯类职责过多的问题,因此设计的核心思路是 “拆分与解耦”。在原有基础上新增了请求队列类和控制类,形成了五大核心类的结构:
外部请求类仍负责封装请求信息- (并新增 equals 和 hashCode 方法用于去重);
电梯类仅保留状态管理功能(当前楼层、运行方向、楼层范围),通过 getter 和 setter 方法提供状态访问和修改接口,不再参与任何逻辑运算;
** 请求队列类**专门负责请求的管理,包括内部请求和外部请求的存储、重复请求过滤、无效楼层请求过滤;
控制类作为 “大脑”,协调电梯类和请求队列类,集中实现请求处理、方向决策、移动控制、停靠判断等核心逻辑;
主类则负责输入解析和请求分发。
这种设计严格遵循了单一职责原则,每个类只专注于自己的核心职责:电梯管状态、队列管存储、控制器管逻辑、主类管输入,模块间通过明确的接口交互,耦合度显著降低。
2. 核心优化点分析
重复请求过滤:请求队列类通过记录上一次添加的内部请求和外部请求,在新增请求时进行对比,若为连续相同请求则直接忽略。外部请求的对比依赖重写的 equals 和 hashCode 方法,确保基于 “楼层 + 方向” 的唯一性判断。
无效楼层过滤:在请求队列类的添加请求方法中,传入电梯的楼层范围参数,新增请求时先校验楼层是否在有效范围内,无效请求直接过滤,无需传入后续逻辑处理。
逻辑集中管理:所有与电梯运行相关的核心逻辑(方向决策、移动控制、停靠判断)都集中在控制类中,代码结构更清晰,后续修改逻辑只需调整控制类,无需改动电梯类或队列类。


第二次我就尝试了第一次成功的两种不同的方法,发现每次都只对了一个测试点,于是我将二者结合了一下。新增 equals 和 hashCode 方法用于去重。
3. 类交互与依赖关系
类之间的交互遵循 “低耦合、高内聚” 原则。主类通过创建电梯、请求队列、控制器的实例,将解析后的请求添加到请求队列;控制器通过调用电梯类的 getter 方法获取当前状态,调用队列类的方法获取请求信息,再通过 setter 方法修改电梯状态;电梯类和队列类之间不直接交互,完全通过控制器作为中间协调者,避免了类之间的直接依赖。
4. 采坑心得
类依赖关系混乱:初始设计时,控制类直接访问电梯类和队列类的私有属性,违反了封装原则。后续修改电梯类的属性名称时,控制类中所有相关代码都需要同步修改。心得:类设计应严格遵循封装原则,通过 getter/setter 方法提供状态访问接口,避免直接操作私有属性,降低类之间的耦合度,提升代码可维护性。
外部请求去重失败:未正确重写 equals 和 hashCode 方法,导致相同的外部请求无法被识别,去重逻辑失效。心得:自定义类用于集合元素对比时,必须重写 equals 和 hashCode 方法,确保基于核心属性(如外部请求的楼层 + 方向)进行相等性判断,否则默认使用 Object 类的方法,无法实现预期的去重效果。
模块间参数传递缺失:请求队列类添加请求时未传入电梯的楼层范围参数,导致无法校验楼层有效性。心得:模块间交互前需明确参数需求,提前规划方法的参数列表,避免因参数缺失导致功能残缺,减少后续大面积修改代码的情况。
方向决策逻辑冲突:当内部请求和外部请求方向不一致时,未明确优先级,导致电梯运行方向频繁切换。
SourceMonitor度量分析


图表特征与优化点
Block Histogram:深度 3~4 层的语句块占比提升至 70%,Controller.determineDirection() 仍为复杂度峰值,但电梯类和队列类的方法深度降至 3 层以内。例如队列类addOuterRequest() 仅需处理请求去重,代码深度 2 层,语句数 8 条,符合 “单一职责” 后的简洁性。
Kiviat Graph:“类耦合度” 维度明显改善(从 0.8 降至 0.4),但 “圈复杂度” 和 “嵌套深度” 维度仍为红色。说明类拆分解决了 “职责过载” 问题,但核心算法的复杂性未优化,属于 “结构优化、逻辑未优化” 的过渡状态。
类图:

(三)题目集 3:新增乘客类 —— 贴近真实场景
1. 设计思路升级
题目集 3 的核心变化是取消原有请求类,新增乘客类,这一调整让程序设计更贴近真实场景。乘客类不再是单纯的 “请求信息封装”,而是完整的 “乘客行程封装”,包含乘客的请求源楼层(发起请求的楼层)和目的楼层(想要到达的楼层),通过这两个属性可以自动推导乘客的乘梯方向,无需额外传入方向参数。
类结构在题目集 2 的基础上进一步优化:电梯类保持状态管理功能不变;请求队列类的外部请求队列改为存储乘客实例,内部请求队列仍存储目的楼层;控制类新增 “外部请求转内部请求” 的逻辑 —— 当电梯处理完某一外部请求(即乘客成功上车)后,自动将该乘客的目的楼层加入内部请求队列,实现 “接客 - 送客” 的完整流程;主类则适配新的输入格式,解析 “源楼层 + 目的楼层” 的外部请求,创建乘客实例并添加到队列中。
2. 核心逻辑优化
乘梯方向自动推导:控制类通过对比乘客的源楼层和目的楼层,自动判断乘客的乘梯方向(源楼层小于目的楼层为上行,反之则为下行),无需像之前那样依赖外部传入的方向参数,减少了输入错误的可能性,也更符合真实场景中乘客的乘梯逻辑。
内外请求联动:外部请求不再是独立的 “呼叫电梯” 行为,而是与内部请求形成联动。乘客呼叫电梯(外部请求)后,电梯到达接客楼层,乘客上车,其目的地自动成为电梯的内部请求,电梯继续运行至该楼层,完成整个服务流程,逻辑更连贯。
输入格式适配:外部请求格式从 “楼层 + 方向” 改为 “源楼层 + 目的楼层”,主类通过调整正则表达式校验规则,确保输入格式的准确性,同时对源楼层和目的楼层分别进行有效性校验,避免无效请求进入队列。
3. 设计优势与不足
优势:乘客类的引入让程序模型更贴近现实,类职责划分更细致,内外请求的联动逻辑让电梯运行更符合实际使用场景;模块化程度进一步提升,后续若需新增乘客相关功能(如乘客优先级、载重限制),可直接在乘客类或控制类中扩展,无需大幅重构。
不足:请求队列仍采用动态数组存储,当请求数量较多时,删除队列头部元素的效率较低;控制类的方向决策逻辑仍较为复杂,多重条件判断可能导致后续维护难度增加;未考虑电梯运行中的特殊场景(如中途新增高优先级请求、乘客取消请求等)。
4. 采坑心得
乘客类属性设计不合理:初始设计时未考虑源楼层和目的楼层相同的情况,导致乘客发起 “原地请求” 时,电梯仍会进行无效运行。心得:新增类时需充分考虑边界场景,对核心属性的合理性进行校验(如源楼层与目的楼层不能相同),提前过滤无效请求,避免程序做无用功。
外部请求转内部请求时机错误:初始逻辑中,在添加外部请求时就将目的楼层加入内部队列,导致电梯未接客就先前往目的楼层。心得:模块间逻辑联动需明确触发时机,“接客后送客” 的逻辑应在电梯停靠接客楼层、处理完外部请求后执行,避免时序错误导致逻辑混乱。
方向推导逻辑遗漏:当乘客源楼层大于目的楼层时,未正确推导为下行方向,导致电梯无法停靠接客楼层。心得:逻辑推导需覆盖所有可能的属性组合,对于 “源楼层与目的楼层” 的对比,要明确两种情况(大于、小于)的处理逻辑,避免因遗漏分支导致功能异常。
SourceMonitor度量分析:


图表特征与迭代效果
Block Histogram:深度 3~4 层的语句块占比超 80%,Controller.determineDirection() 仍为复杂度峰值,但exStop() 等方法的深度从 5 层降至 4 层,语句数从 12 条减至 8 条。例如外部请求处理时,通过Passenger.getDirection() 自动推导方向,减少了手动判断的嵌套条件。
Kiviat Graph:“嵌套深度” 维度从红色转为黄色,“注释覆盖率” 维度提升至可接受区间。反映出迭代后代码在 “可读性” 和 “结构合理性” 上的进步,属于 “模块清晰、逻辑待优” 的成熟设计阶段。
类图:

三、改进建议
1. 数据结构优化
当前请求队列采用动态数组存储,删除头部元素时需要移动后续所有元素,效率较低。可以将动态数组替换为链表结构,链表的删除和插入操作效率更高,更适合请求队列 “先进先出” 的特性,尤其是在请求数量较多的场景下,能显著提升程序运行效率。同时,可对链表进行封装,提供统一的入队、出队、查询接口,隐藏底层实现细节,便于后续替换其他数据结构。
2. 逻辑结构优化
控制类中的方向决策和停靠判断逻辑过于集中,多重条件判断导致代码可读性和可维护性较差。可以采用 “策略模式” 对调度逻辑进行重构,将不同的调度策略(如同向优先、最近请求优先)封装为独立的策略类,控制类根据实际需求选择对应的策略。后续若需修改调度规则或新增策略,只需新增或修改策略类,无需改动控制类的核心代码,提升程序的可扩展性。
3. 功能扩展完善
性能优化:新增请求合并功能,对于同一楼层的相同方向外部请求(或同一目的楼层的内部请求),自动合并为一个请求,减少电梯停靠次数;添加电梯空闲状态处理,当无请求时,电梯可自动返回指定楼层(如 1 层)或停留在当前楼层,避免无效能耗。
4. 代码规范提升
增加注释说明:对核心类、核心方法添加详细注释,说明其职责、参数含义、返回值、逻辑流程等。例如,在控制类的方向决策方法中,通过注释说明每种场景的决策依据,便于后续维护和他人阅读。
异常处理完善:当前仅对输入格式和楼层有效性进行了简单校验,建议新增更全面的异常处理机制。例如,输入非数字字符时抛出非法参数异常,并提示用户 “请输入有效的数字楼层”;楼层为负数时,明确提示 “楼层不能为负数”,提升程序的健壮性和用户体验。
四、总结
三次电梯调度程序的迭代实践,让我收获良多。我巩固了 Java 基础语法与面向对象核心思想,学会了将现实场景抽象为程序模型,通过拆分职责、模块化设计提升代码可维护性。从单类实现到类职责拆分,再到新增乘客类贴近真实场景,每一次迭代都让我对单一职责原则有了更深理解。
同时我也发现了自身不足,复杂逻辑设计仍有疏漏,对设计模式应用不够熟练。后续我会深入学习数据结构与设计模式,积累工程化设计经验,提升代码调试和优化能力,在编程道路上持续进步。
大作业之路才刚刚开始,后面的题目老师们详细指明了每个测试点,就可以更细致定位到每一个点,不会像这次这样无头苍蝇一样毫无头绪。道阻且长,与诸君共勉。