电梯调度程序

前言:
前三个题目集的题量都适中,除电梯调度程序外,其他题目难度都不大,都在教我们面向对象的设计、单一职责原则等,而且都给了类图,只要照着写就可以了,但这三次电梯程序都属于中等偏上难度,其题目要求较多,需要同时处理多个请求并且实现方向切换、状态切换等功能,主要难度在对题目具体需求的分析和算法的实现,其实我认为最难的在第一个题目集,只要理清了思路就没有什么大问题了,但难就难在理清这个思路,其实我一开始对题目需求的分析完全错误,在错误的分析下写代码浪费了不少时间,一直到老师讲解之后我才明白题目的要求,虽然我没有通过第一个电梯调度程序的测试点,但还是在不断调试修改后在已完成题目集里通过了,这对我后来的两次作业有很大帮助,后来的两次迭代都是在这个基础上做一些修改,使其更好实现单一职责、职责更完善,其实就没有那么难了。

设计与分析:

第一次作业
类图设计如下

对类图的分析:
Request 类

  • 职责:表示电梯的外部请求(如用户按下楼层按钮)。
  • 属性:
    dir(方向):字符串类型,描述请求方向(如 "UP" 或 "DOWN")。

floor(楼层):整型,表示目标楼层。

  • 方法:

  • 构造函数:
    Request():默认构造函数。

Request(String requestFloor, String requestDirection):通过字符串参数初始化楼层和方向(需内部转换逻辑)。

  • Setter 方法:
    setdir(String direction):设置方向。

setfloor() 和 setfloor(String Floor):设置楼层,后者接受字符串参数并可能包含转换逻辑。

setDir():可能用于获取或设置方向(存在命名歧义)。

Elevator 类

  • 职责:模拟电梯的运行逻辑,管理内部和外部请求,并控制移动。

  • 属性:
    maxFloor 和 minFloor:整型,表示电梯运行的范围。

currentFloor 和 lastArrivedFloor:记录电梯当前位置和历史位置。

direction 和 status:字符串类型,描述电梯的当前方向和状态(如 "IDLE" 或 "MOVING")。

internalRequest:LinkedList,存储电梯内部按钮按下的目标楼层。

externalRequest:LinkedList(存在语法错误,应为LinkedList),存储外部用户的请求。

  • 方法:

  • 构造函数:
    Elevator():默认构造函数。

Elevator(int maxFloor, int minFloor):初始化电梯的楼层范围。

  • 核心功能:

move():控制电梯移动的主逻辑。

moveToFloor(int targetFloor):直接移动至指定楼层。

addRequest(String Request):添加请求(参数类型应为Request)。

  • 辅助功能:

setDirection() 和 setStatus():设置方向和状态。

printFloor() 和 openAndPrint():输出当前楼层和开门状态。

Main 类

  • 职责:程序的入口,协调电梯和请求的交互。

  • 方法:

main(String[] args):初始化电梯对象,接收用户输入(如请求生成),并触发电梯运行逻辑。

  • 类间关系
  • 依赖关系:

Main 类依赖 Elevator 和 Request,负责创建它们的实例并调用方法。

  • 聚合关系:

Elevator 类通过 externalRequest 聚合多个 Request 对象,表示电梯管理外部用户的请求队列。

internalRequest 聚合多个整数楼层值,表示内部按钮按下的目标楼层。

  • 系统运作流程

请求生成:用户通过 Main 类创建 Request 对象(如输入楼层和方向)。
请求添加:Main 调用 Elevator.addRequest() 将请求加入队列。
电梯移动:Elevator.move() 根据请求队列计算移动路径,更新楼层和方向。
状态输出:通过 printFloor() 和 openAndPrint() 显示电梯实时状态。

源码分析:
Elevator和Request类

SourceMonitor分析结果

  1. 方法复杂度极高
    move()方法圈复杂度为42:远超合理范围,表明该方法包含大量条件分支(如if-else或switch),逻辑臃肿,维护和测试难度极大。

  2. 深层嵌套结构
    最大块深度为7层:代码中存在多层嵌套(如循环或条件语句叠加),导致可读性差,且容易遗漏边界条件处理。

深度≥3的语句占比46%:近半数代码处于深层嵌套中,增加逻辑错误风险。

  1. 注释严重不足
    注释率仅9.8%:关键逻辑(如move())缺乏文档说明,代码意图不清晰,增加协作和维护成本。

  2. 高频方法调用
    move()调用其他方法71次:高频调用可能导致性能瓶颈,且隐含强耦合性,增加调试和扩展难度。

  3. 块深度分布失衡
    深度≥3的语句占比显著:代码结构偏向复杂嵌套,而非扁平化设计,违反“避免深层嵌套”的编码原则。

  4. 异常处理与健壮性缺失
    未体现输入校验或异常捕获:报告中无异常处理相关指标,推测代码对非法输入(如无效楼层)缺乏防护,运行时崩溃风险高。

  5. 方法职责不清晰
    move()包含80条语句:违反单一职责原则,承担移动控制、状态更新、请求处理等多重任务,功能混杂。

  6. 潜在的性能问题
    高复杂度与高频调用结合:可能导致算法效率低下(如重复计算或冗余操作),影响系统响应速度。

Main类

SourceMonitor分析结果

  1. 注释率低(4%)
    代码中仅4%的行包含注释,关键逻辑(如初始化、输入处理)缺乏说明,导致可读性和可维护性低下。

  2. main 方法职责过重
    main 方法包含13条语句和10次方法调用,承担初始化、输入处理、逻辑控制等多重职责,违反单一职责原则,难以扩展和维护。

  3. 嵌套深度问题
    最大块深度为3层,表明存在多层条件或循环嵌套(如if中嵌套while),增加代码理解难度和潜在逻辑错误风险。

  4. 方法调用耦合度高
    main 方法调用了10次其他方法,高频调用可能导致代码耦合度过高,难以独立测试或修改某一功能模块。

  5. 异常处理缺失
    报告中未体现输入校验(如非数字输入)或异常处理逻辑(如try-catch块),程序可能在非法输入时崩溃。

  6. 代码结构单一化
    仅1个类(Main)和1个方法(main),未通过合理封装拆分功能模块,代码复用性和扩展性不足。

心得
这个题目要求每次只看内外需求队列的队头,不考虑后面的需求,这和一般的look算法不同,一开始我没有理解到这一点,算法完全写错了,次次提交都是运行超时或者答案错误,后来发现是陷入死循环了,
第一次设计时也没有关注代码的复杂度和复用性,只想把这个测试点过了,导致代码逻辑有些许混乱,可读性差,找错误也很困难,在后续进行单一职责划分时需要进行大量修改。

第二次作业
类图已经在题目里给出

对类图的分析
相比于第一次类的设计,第二次的类图优化了许多:

  1. 职责单一化:第二张图把电梯的控制逻辑从Elevator类分离到Controller类,请求管理功能交给RequestQueue类。这样每个类专注自身核心功能,修改其中某部分逻辑时,不会过多影响其他类。例如若要调整电梯请求处理逻辑,只需在Controller类中操作,不用在Elevator类复杂的代码里找相关逻辑,降低维护难度。

  2. 层次结构清晰:通过新增类构建起更清晰的层次结构。Controller类协调Elevator和RequestQueue类工作,依赖关系明确。当系统需求变化,如添加新的电梯控制策略,可在Controller类中扩展,不破坏整体结构,维护起来更方便。

  3. 请求处理扩展:第二张图将Request细分为ExternalRequest,专门处理外部楼层请求。当有新的请求类型(如特殊楼层请求、紧急请求等)加入时,可基于现有的请求类结构进行扩展,添加新的请求子类,而不用大幅修改整体请求处理逻辑。

  4. 功能模块扩展:由于类职责清晰,添加新功能时能找到明确的入口。例如要实现电梯的智能调度功能,可在Controller类中添加相关算法和逻辑,利用现有的类间协作关系,快速集成新功能,不会对现有稳定功能造成干扰。
    源码分析

    SourceMonitor分析结果

  5. 高复杂度方法

Controller.getNextFloor() 的复杂度高达 51(最大复杂度),包含 78 条语句,嵌套深度为 8,且调用了 98 次其他方法。此方法极可能存在过度耦合、逻辑冗余或难以维护的风险。

Controller.determineDirection() 复杂度为 17,包含 28 条语句,表明其逻辑分支复杂,可能包含大量条件判断或循环结构,易引入错误。

  1. 深层嵌套问题

最深层块深度为 8(行号 228),且存在 19 条语句 位于深度 4 的块中。深层嵌套可能导致代码可读性差、逻辑混乱,增加调试和维护成本。

  1. 注释不足

仅有 12.6% 的代码行包含注释,可能影响代码的可理解性,尤其是对于高复杂度方法(如 getNextFloor())缺乏必要的解释。

  1. 方法调用频繁

方法调用语句占比 26.2%,且部分方法(如 getNextFloor())调用次数极高(98 次),可能引发性能瓶颈或过度依赖问题。

  1. 代码冗余风险

存在重复命名的构造函数(如 Controller.Controller() 出现两次),可能为冗余代码或设计缺陷的迹象。

  1. 块深度分布不均

大部分语句集中在深度 1-3,但深度 4 及以上仍有 54 条语句(深度 4 的 19 条 + 更深层的未明确统计部分),增加了逻辑复杂性和潜在错误点。

心得
这段代码在初期追求功能完备性的过程中,忽视了模块化设计、注释规范和技术债管理,最终导致核心逻辑陷入“越改越复杂,越复杂越难改”的恶性循环。这一教训深刻印证了“代码是写给人看的”这一原则,虽然经过单一职责划分,代码还是存在缺乏可读性和可维护性的问题。

第三次作业
类图已经在题目中给出

对类图的分析
与前两次类的设计相比:

  1. 新增Passenger类:本次类图新增了Passenger类,明确表示乘客信息,包含sourceFloor(起始楼层)和destinationFloor(目标楼层)属性 。相较于前两次仅用简单的楼层请求表示,现在可以更全面地模拟乘客行为,比如可以基于乘客的出发和目的楼层实现更复杂的调度策略,如优先处理同方向乘客等。

  2. 请求队列关联变化:RequestQueue类中,请求队列的泛型类型从之前的楼层相关类型(如Integer 、ExternalRequest )变为Passenger 。这使得请求队列管理的是具体乘客请求,能够更好地跟踪乘客的行程,为实现个性化的电梯服务和更精准的调度算法提供了基础,在处理请求逻辑上更加贴合实际场景。

  3. Controller类中新增了addExternalRequest(Passenger passenger)方法,用于添加外部乘客请求。
    源码分析

    SourceMonitor分析结果

  4. 核心方法复杂度恶化
    Controller.getNextFloor()方法进一步膨胀:复杂度保持 51,但语句数从 78 增加至 83,方法调用次数从 98 提升至 106。这表明该方法在迭代中持续承担过多职责,逻辑复杂度和耦合度进一步加剧,成为代码稳定性的重大隐患。

高频调用与性能风险:106次方法调用远超常规水平,可能引发性能瓶颈,尤其在多电梯或高并发场景下,响应延迟风险显著增加。

  1. 深层嵌套问题扩散
    嵌套层级分布恶化:深度5的块中新增 34条语句,深度6的块包含 13条语句,表明深层嵌套逻辑未得到控制,反而在扩展中新增复杂条件分支。

最深层仍为8级嵌套(行号210),且深度4及以上语句总数达到 80条(原为54条),代码可读性和维护成本进一步升高。

  1. 注释覆盖率持续降低
    含注释的代码行占比从 12.6% 下降至 11.4%,关键方法(如getNextFloor())依然缺乏必要注释。这会加剧团队协作中的理解成本,尤其是新增逻辑(如深度5-6的嵌套块)缺乏解释,易引发误修改。

  2. 类与构造函数设计混乱
    冗余构造函数泛滥:

Passenger类存在 3个构造函数,其中Passenger()的语句数分别为1、2、0,可能存在未初始化的构造逻辑或冗余代码。

Controller和RequestQueue类的构造函数重复定义问题未解决,暗示设计规范缺失。

职责分配失衡:

Controller类仍集中了核心调度逻辑(如getNextFloor()、determineDirection()),而Passenger类仅作为数据容器,未封装行为逻辑,违背面向对象封装原则。

  1. 块深度与复杂度分布极端化
    复杂度两极分化:平均复杂度从 3.47 升至 3.64,最大值保持 51,表明代码健康度整体下降,局部问题更加突出。

块深度分布畸形:深度0-2的语句占比 66.7%(141/211),而深度5-8的语句占比 28.4%(60/211),深层逻辑碎片化严重,调试和测试难度陡增。

心得
对比上一次分析,此次度量结果显示代码问题未得到遏制,反而在复杂度、嵌套深度和类设计混乱度上进一步恶化了,核心方法getNextFloor()的复杂度增加、深层嵌套的扩散以及注释的缺失,体现出“功能优先”的设计理念,也就是为了过测试点而忽略了代码的设计。

踩坑心得

  1. 在代码调试阶段,运行超时问题频发,算法层面,时间复杂度较高的操作大量存在,例如,多重嵌套循环结构在处理大规模数据时,计算量增长很快,占用过多运行时间。递归调用过于复杂,存在大量重复计算,也会拖慢速度。 而死循环是成为更棘手的难题,因代码架构设计欠佳,函数间调用逻辑紊乱,缺乏清晰的控制流规划,比如,函数A调用函数B,B又反向调用A,且未设置有效终止条件,易陷入死循环。加之代码注释不足,关键逻辑未作说明,代码结构与意图隐晦难明,因此在排查死循环时,因代码可读性差,难以追踪变量状态与函数调用轨迹,无法快速判定是循环变量未正确更新,还是条件判断逻辑有误,导致循环持续无法终止,对递归调用的深度和范围也难以把控,难以定位导致死循环的具体代码片段,大幅增加调试难度与工作量。
  2. 在进行设计时总是以功能优先,其次才考虑设计的合理性,导致程序的复用性和可扩展性差,算法和性能也会出现问题,也正因为如此,程序经常运行超时,还难以纠错。

改进建议

  1. 聚焦高复杂度方法,通过模块化与设计模式,降低代码复杂度臃肿度。

  2. 简化嵌套结构,增加注释,确保代码关键部分意图明确,提升可读性。

  3. 优化类设计:统一冗余构造函数,合理分配类职责(如将调度职责分配至RequestQueue),遵循单一职责原则。

总结
经过这几次大作业,我已经完完全全适应了java这门语言,学习到了面向对象的设计以及单一职责的划分原则,要尽量减少方法的长度;还让我注意到了注释的必要性,在程序的关键部分注释是必不可少的,这对提高代码的可读性有大大的帮助;这三次题目集之后我发现设计类的能力还不是很好,需要在以后的训练中不断进步才行。

posted @ 2025-04-20 11:22  今天收到roy6了吗  阅读(59)  评论(0)    收藏  举报