题目集 1-3 单部电梯调度程序总结 Blog

一、前言
本阶段三次题目集围绕单部电梯调度程序展开迭代式开发,逐步深化对面向对象设计原则、队列管理、调度算法等核心知识点的应用。

  1. 知识点覆盖
    三次题目集核心知识点高度聚焦且层层递进:
    基础层面:Java 语法规范、输入输出处理、正则表达式匹配、异常请求过滤(无效楼层、重复请求);
    数据结构:队列(LinkedList)的应用,包括请求的入队、出队、去重等操作;
    面向对象:类的设计、封装、继承思想,尤其是单一职责原则(SRP)的逐步落地,从单类实现到多类分工协作;
    业务逻辑:电梯调度算法(同方向优先处理、顺路停靠)、状态与方向的动态决策。
  2. 题量与难度
    题量:三次题目集均围绕 “单部电梯调度” 单一核心题目展开,输入输出格式保持一致性,在类设计要求和业务规则上迭代变更,题量集中且针对性强;
    难度梯度:呈明显上升趋势。第一次题目仅要求实现基础调度逻辑,类设计无强制要求(单类即可完成);第二次题目新增单一职责原则约束,需拆分电梯类、请求类、队列类、控制类,同时增加重复请求去重处理;第三次题目取消请求类,新增乘客类,变更外部请求格式并要求将目的楼层自动入内部队列,业务逻辑关联性和类协作复杂度进一步提升。
    二、设计与分析
    三次题目集的核心演进逻辑是 “从单一职责混乱到职责清晰、从简单功能实现到复杂业务协作”,以下结合源码、SourceMonitor 报表及类设计进行详细分析。

(一)第一次题目:单类实现基础调度

  1. 类设计结构
    核心设计为Main类 +Elevator类,其中Elevator类承担了所有核心职责:
    存储电梯属性(最大 / 最小楼层、当前楼层、运行方向、状态);
    管理请求队列(内部 + 外部请求队列);
    实现请求解析(判断请求类型、提取楼层、方向);
    调度算法(方向决策、停靠判断);
    状态输出。
    类图如下:
    image

  2. 源码核心逻辑分析

// 核心调度逻辑在ans()方法中,顺序为:输出状态→检查停靠→决策方向→移动楼层
public static void ans(Elevator elevator) {
    String nextDir;
    while (!elevator.ISqueue_empty()) {
        // 输出当前状态
        System.out.println("Current Floor: " + elevator.GetCurrently_Floor() + " Direction: " + elevator.GetDirection_Operation());
        // 检查内部请求停靠
        if (elevator.Get_Number(elevator.GetInside_elevator()) == elevator.GetCurrently_Floor()) {
            System.out.println("Open Door # Floor " + elevator.GetCurrently_Floor());
            System.out.println("Close Door");
            elevator.removeInside_elevator();
        }
        // 检查外部请求停靠
        if (elevator.Get_Number(elevator.GetOutside_elevator()) == elevator.GetCurrently_Floor()
                && (elevator.GetInside_elevator() == null
                || elevator.GetDirection_Operation().equals(elevator.Get_Outside_Direction(elevator.GetOutside_elevator())))) {
            System.out.println("Open Door # Floor " + elevator.GetCurrently_Floor());
            System.out.println("Close Door");
            elevator.removeOutside_elevator();
        }
        // 决策下一个方向
        nextDir = elevator.getnextDirection();
        if (nextDir != null) {
            elevator.SetDirection_Operation(nextDir);
        }
        // 移动楼层
        if (elevator.GetDirection_Operation().equals("UP")) {
            elevator.SetCurrently_Floor(elevator.GetCurrently_Floor() + 1);
        } else if (elevator.GetDirection_Operation().equals("DOWN")) {
            elevator.SetCurrently_Floor(elevator.GetCurrently_Floor() - 1);
        }
    }
}
  1. SourceMonitor 报表分析

屏幕截图 2025-11-21 143541

关键指标 数值 分析
类与接口数 2 仅Main和Elevator两个类,设计极简
每个类的方法数 5.50 Elevator类承担所有职责,方法数较多(含 get/set、队列操作、调度逻辑),Main类仅 2 个方法
平均每个方法的语句数 10.91 核心方法ans()和getnextDirection()语句密集,逻辑耦合度高
分支语句占比 29.7% 调度算法中包含方向判断、停靠判断等多分支逻辑,分支占比较合理
注释行占比 17.7% 注释覆盖率中等,核心逻辑无详细说明,可读性一般
  1. 设计心得
    优点:实现简单,无需考虑类间协作,适合快速完成基础功能;
    缺点:Elevator类职责臃肿(属性存储、队列管理、调度算法、输入解析集于一身),后续维护和扩展难度极大,若新增功能(如多电梯协作)需大幅修改源码。

(二)第二次题目:多类拆分 + 去重处理

  1. 类设计结构(遵循单一职责原则)
    本次设计拆分出 4 个核心类,职责分工明确:
    Elevator类:仅管理电梯自身属性(当前楼层、方向、状态、楼层范围校验),不参与请求管理和调度;
    ExternalRequest类:封装外部请求(楼层 + 方向),统一请求数据结构;
    RequestQueue类:管理内部 / 外部请求队列,负责请求的入队(含去重逻辑)和存储;
    Controller类:核心调度类,负责方向决策、停靠判断、请求移除、状态输出;
    Main类:仅处理输入解析和对象初始化。
    类图如下:
    image

  2. 源码核心改进分析
    (1)请求去重逻辑(RequestQueue类)

// 内部请求去重:避免连续相同楼层请求
public void addInternalRequest(int floor) {
    if (internalRequests.isEmpty() || internalRequests.getLast() != floor) {
        internalRequests.add(floor);
    }
}
// 外部请求去重:避免连续相同楼层+相同方向的请求
public void addExternalRequest(int floor, Direction direction) {
    if (externalRequests.isEmpty() ||
            (externalRequests.getLast().getFloor() != floor || externalRequests.getLast().getDirection() != direction)) {
        externalRequests.add(new ExternalRequest(floor, direction));
    }
}

(2)调度逻辑解耦(Controller类)
将调度逻辑拆分到多个独立方法,可读性和维护性提升:

public void processRequests() {
    // 初始状态输出
    System.out.println("Current Floor: " + elevator.getCurrentFloor() + " Direction: UP");
    while (true) {
        if (shouldStop(elevator.getCurrentFloor())) { // 判断停靠
            openDoors(); // 开关门
            removeRequests(elevator.getCurrentFloor()); // 移除请求
        }
        determineDirection(); // 决策方向
        if (queue.getInternalRequests().isEmpty() && queue.getExternalRequests().isEmpty()) {
            break;
        }
        getNextFloor(); // 移动并输出
    }
}
  1. SourceMonitor 报表分析

屏幕截图 2025-11-21 143759

关键指标 数值 分析
类与接口数 1 实际源码包含 5 个类(含枚举),推测报表未统计枚举类
每个类的方法数 22.00 方法分布更均匀,Controller类承担核心调度方法,RequestQueue类专注队列操作,职责拆分后单个类方法数更合理
平均每个方法的语句数 5.45 方法粒度细化,单个方法逻辑更简洁,可读性提升
分支语句占比 17.2% 分支占比下降,因逻辑拆分后每个方法的分支判断更集中,减少了嵌套分支
注释行占比 9.1% 注释占比下降,需补充核心调度逻辑注释以提升可读性
  1. 设计心得
    单一职责原则的落地显著提升了代码的可维护性,新增功能(如去重)仅需修改RequestQueue类,无需影响电梯属性管理和调度逻辑;
    类间协作通过依赖注入实现(Controller依赖Elevator和RequestQueue),降低了耦合度;
    枚举类(Direction、State)的使用规范了方向和状态的取值,避免了字符串硬编码导致的错误。

(三)第三次题目:新增乘客类 + 业务逻辑扩展

  1. 类设计结构调整
    取消ExternalRequest类,新增Passenger类:封装乘客的源楼层和目的楼层,自动计算乘梯方向(getDirection()方法);
    RequestQueue类调整:外部请求队列改为存储Passenger对象,内部请求队列保持不变;
    Controller类增强:处理外部请求时,需将乘客目的楼层自动加入内部请求队列。
    类图如下:
    image

  2. 源码核心变更分析
    (1)乘客类设计(Passenger类)

class Passenger {
    private Integer sourceFloor = null;
    private Integer destinationFloor = null;
    // 自动根据源楼层和目的楼层计算方向
    public Direction getDirection() {
        if (sourceFloor == null || destinationFloor == null) {
            return Direction.IDLE;
        }
        return sourceFloor < destinationFloor ? Direction.UP : Direction.DOWN;
    }
    // get/set方法省略
}

(2)外部请求处理增强(Controller类)

private void removeRequests(int currentFloor) {
    // 处理外部请求:移除后将目的楼层加入内部队列
    if (!queue.getExternalRequests().isEmpty()) {
        Passenger req = queue.getExternalRequests().peek();
        if (req.getSourceFloor() == currentFloor && (elevator.getDirection() == req.getDirection() || queue.getInternalRequests().isEmpty())) {
            queue.getExternalRequests().remove();
            // 目的楼层入内部队列(自动去重)
            if (elevator.isValidFloor(req.getDestinationFloor())) {
                queue.addInternalRequest(req.getDestinationFloor());
            }
        }
    }
}
  1. SourceMonitor 报表分析

屏幕截图 2025-11-21 144121

关键指标 数值 分析
类与接口数 6 类数量进一步增加,但职责更单一
每个类的方法数 27.00 Passenger类仅含属性访问和方向计算方法,Controller类新增乘客目的楼层入队逻辑,方法粒度保持精细
平均每个方法的语句数 5.45 方法粒度细化,单个方法逻辑更简洁,可读性提升
分支语句占比 17.2% 分支占比下降,因逻辑拆分后每个方法的分支判断更集中,减少了嵌套分支
注释行占比 9.1% 注释占比下降,需补充核心调度逻辑注释以提升可读性
  1. 设计心得
    新增Passenger类后,请求数据结构更贴合业务本质,外部请求不再是孤立的 “楼层 + 方向”,而是完整的 “乘客出行需求”;
    目的楼层自动入队逻辑的实现,体现了业务逻辑的连贯性,减少了手动输入内部请求的冗余,同时通过RequestQueue的去重机制避免重复请求;
    类间协作更紧密但不耦合,Controller通过Passenger类获取源楼层、目的楼层和方向,无需直接操作原始请求字符串,降低了解析错误风险。

(四)三次设计演进对比

设计维度 第一次题目 第二次题目 第三次题目
类数量 2 个 5 个(含枚举) 6 个(含枚举)
核心职责分布 单类承担所有职责 多类分工(电梯、请求、队列、控制) 职责进一步细化(乘客类封装出行需求)
请求处理 无去重,直接入队 连续重复请求去重 去重 + 目的楼层自动入队
业务贴合度 低(仅实现基础调度) 中(符合请求处理规范) 高(模拟乘客完整出行流程)
可维护性 良好 优秀

三、采坑心得

(一)第一次题目:逻辑顺序错误导致输出异常

  1. 问题描述
    初始编写ans()方法时,将 “移动楼层” 放在 “输出状态” 之前,导致输出的楼层和方向与实际运行状态不一致。例如:电梯初始在 1 层,移动到 2 层后才输出 1 层的状态,与样例输出顺序不符。
  2. 数据与测试结果
    错误代码顺序:移动楼层→输出状态→检查停靠;
    测试输入:1 20 <7,UP> end;
    错误输出:先输出 “Current Floor: 2 Direction: UP”,再检查 1 层是否停靠,完全不符合预期;
    修正后顺序:输出状态→检查停靠→决策方向→移动楼层,与样例输出一致。
  3. 心得
    核心业务逻辑的执行顺序直接影响输出结果,需严格遵循 “先展示当前状态,再处理当前状态下的操作(停靠、开关门),最后进行下一步动作(移动)” 的逻辑,避免因顺序颠倒导致状态与行为不一致。

(二)第二次题目:请求去重逻辑考虑不全

  1. 问题描述
    初始实现addExternalRequest方法时,仅判断楼层是否相同,未考虑方向差异,导致<5,DOWN>和<5,UP>被误判为重复请求,过滤掉其中一个。
  2. 代码对比
    错误代码:
public void addExternalRequest(int floor, Direction direction) {
    if (externalRequests.isEmpty() || externalRequests.getLast().getFloor() != floor) {
        externalRequests.add(new ExternalRequest(floor, direction));
    }
}

修正代码:

public void addExternalRequest(int floor, Direction direction) {
    if (externalRequests.isEmpty() ||
            (externalRequests.getLast().getFloor() != floor || externalRequests.getLast().getDirection() != direction)) {
        externalRequests.add(new ExternalRequest(floor, direction));
    }
}
  1. 测试结果
    输入:1 20 <5,DOWN> <5,UP> end;
    错误输出:仅处理<5,DOWN>,<5,UP>被过滤;
    修正后输出:两个请求均被处理,符合 “不同方向的请求不属于重复请求” 的业务规则。
  2. 心得
    去重逻辑需基于 “请求的唯一标识” 设计,外部请求的唯一标识是 “楼层 + 方向”,内部请求的唯一标识是 “楼层”,需明确区分不同请求的去重维度,避免因考虑不全导致功能异常。

(三)第三次题目:外部请求方向判断错误

  1. 问题描述
    初始实现Passenger类的getDirection()方法时,误将 “源楼层> 目的楼层” 判断为UP,导致电梯方向决策错误,无法正确停靠外部请求楼层。
  2. 代码对比
    错误代码:
public Direction getDirection() {
    if (sourceFloor == null || destinationFloor == null) {
        return Direction.IDLE;
    }
    return sourceFloor < destinationFloor ? Direction.DOWN : Direction.UP;
}

修正代码:

public Direction getDirection() {
    if (sourceFloor == null || destinationFloor == null) {
        return Direction.IDLE;
    }
    return sourceFloor < destinationFloor ? Direction.UP : Direction.DOWN;
}
  1. 测试结果
    输入:1 20 <5,4> end(源楼层 5,目的楼层 4,方向应为 DOWN);
    错误输出:电梯方向决策为 UP,持续向上运行,无法停靠 5 层;
    修正后输出:电梯从 1 层 UP 运行到 5 层,停靠后方向转为 DOWN,运行到 4 层停靠,符合预期。
  2. 心得
    Passenger类的方向计算是外部请求调度的核心依据,方向判断错误会导致整个调度逻辑失效。开发过程中需结合实际场景验证核心方法的正确性,尤其是涉及方向、楼层等关键属性的计算逻辑。

(四)共性问题:无效楼层请求处理

  1. 问题描述
    三次题目均要求过滤超出最大 / 最小楼层的无效请求,初始实现时未在输入解析阶段进行校验,导致无效请求入队后影响调度逻辑。
  2. 解决方案
    在Main类的输入处理阶段,通过elevator.isValidFloor()方法校验楼层有效性,仅将有效请求加入队列:
// 内部请求校验
if (line.matches("^<\\d+>$")) {
    int floor = Integer.parseInt(line.replaceAll("<|>", ""));
    if (elevator.isValidFloor(floor)) {
        queue.addInternalRequest(floor);
    }
}
// 外部请求校验(第三次题目)
else if (line.matches("^<\\d+,\\d+>$")) {
    String[] parts = line.replaceAll("<|>", "").split(",");
    int source = Integer.parseInt(parts[0]);
    int dest = Integer.parseInt(parts[1]);
    if (elevator.isValidFloor(source) && elevator.isValidFloor(dest)) {
        Passenger passenger = new Passenger(source, dest);
        queue.addExternalRequest(passenger);
    }
}
  1. 心得
    无效输入的过滤应在 “请求入队前” 完成,避免无效数据进入队列后导致调度算法判断失误,同时减少队列操作的冗余开销。

四、改进建议

(一)代码层面改进

  1. 增加日志输出,便于调试
    当前代码仅输出电梯运行状态,无调试信息。可引入日志框架(如 SLF4J)或简单的日志输出语句,记录请求入队、请求移除、方向变更等关键操作,例如:
// RequestQueue类添加日志
public void addInternalRequest(int floor) {
    if (internalRequests.isEmpty() || internalRequests.getLast() != floor) {
        internalRequests.add(floor);
        System.out.println("[日志] 内部请求入队:" + floor);
    } else {
        System.out.println("[日志] 重复内部请求,已过滤:" + floor);
    }
}
  1. 优化方向决策算法,支持多请求优先级
    当前调度算法仅处理队列头部请求,未考虑同方向后续请求的顺路处理。可优化determineDirection()方法,遍历队列中所有同方向请求,优先处理顺路请求,减少电梯往返次数。例如:
// Controller类优化方向决策
private void determineDirection() {
    // 遍历内部请求,寻找同方向顺路请求
    for (int floor : queue.getInternalRequests()) {
        int diff = floor - elevator.getCurrentFloor();
        if ((diff > 0 && elevator.getDirection() == Direction.UP) || (diff < 0 && elevator.getDirection() == Direction.DOWN)) {
            return; // 保持当前方向,处理顺路请求
        }
    }
    // 遍历外部请求,寻找同方向顺路请求
    for (Passenger passenger : queue.getExternalRequests()) {
        int diff = passenger.getSourceFloor() - elevator.getCurrentFloor();
        if ((diff > 0 && elevator.getDirection() == Direction.UP) || (diff < 0 && elevator.getDirection() == Direction.DOWN)) {
            return; // 保持当前方向,处理顺路请求
        }
    }
    // 无顺路请求,再切换方向
    // 原有方向决策逻辑...
}
  1. 封装输入解析逻辑,减少 Main 类职责
    当前Main类承担输入解析和对象初始化职责,可新增InputParser类,专门处理输入字符串的解析、校验和请求转换,进一步遵循单一职责原则:
class InputParser {
    private Elevator elevator;
    public InputParser(Elevator elevator) {
        this.elevator = elevator;
    }
    // 解析一行输入,返回对应的请求类型(内部/外部/无效/end)
    public Request parse(String line) {
        // 解析逻辑实现...
    }
}

(二)功能扩展建议

  1. 支持并发请求处理
    当前题目要求串行处理请求,可扩展为支持并发请求(多个请求同时入队),通过线程安全的队列(如ConcurrentLinkedQueue)替代LinkedList,确保多线程环境下请求处理的安全性。
  2. 新增电梯负载限制
    实际电梯存在载客量限制,可新增maxPassengers属性和currentPassengers计数器,当电梯满载时,拒绝新的外部请求,直至有乘客下梯后释放负载。
  3. 可视化界面展示
    可结合 Java Swing 或 JavaFX 实现电梯运行可视化界面,直观展示电梯当前楼层、方向、状态及请求队列,提升程序的交互性和可读性。
    五、总结
    (一)学习收获
    面向对象设计能力显著提升:从最初的单类实现,到逐步拆分出电梯类、请求类、队列类、控制类、乘客类,深刻理解了单一职责原则的重要性,学会了通过类的分工协作降低耦合度;
    业务逻辑梳理能力增强:三次迭代中,逐步掌握了 “需求分析→类设计→逻辑实现→测试验证” 的完整开发流程,尤其是对电梯调度算法的核心(方向决策、停靠判断、请求优先级)有了清晰的认识;
    问题排查能力提升:通过解决输出顺序错误、去重逻辑漏洞、方向判断错误等问题,学会了结合测试用例和日志输出定位问题,培养了 “代码→测试→修正” 的闭环思维。
    (二)需进一步学习的方向
    数据结构与算法优化:当前调度算法较为基础,需学习更高效的电梯调度算法(如 SCAN 算法、LOOK 算法),提升电梯运行效率;
    设计模式应用:可尝试引入单例模式(确保电梯实例唯一)、策略模式(不同调度算法可切换)等设计模式,进一步优化类设计;
    多线程编程:当前程序为串行处理,需学习多线程编程,实现并发请求处理和电梯状态的实时更新。
    (三)改进建议
    作业设计:建议在题目中提供更详细的类图说明,尤其是第二次和第三次题目中类间的依赖关系,帮助更好地理解单一职责原则的落地方式;
    测试用例:可提供更多边界测试用例(如最小楼层请求、最大楼层请求、反向请求连续输入等),帮助全面验证代码的正确性;
    反馈机制:建议增加代码评审环节,通过同学间的代码互评,发现类设计和逻辑实现中的问题,相互学习提升;
    扩展练习:可在三次题目基础上,增加多电梯调度的扩展练习,进一步巩固面向对象设计和调度算法知识,提升复杂系统的设计能力。
posted @ 2025-11-21 15:27  yaya7  阅读(21)  评论(0)    收藏  举报