电梯调度程序分析

一.前言
本次博客将完整复盘电梯调度系统的三次迭代开发历程。第一次迭代聚焦核心功能落地,以单一电梯类封装所有状态、队列与调度逻辑,实现 “同向优先、逐层停靠” 的基础运行规则,解决 “能跑起来” 的核心问题;第二次迭代针对单一类职责过载的痛点,拆分出乘客请求类、队列类与控制类,新增无效请求过滤、重复请求去重机制,在保证功能稳定的同时,让代码架构更符合面向对象设计原则;第三次迭代则进一步优化实体建模,取消独立请求类、新增乘客类,调整外部请求输入格式为 “源楼层 - 目的楼层”,并新增 “外部请求处理后自动将目的楼层加入内部队列” 的业务逻辑,让系统更贴近真实电梯的运行场景,同时强化各模块的职责单一性。将深入拆解每一次迭代的设计思路、核心难点与代码实现:从单一类的逻辑封装,到多模块的协同设计

二、设计与分析

1.第一次题目集设计与分析

(1)类结构设计

MoveDir枚举类:定义电梯运行方向的标准枚举,统一方向标识与输出格式CallNode数据结构类:作为请求队列的基础数据节点,封装单个电梯请求的关键信息

CallQueue数据管理类:封装请求队列的所有操作,负责请求的存储、查询、删除与统计,独立管理请求生命周期

ElevatorController核心控制类:电梯调度的核心大脑,负责电梯状态管理、输入解析、调度决策、运行控制与停靠处理,串联全流程业务逻辑

CallNode数据结构类:作为请求队列的基础数据节点,封装单个电梯请求的关键信息

Main程序入口类:程序启动与输入输出交互,初始化系统并触发调度流程

类图设计:

image

(2)调度逻辑分析

主要围绕着“同向优先,沿途服务” 策略展开:

CallQueue 类负责管理所有待处理的请求,外部呼叫(<楼层,方向>)和内部目的楼层(< 楼层 >)经校验后,会被封装为 CallNode 节点存入该类维护的链表队列中,为调度提供数据基础;
ElevatorController 类是整个系统的核心,它不仅负责通过 handleInput () 方法解析输入请求,还承载着核心调度逻辑与运行控制 —— 其中调度算法的核心体现在 identifyNextTarget () 和 findTargetInCurrentDir () 中,具体实现 “同向优先,沿途服务” 的目标选择规则:电梯静止时优先选上行目标,再选下行;运行中先沿当前方向通过 searchUpTarget ()/searchDownTarget () 找目标,无则切换方向查找,仍无则处理任意方向。
 
runElevator () 方法则像一个总指挥,协调着目标选择、移动和停靠这三个关键环节,构成整个电梯模拟的主流程:它控制电梯逐层移动到目标楼层,每移动一步输出当前楼层和方向;到达目标楼层后,触发 processLevelStop () 方法处理停靠逻辑,优先处理当前方向的外部呼叫,再处理内部请求,处理完成后通过 CallQueue 的 removeSpecific () 等方法从队列移除对应请求。整个过程循环执行,直到 CallQueue 中所有请求处理完毕,电梯恢复静止状态。

(3)源码指标分析(基于SourceMonitor) 

image

 

image

 关键指标数据速览:

Maximum Complexity(最大圈复杂度) 衡量函数内独立执行路径的数量,数值越高逻辑越复杂(一般建议≤10) 5,处于合理范围,但Main.processLevelStop()复杂度为 5,是最复杂的方法
Maximum Block Depth(最大模块深度) 函数内分支结构的嵌套层数(如if-else、循环的嵌套),层数越多可读性越差 5,说明存在嵌套较深的逻辑块(如多层if-else或循环嵌套)
Average Block Depth(平均模块深度) 所有函数的模块深度平均值 2.42,整体嵌套层次较适中
Lines(总行数) 代码总行数(含空行、注释) 427 行,属于中小型规模代码
Statements(语句数) 可执行语句的数量(如赋值、分支、循环等) 97,语句密度适中
Percent Branch Statements(分支语句占比) ifforwhile等分支语句占总语句的比例,反映逻辑分支密度 22.7%,说明约五分之一的语句是分支逻辑,逻辑不算过度分散
Methods per Class(每类方法数) 单个类包含的方法数量 9.00,类的方法数偏多,需关注类的职责是否单一
Average Statements per Method(平均每方法语句数) 单个方法的平均语句数,反映方法的颗粒度 8.78,方法颗粒度较合理,未出现过度冗长的方法

规模与结构:代码总行数 427 行,包含 97 条可执行语句,单个类(Main)承载 9 个方法,平均每个方法约 8.78 条语句。这种规模属于中小型程序,但类的方法数偏多,存在 “职责过载” 的潜在风险。
复杂度分布:整体平均复杂度 3.00,处于合理范围,但核心方法Main.processLevelStop()的圈复杂度达到 5,是代码中最复杂的方法,说明该方法内逻辑分支较多,维护成本较高。
结构深度:最大模块深度为 5(即分支嵌套最多 5 层),平均模块深度 2.42,主要逻辑集中在 2-3 层嵌套,整体结构层次较适中,但深度为 5 的嵌套块仍需关注可读性。

2.第二次题目集设计与分析

(1)类结构设计

Direction枚举类:定义电梯运行方向与请求方向的标准枚举,统一方向标识,避免字符串直接使用导致的格式混乱

Request抽象基类:封装所有电梯请求的公共属性与行为,定义请求类的抽象接口,为外部请求和内部请求提供统一的继承基础

InternalRequest(内部请求)请求子类:封装电梯内部的目的楼层请求(如电梯内乘客按下目标楼层按钮),继承并实现Request抽象类的抽象方法

ExternalRequest(外部请求)请求子类:封装电梯外部的呼叫请求(如楼层外乘客按下上行 / 下行按钮),继承并实现Request抽象类的抽象方法

RequestQueue队列管理类:统一管理所有请求的存储、去重、查询与删除,区分外部请求和内部请求的独立队列,独立承担请求生命周期管理职责。

Elevator电梯实体类:封装电梯的物理属性与基础行为,管理电梯自身状态(当前楼层、运行方向),提供楼层移动的基础方法

ElevatorController核心控制类:电梯调度的 “大脑”,负责协调电梯与请求队列,实现调度算法(请求优先级判断、运行方向设置)、电梯运行控制与停靠处理

Main程序入口类:程序启动与输入输出交互,负责初始化系统组件、解析用户输入、触发调度流程

类结构图设计:

image

 (2)调度逻辑分析

这个电梯调度系统的混合策略以 “效率优先 + 体验平衡” 为核心,通过三层递进式规则处理请求:

一、核心策略 1:同向优先(效率第一原则,避免无效折返)

1. 核心逻辑

电梯一旦确定运行方向(向上 / 向下),会优先处理 “当前方向路径上” 的所有请求,直到该方向无待处理请求后,才会考虑切换方向。

2. “顺路” 请求判定标准

向上运行时:仅处理 “目标楼层≥当前楼层” 且 “请求为向上需求” 的请求
外部请求:对应楼层外按的「上按钮」(比如电梯在 3 楼向上,5 楼有人按上→顺路)
内部请求:只要目标楼层在当前楼层之上→默认顺路
向下运行时:仅处理 “目标楼层≤当前楼层” 且 “请求为向下需求” 的请求
外部请求:对应楼层外按的「下按钮」(比如电梯在 6 楼向下,4 楼有人按下→顺路)
内部请求:只要目标楼层在当前楼层之下→默认顺路

二、核心策略 2:距离就近(方向切换时的决策规则)

1. 触发场景(仅两种情况生效)

场景 1:当前方向无顺路请求,需切换方向(比如电梯到 10 楼后,向上方向无请求,需向下处理请求)
场景 2:电梯初始处于空闲状态(未运行),同时收到多个不同方向的请求

2. 决策逻辑

计算所有待处理请求与电梯当前楼层的直线距离(楼层差绝对值),优先处理距离最近的请求。

三、核心策略 3:内部请求优先(乘客体验优化规则)

1. 优先前提(非绝对优先,需满足 “顺路”)

内部请求(电梯内按的楼层按钮)之所以更容易被优先处理,核心是其 “双向顺路” 特性 —— 无论电梯向上还是向下运行,只要目标楼层在当前运行路径上,就会被判定为顺路。

2. 与外部请求的差异

外部请求:仅对应按钮方向(上 / 下)与电梯运行方向一致时,才视为顺路
内部请求:无论电梯向上还是向下,只要目标楼层在路径上,就视为顺路

四、完整调度流程(从请求到执行)

请求入队:外部呼梯、内部按楼层的请求,经有效性校验后进入队列(避免重复请求)
方向决策:控制器判断当前最优请求,将电梯方向设为 “朝向该请求的目标楼层”
移动运行:电梯按设定方向逐层移动,过程中只处理同方向顺路请求
停靠处理:到达最优请求的目标楼层时,触发开关门动作,处理该请求并从队列中移除
循环执行:重复 “方向决策→移动→停靠”,直到所有请求处理完毕,电梯进入空闲状态

(3)源码指标分析(基于SourceMonitor) 

image

 

image

 

image

  关键指标数据速览:

一、代码规模与结构指标

指标数值说明
Lines(代码行数) 343* 代码总行数约 343 行(含空行、注释等),属于中等规模的单文件代码。
Statements(可执行语句数) 24 实际可执行的业务语句仅 24 行,说明代码中可能存在大量空行、声明或结构定义。
Classes and Interfaces(类和接口数) 2 代码中定义了 2 个类或接口,属于简单的类结构。
Methods per Class(每类方法数) 0.50 平均每个类仅 0.5 个方法,结合 “2 个类” 推测:其中 1 个类可能包含方法,另 1 个可能是工具类 / 空类。
Average Statements per Method(每方法平均语句数) 20.00 若按 “有方法的类” 计算,单个方法平均包含 20 行可执行语句,属于较紧凑的方法规模。

二、代码复杂度指标

指标数值说明
Maximum Complexity(最大方法复杂度) 0 所有方法的圈复杂度为 0,说明代码中无分支逻辑(if、for、while 等都没有),逻辑极其简单。
Line Number of Most Complex Method {undefined} 因方法复杂度为 0,不存在 “最复杂方法” 的行号。
Name of Most Complex Method {no methods} 同理,无复杂方法。
Maximum Block Depth(最大代码块嵌套深度) 5 代码块(如 if-else、循环)的最大嵌套层数为 5 层。虽然方法无分支,但可能存在多层结构定义(如嵌套类、嵌套初始化块)。
Average Block Depth(平均代码块嵌套深度) 2.58 整体代码块的平均嵌套深度约 2.6 层,属于较浅的嵌套,结构清晰。

三、可读性与维护性指标

指标数值说明
Percent Lines with Comments(注释行占比) 0.0 代码无任何注释,可读性较差,后续维护时理解逻辑成本高。
Method Call Statements(方法调用语句数) 12 代码中包含 12 次方法调用,说明存在一定的逻辑封装(如调用工具类、API 等)。
Percent Branch Statements(分支语句占比) 20.8 分支语句(if、switch 等)占可执行语句的 20.8%,但结合 “最大复杂度为 0” 推测:这些分支可能是简单的 “非业务逻辑分支”(如异常捕获、参数校验)。

总结:
代码规模与结构:单文件约 343 行,含 2 个类,可执行语句 24 行,类平均方法数 0.5,方法平均语句数 20 行,属于结构简单的中等规模代码。
复杂度与可读性:无方法逻辑复杂度(圈复杂度 0),代码块最大嵌套深度 5、平均 2.58,分支语句占比 20.8%;但无任何注释,可读性差。
结构分布:代码块嵌套深度以 3 层为主,语句分布集中,整体结构清晰且层次浅。

2.第三次题目集设计与分析

(1)类结构设计

LiftDirection枚举类:定义电梯运行状态(UP/DOWN/STOP),统一状态标识,避免魔法值。

LiftRequest抽象类:作为所有电梯请求的父类,定义请求的通用属性(楼层号)和抽象方法(方向判断、内外请求区分)

电梯内请求类CabinRequest:封装电梯内部目标楼层请求及特殊约束

电梯外请求类HallRequest:封装外部呼叫请求,关联呼叫楼层与目标楼层

电梯实体类Lift:封装电梯的物理状态和基础动作(上下移动、方向设置)

请求管理类RequestHandler:接收、过滤、存储请求,提供请求的添加 / 移除 / 查询接口,避免重复请求

电梯控制类LiftOperator:核心控制逻辑(请求优先级判断、运行方向决策、启停控制、门开关处理)。

主类Main接收输入参数(楼层范围、请求指令)、解析请求、初始化组件、启动电梯运行。

类结构图设计:

image

  (2)调度逻辑分析

采用「同向优先 + 距离补位」的调度策略,先处理与电梯当前运行方向一致的请求,方向冲突时按距离近的优先,全程通过请求队列管理和优先级判断实现自动化运行

一、调度核心流程(从启动到结束)
1. 初始化与请求预处理(Main 类)
读取输入参数:电梯运行的最低 / 最高楼层,以及所有请求指令(外部呼叫 / 内部目标楼层)
预处理请求:解析请求类型(HallRequest/CabinRequest),标记 CabinRequest 的downOnly属性(是否只能下行到达),过滤无效楼层请求
初始化组件:创建电梯实例(Lift)、请求管理器(RequestHandler)、电梯控制器(LiftOperator),并将请求添加到对应队列
2. 电梯启动与循环运行(LiftOperator.start ())
启动后先输出初始状态(最低楼层,方向向上)
进入主循环:只要有未处理请求且未达最大运行步数,就重复「决策方向→移动→判断是否停站→处理停站」流程
二、核心调度逻辑拆解
1. 方向决策逻辑(setNextDir ())
无请求时:电梯设为 STOP 状态,退出循环
有请求时:先获取优先级最高的请求(getTopPriorityReq ()),根据请求楼层与当前楼层的关系设置方向
请求楼层 > 当前楼层 → 向上(UP)
请求楼层 < 当前楼层 → 向下(DOWN)
已在请求楼层 → 保持当前方向(避免停止后重新启动的方向混乱)
2. 请求优先级判断(getTopPriorityReq ())
这是调度逻辑的核心,优先级规则按顺序执行
规则 1:空队列优先 —— 外部请求队列为空则取内部请求,反之亦然
规则 2:同向优先 —— 判断请求是否与电梯当前方向一致(isSameDir ()),仅同向请求优先处理
向上方向:仅处理「上行请求且目标楼层≥当前楼层」
向下方向:仅处理「下行请求且目标楼层≤当前楼层」
规则 3:距离补位 —— 若两个请求方向相同或都不同向,取距离当前楼层更近的请求
3. 停站与请求处理(needStop () + handleCurrentFloor ())
停站判断:当前楼层等于优先级最高请求的楼层时,触发停站
停站处理:
外部请求(HallRequest):开门→关门,自动将乘客的目标楼层转为内部请求(CabinRequest)加入队列,移除已处理的外部请求
内部请求(CabinRequest):开门→关门,直接移除已处理的内部请求
三、关键细节补充
1. 请求去重与过滤(RequestHandler.addRequest ())
过滤无效请求:楼层超出电梯运行范围(minFloor~maxFloor)的请求直接丢弃
避免重复请求:通过 HashSet 存储已存在的请求,确保同一请求(同类型、同楼层、同约束)不会重复加入队列
2. CabinRequest 的 downOnly 属性作用
由 Main 类解析时标记:当请求楼层是所有请求中的最高层且已经过后,downOnly设为 true
影响方向判断:downOnly=true时,该内部请求仅被视为下行请求,避免电梯在下行过程中遗漏该请求
四、调度逻辑的优势与局限
优势:
符合现实电梯运行习惯:同向优先策略减少频繁变向,提升运行效率
逻辑闭环:从请求解析、优先级判断到停站处理,全流程自动化,无明显逻辑断层
容错性强:通过请求过滤、去重和最大运行步数限制,避免无效请求和死循环
局限之处:
未考虑请求超时:无请求超时机制,若请求长期未被处理(如被高优先级请求持续插队),可能导致等待过久
单一优先级维度:仅考虑方向和距离,未支持特殊场景(如消防、VIP 优先)
队列顺序固定:外部 / 内部请求队列均为 FIFO,同一优先级内无法动态调整顺序

(3)源码指标分析(基于SourceMonitor) 

image

 

image

image

 关键指标数据速览:

代码行数(Lines):381 行,属于中等规模的代码文件
类与接口数量(Classes and Interfaces):3 个,说明代码采用了一定的面向对象设计(如Lift、RequestHandler、LiftOperator等类的拆分)
平均每个类的方法数(Methods per Class):14.67,这个数值偏高,意味着部分类(如Main类)可能承担了过多职责,存在职责不单一的风险
平均每个方法的语句数(Average Statements per Method):6.00,处于合理范围,说明方法颗粒度较适中
二、代码复杂度:
最复杂方法(Name of Most Complex Method):Main.main(),复杂度为 13,且块深度(Max Block Depth)达到 6。这说明main方法内存在多层嵌套逻辑(如循环、条件判断的嵌套),可读性和维护性较差,是重点优化对象
平均复杂度(Average Complexity):6.00,整体复杂度处于中等水平,但因main方法的高复杂度,需关注局部模块的重构
分支语句占比(Percent Branch Statements):19.6%,说明代码中条件判断(if、else、switch等)较多,逻辑分支较密集
三、代码质量与可维护性:
注释行占比(Percent Lines with Comments):5.8%,注释比例偏低。对于逻辑较复杂的模块(如电梯调度、请求优先级判断),缺乏注释会增加他人理解代码的成本
块深度分布(Block Histogram):大部分语句集中在块深度 0-2 层,这是健康的,但深度 6 的块(对应Main.main())属于 “深嵌套”,需通过提取子方法、拆分逻辑来降低嵌套层级
总结与优化建议:
重构Main.main():将其中的请求解析、组件初始化、流程控制等逻辑拆分为多个子方法,降低复杂度和嵌套深度
类职责拆分:Main类承担了输入解析、组件初始化、流程启动等过多职责,可拆分出InputParser、AppStarter等类,实现单一职责
补充注释:在复杂逻辑(如请求优先级判断、电梯方向决策)处添加关键注释,说明设计意图和逻辑分支
方法粒度优化:对于复杂度较高的方法(如LiftOperator中的调度逻辑),可进一步拆分功能,让每个方法只做一件事

 四、改进建议

第一次作业代码改进:
核心结论:代码功能完整但存在请求处理逻辑冗余、边界校验不足、性能优化空间等问题,需针对性优化
1. 减少请求处理逻辑冗余
合并processUpStop()、processDownStop()和processRemainingCalls()方法,提取通用的 “处理楼层请求” 逻辑,避免重复的开关门和请求移除代码
移除callQueue.getRequestCount() == 1的特殊处理分支,统一用通用逻辑处理所有停靠场景,降低代码复杂度
2. 强化输入与边界校验
在addInternalTarget()和addExternalCall()中,增加对 “目标楼层与当前楼层相同” 的提示输出,让用户明确知道该请求未被添加。
对外部呼叫请求,补充 “顶层 UP 请求”“底层 DOWN 请求” 的无效提示,便于用户排查输入问题
3. 优化队列查询性能
将CallQueue的链表结构改为LinkedList<CallNode>存储,利用 Java 集合的内置方法替代手动遍历查找,提升searchUpTarget()、searchDownTarget()等方法的查询效率
新增isEmpty()方法替代hasNoRequests(),命名更符合 Java 集合的通用规范,提升代码可读性
4. 修复方向切换逻辑漏
在findTargetInCurrentDir()中,切换方向后若仍无目标,需将电梯方向设为STOPPED,避免无效的方向循环
第二次作业代码改进:
核心结论:代码架构清晰但存在请求去重过度、优先级逻辑单一、状态输出不规范等问题,需优化合理性和易用性
1. 调整请求去重策略
移除processedReqs集合的强去重逻辑,改为 “允许同一楼层同一类型请求重复添加”,符合电梯实际使用场景(多人连续按同一按钮)
若需避免重复处理,可在addReq()中仅过滤 “当前正在处理的请求”,而非所有历史请求
2. 优化优先级判断逻辑
扩展getPriorReq()方法,除 “同向优先”“距离优先” 外,增加 “内部请求优先于外部请求” 的规则(电梯内乘客需求更紧急)
修复isReqInCurrDir()中 “静止状态返回 false” 的问题,静止时应默认按距离判断优先级,而非直接归类为不同向
3. 规范状态输出与异常处理
初始化时若电梯初始楼层为minFloor,首次输出方向应为IDLE而非UP,符合电梯静止初始状态
在parseReqStr()中,对无效格式或楼层的输入,明确输出错误提示(如 “无效楼层:超出电梯范围”),而非仅返回null
4. 补充边界场景
在run()方法中,当电梯到达maxFloor或minFloor时,强制切换方向,避免在端点处无效循环
新增 “无请求时输出最终静止状态” 的逻辑,确保输出完整(如 “Current Floor: X Direction: IDLE”)
第三次作业代码改进:
核心结论:代码新增外部请求转内部请求逻辑,但存在解析格式错误、方向控制僵硬、请求管理混乱等问题,需修复逻辑漏洞并优化结构
1. 修复请求解析格式错误
修正parseRequest()方法,外部请求格式应为<楼层, UP/DOWN>而非<来源楼层, 目标楼层>,需重新适配输入格式(参考前两次作业的外部请求格式)
新增解析格式校验,对不符合<数字>或<数字, 方向>的输入,输出明确错误提示(如 “无效请求格式:请输入 < 楼层> 或 < 楼层,UP/DOWN>”
2. 优化方向控制与停靠逻辑
改进setNextDir()方法,当电梯到达目标楼层且无同向请求时,主动切换方向查找反向请求,避免 “停在楼层不动” 的问题
修复needStop()方法,除优先级请求外,需检查当前楼层是否有其他待处理请求(如同一楼层的不同类型请求),确保所有请求都能被处理
3. 规范请求管理与去重
优化RequestHandler的addRequest()方法,区分 “外部请求转内部请求” 与 “原生内部请求”,避免重复添加同一目标楼层的内部请求
新增clearProcessedReq()方法,定期清理已完成的请求,避免existingReqs集合无限增大,提升性能
总结:

从三次电梯调度系统的迭代开发里,能清晰看到一段 “从能用、到好用、再到贴近真实” 的成长轨迹
第一次迭代像摸着石头过河,核心目标是 “让电梯跑起来”。把所有逻辑打包进核心类,虽然有点 “一锅炖”,但硬是啃下了 “同向优先、逐层停靠” 的基础功能,解决了从 0 到 1 的关键问题 —— 毕竟先实现再优化,才是最实在的开发思路
第二次迭代明显开始 “精打细算”,学会了给代码 “分工”。拆分出请求类、队列类和控制类,让每个模块各司其职,还加了无效请求过滤、重复请求去重,就像给电梯加了 “智能筛选器”。这时候已经不满足于 “能跑”,更追求 “跑的稳、不添乱”,比如通过 “同向优先 + 距离就近” 的策略减少无效折返,悄悄提升了运行效率
第三次迭代则更进一步,开始往 “真实场景” 靠拢。新增外部请求自动转内部请求的逻辑,就像考虑到 “乘客按了呼梯键,肯定是要去某个楼层”,让系统更懂用户需求。但过程中也踩了坑,比如请求格式解析出错、方向控制不够灵活,这些小问题恰恰说明,开发不只是写代码,更要站在 “用电梯的人” 的角度想问题 —— 既要让电梯高效运行,也要避免让乘客等太久、或者按了按钮没反应的尴尬。
回头看这三次迭代,不只是代码结构从 “杂乱” 到 “清晰” 的转变,更是从 “只关注功能” 到 “兼顾效率与体验” 的思维升级。每一次改进都是对前一次不足的弥补,每一个 bug 修复都让系统更贴近真实生活中的电梯逻辑。虽然还有可优化的空间(比如没考虑请求超时、特殊场景优先),但这种一步步打磨、不断贴近用户需求的过程,正是开发中最有温度的部分 

posted @ 2025-11-22 19:08  hhh03  阅读(0)  评论(0)    收藏  举报