三次电梯迭代总结blog

Posted on 2025-04-20 20:28  java学不会呀  阅读(55)  评论(0)    收藏  举报
一:前言:
在面向对象程序设计的学习过程中,电梯调度问题是一个经典的综合实践案例,能够有效考察类设计、状态管理、队列操作及调度算法等核心能力。本文将对三次电梯题目集的设计要求、知识点演进及难度变化进行总结,展现从单一类实现到遵循设计原则的迭代优化过程,以及如何通过职责拆分和类结构调整逐步构建更健壮的系统。
第一次题目:基础电梯类设计(单一类实现) 知识点: 面向对象基础:类的封装(属性、方法)、状态机设计(电梯状态:停止、移动、开门、关门)、队列数据结构(内部请求队列、外部上下行请求队列)。 调度逻辑:方向优先策略(同方向请求优先处理)、状态转换规则(静止→移动的触发条件)、输入验证(无效楼层过滤)。 模拟流程:键盘输入处理、输出格式控制(楼层移动与开关门事件的差异化输出)。
第二次题目:迭代设计与单一职责原则(类拆分) 知识点演进: 设计原则:单一职责原则(SRP),将原电梯类的职责拆分为 电梯类(状态与动作)、乘客请求类(请求属性与有效性校验)、队列类(请求去重与管理)、控制类(调度逻辑)。 输入处理增强:过滤无效请求(超界楼层)和重复请求(连续相同请求去重),确保请求队列的有效性。 类协作:通过控制类协调电梯与请求队列的交互,电梯类专注于状态变更和物理动作(移动、开关门),请求类封装请求属性及校验逻辑。
第三次题目:引入乘客类与请求流程重构 知识点深化: 领域建模:新增 乘客类,将外部请求从 “源楼层 + 方向” 改为 “源楼层 + 目的楼层”,外部请求处理后需将目的楼层加入内部队列,体现乘客从候梯到乘梯的完整流程。 职责进一步细化:乘客类封装请求的源与目的楼层,电梯类通过控制类接收处理后的内部请求,队列类统一管理不同阶段的请求(外部请求入队→处理后转换为内部目的楼层请求)。 边界处理:确保外部请求到内部请求的转换逻辑正确,避免因请求格式变更导致的调度错误。
题目难度总结:难度属于中上水平,题目给出了提示以及类的设计参考图,只要认真阅读题目的提示以及参考图还是可以写出来的。
二:设计与分析:
第一次电梯题目:

类的设计:

Main类:处理输入的数据,创建电梯类、电梯外请求队列和电梯内请求队列。用循环调用电梯运行方法,操控电梯。
Elevator类:成员变量( 最小楼层数、最大楼层数、当前楼层、运行方向、运行状态、电梯内请求队列和电梯外请求队列)。已及相应的构造方法和get\set方法。有着电梯相关信息包括请求队列,有电梯运行、开门、寻找下次目标楼层、转向等方法。
 SourceMontor分析结果:

分析:该代码整体复杂度良好(平均2.39),但存在局部高复杂度问题,尤其Elevator.runElevatorForTheFirstTime()方法复杂度达13(含22条语句)和Main.main()方法30行过长,是需要特别关注并且优化。代码嵌套较深(20处4层嵌套),可读性差,不利于后期维护。注释率11.6%略低,可读性差,看懂代码花费时间长。应该拆分复杂方法,补充关键注释,并将Main逻辑抽取到独立类,可以提升代码的可维护性。控制流复杂度适中(25.5%),方法平均长度合理(8.05行),重点需解决少数高复杂度方法的逻辑集中问题。
第二次题目:

sourceMonter报表:

 

分析:在第二次的电梯作业中,因为题目中给出了参考类图,所以相对的工作量不会很大,也更有思路,相较于第一次的题目集,这次的代码基本符合了类的单一职责原则,有276行代码,对目前的我来说也算是多的,有五分之一的分支语句,方法的调用语句有118条,方法调用频繁,但是代码的注释依然还是没有,严重影响了代码的可读性和可维护性,平均每个类的方法较多,方法规模小,功能单一,整体代码复杂程度适中,但存在局部复杂点,以及存在一些逻辑错误,毕竟我也没有将此次的题目集电梯题做出来,代码存在局部复杂度高的地方,还有改进的地方。
第三次题目分析:

代码整体分析:

由于与第二次大作业相差不大,所以代码的行数和语句数还有分支语句也没有变化很大,依旧是没有注释,类和接口数多了一些,方法规模较小,平均深度还行,但是最大深度较大,局部逻辑嵌套较深,增加理解与调试难度,依然存在局部复杂度过高的问题,逻辑几乎与第二次相同,所以结果可想而知的是失败了,但也是满足了类的单一职责原则。

三、采坑实录:

1. 第二次迭代:职责拆分不彻底导致的逻辑混乱(PTA 通过率从 60% 到 90% 的血泪史)
问题描述:
在第二次作业中,错误地将请求校验逻辑放在电梯类而非请求队列类,导致电梯类同时处理状态变更和请求过滤,违反 SRP 原则。例如,当输入无效楼层<0,UP>时,电梯类未正确忽略,仍尝试加入队列,引发后续空指针异常。
源码证据(错误实现):
// 电梯类中错误包含请求校验
public void addExternalRequest(String input) {
    if (input.contains("END")) return; // 错误!未校验楼层范围
    // 直接解析输入并加入队列,未判断楼层是否在[min, max]之间
}
测试复现:
输入<0,UP>后,电梯输出Current Floor: 1 Direction: UP后报错,因为队列中存在无效楼层,调度时计算方向出错。
解决方案:
将请求校验逻辑移至请求队列类,新增isValidRequest()方法:
 
// 请求队列类中的校验逻辑
private boolean isValidRequest(String input, int minFloor, int maxFloor) {
    // 解析楼层,判断是否在[min, max]之间,且方向合法
    if (floor < minFloor || floor > maxFloor) {
        return false; // 自动忽略无效请求
    }
}
改进效果:PTA 通过率从 60% 提升至 90%,无效请求处理逻辑与电梯状态解耦。
2. 第三次迭代:外部请求转换遗漏导致的 “乘客失踪” BUG
问题描述:
外部请求格式变更为<源楼层,目的楼层>后,未正确将目的楼层加入内部队列。例如,外部请求<3,5>被处理后,电梯在 3 楼开门,但未将 5 楼加入内部请求,导致电梯关门后直接跳过 5 楼。
调试日志:
Open Door # Floor 3  // 正确停靠源楼层
Close Door
// 应处理内部队列中的5楼,但此时队列为空,电梯直接反向
Current Floor: 2 Direction: DOWN  // 错误!应前往5楼
源码漏洞:
在控制类处理外部请求时,仅移除了源楼层请求,未添加目的楼层:
 
// 错误:未将目的楼层加入内部队列
if (request instanceof ExternalRequest) {
    queue.remove(request); // 移除外部请求
    // 缺少:internalQueue.add(request.getDestinationFloor());
}
修复代码:
// 正确逻辑:处理外部请求后,将目的楼层加入内部队列
if (request instanceof ExternalRequest) {
    ExternalRequest externalReq = (ExternalRequest) request;
    internalQueue.add(externalReq.getDestinationFloor()); // 添加目的楼层
    externalQueue.remove(externalReq); // 移除已处理的外部请求
}
测试验证:输入<3,5>后,电梯在 3 楼开门,关门后输出Current Floor: 4 Direction: UP,最终在 5 楼正确停靠,BUG 修复。
3. 通用坑点:重复请求去重的 “边界盲区”
三次迭代中均出现重复请求未完全过滤的问题,例如连续输入<3><3><3>时,第一次实现仅过滤了完全相同的相邻请求,但未处理间隔重复(如<3><5><3>)。
错误逻辑:
// 仅检查相邻请求是否相同(错误)
if (queue.isEmpty() || lastRequest != currentRequest) {
    queue.add(currentRequest);
}
正确实现:
使用Set存储请求去重,确保队列中无重复楼层(内部请求)或相同源楼层 + 方向(外部请求):
// 内部请求队列使用LinkedHashSet保持顺序并去重
private Set<Integer> internalRequests = new LinkedHashSet<>();

四:改进建议:让代码更 “抗揍” 的可持续方案

1. 输入处理:封装成独立的 “请求解析器” 类
将输入解析与校验逻辑从控制类剥离,新增RequestParser类:
 
public class RequestParser {
    public static Request parse(String input, int minFloor, int maxFloor) {
        if (input.startsWith("<") && input.endsWith(">")) {
            // 解析内部/外部请求,校验楼层范围
            if (isInternalRequest(input)) {
                int floor = Integer.parseInt(input.substring(1, input.length()-1));
                if (floor >= minFloor && floor <= maxFloor) {
                    return new InternalRequest(floor);
                }
            } else if (isExternalRequest(input)) {
                // 解析源楼层和目的楼层,校验范围
            }
        }
        return null; // 无效请求
    }
}
优势:后续需求变更(如请求格式调整)时,只需修改解析器,不影响电梯核心逻辑。
2. 调度算法:策略模式分离方向决策逻辑
将电梯方向决策(如 “同方向优先”)封装为策略接口DirectionStrategy,不同调度策略(如经典 SCAN 算法、LOOK 算法)实现该接口:
 
public interface DirectionStrategy {
    Direction decideNextDirection(Elevator elevator);
}

// 实现类:同方向优先策略
public class SameDirectionFirstStrategy implements DirectionStrategy {
    @Override
    public Direction decideNextDirection(Elevator elevator) {
        // 判断当前方向是否有未处理请求,否则反转方向
    }
优势:后续扩展调度算法时,只需新增策略类,符合开闭原则。
3. 日志与调试:结构化输出关键状态
在电梯移动、开关门、请求处理时打印详细日志,例如:
// 电梯类移动方法
public void moveTo(int targetFloor, Direction direction) {
    System.out.printf("Current Floor: %d Direction: %s%n", currentFloor, direction);
    // 记录当前状态到日志文件(可选)
}
实践价值:在第三次作业调试 “目的楼层未入队” 时,通过日志快速定位到控制类中缺失的internalQueue.add()语句。

五、阶段总结:从面向过程到面向对象的思维跃迁

1. 核心能力提升
  • 类设计原则:从第一次的 “大杂烩” 电梯类,到第三次严格遵循 SRP 的四层架构(控制类 + 电梯类 + 乘客类 + 队列类),理解了 “每个类只做一件事” 的重要性 —— 例如Elevator类只负责状态变更(移动、开关门),ControlClass专注调度逻辑,QueueManager处理请求去重与队列操作。
  • 边界条件处理:学会预判无效输入(楼层超界、格式错误)、重复请求(连续 / 非连续重复)、状态异常(静止时无方向、开门时移动),并通过前置校验(如RequestParser)和防御性编程(非空判断)避免崩溃。
  • 调试方法论:掌握 “日志驱动调试”,通过输出关键变量(当前楼层、内外队列内容、运行方向)快速定位逻辑漏洞,例如第三次作业中通过打印internalQueue发现目的楼层未入队的问题。
2. 待改进方向
  • 并发处理:三次作业均假设串行请求,但实际电梯需处理并发请求(如多个请求同时到达)。后续可学习多线程编程,使用BlockingQueue实现线程安全的请求队列。
  • 性能优化:当前调度算法为 “同方向优先”,但未考虑最优路径(如电梯在上升时,优先处理上方所有同向请求,而非逐个处理)。可研究电梯调度经典算法(如 SCAN、LOOK),实现更高效的请求合并。
  • 代码复用:队列类可泛型化(Queue<T extends Request>),减少内部 / 外部请求的重复代码;控制类可通过依赖注入切换不同电梯配置(如载货电梯、住宅电梯的不同规则)。

六、结语:写代码如搭积木,职责清晰才能万丈高楼

三次电梯作业像一场 “代码重构马拉松”,从 “能跑就行” 到 “优雅可维护”,每一次迭代都是对面向对象设计的重新理解。印象最深的是第二次作业因职责混乱导致的大面积返工,让我深刻体会到 “前期设计偷懒,后期调试流泪” 的真理。现在看着第三次作业中各司其职的类结构(控制类调度、电梯类执行、队列类管理、乘客类封装请求),终于明白:好的代码不是一次写完的,而是像打磨玉器一样,在不断拆分、重构、测试中逐渐清晰。

 

博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3