bj9

导航

面向对象程序设计——电梯调度总结

一、前言
本次电梯调度问题,通过迭代的方式,不断引导我们加深对类设计、对单一职责性等的理解
三次作业的简单总结:
1.第一次作业
知识点:使用类封装电梯的属性和行为,以及运用正则表达式对字符串解析和异常处理,过滤无效输入,得到有用信息
题量:属于单模块编程题,核心代码集中在电梯类和主类
难度:难度较难,需要理解清楚电梯的运行逻辑
2.第二次作业
知识点:新增单一职责原则、增加类间依赖关系,比如控制类依赖于电梯类和队列类,通过调用其公有方法实现调度。进一步完善无效请求处理,比如重复请求、格式错误请求,提升程序的容错性
题量:属于多模块协作编程题,需设计 4~5 个核心类,需要关注各个类之间的协调合作
难度:难度较第一次增加,如果第一次代码冗杂,为实现单一职责性需重新构思,难度较大
3.第三次作业
知识点:新增对各类型通过公有方法暴露必要接口,来隐藏内部实现;进一步细化职责拆分
题量:属于复杂多类协作编程题,核心类增加至 5~6 个
难度:较高难度,新增核心功能点,且需修改原有调度逻辑来适配新规则
二、设计与分析

1.第一次作业

  • 1.题目要求
    设计一个电梯类,具体包含电梯的最大楼层数、最小楼层数(默认为1层)当前楼层、运行方向、运行状态,以及电梯内部乘客的请求队列和电梯外部楼层乘客的请求队列,其中,电梯外部请求队列需要区分上行和下行。
    电梯运行规则如下:电梯默认停留在1层,状态为静止,当有乘客对电梯发起请求时(各楼层电梯外部乘客按下上行或者下行按钮或者电梯内部乘客按下想要到达的楼层数字按钮),电梯开始移动,当电梯向某个方向移动时,优先处理同方向的请求,当同方向的请求均被处理完毕然后再处理相反方向的请求。电梯运行过程中的状态包括停止、移动中、开门、关门等状态。当电梯停止时,如果有新的请求,就根据请求的方向或位置决定移动方向。电梯在运行到某一楼层时,检查当前是否有请求(访问电梯内请求队列和电梯外请求队列),然后据此决定移动方向。每次移动一个楼层,检查是否有需要停靠的请求,如果有,则开门,处理该楼层的请求,然后关门继续移动。
    使用键盘模拟输入乘客的请求,此时要注意处理无效请求情况,例如无效楼层请求,比如超过大楼的最高或最低楼层。还需要考虑电梯的空闲状态,当没有请求时,电梯停留在当前楼层。
    请编写一个Java程序,设计一个电梯类,包含状态管理、请求队列管理以及调度算法,并使用一些测试用例,模拟不同的请求顺序,观察电梯的行为是否符合预期,比如是否优先处理同方向的请求,是否在移动过程中处理顺路的请求等。为了降低编程难度,不考虑同时有多个乘客请求同时发生的情况,即采用串行处理乘客的请求方式(电梯只按照规则响应请求队列中当前的乘客请求,响应结束后再响应下一个请求),具体运行规则详见输入输出样例。
  • 2.类图设计
    image

请求类(核心内容):作为Dianti的内部类,封装了请求的目标楼层和方向,用于统一管理内部和外部请求的信息

点击查看代码
        private class ReInfo {
            int targetFloor;
            String direction;
            
            ReInfo(int targetFloor, String direction) {
                this.targetFloor = targetFloor;
                this.direction = direction;
            }
        }
电梯类:包含电梯的状态、方向、当前楼层等属性,以及run()、chooseRequest()、processRequest()等方法,负责电梯的运行逻辑、请求选择与处理,是业务逻辑的核心载体

Main类:包含外部请求队列(waiQ)、内部请求队列(neiQ)、楼层范围(minFloor、maxFloor)等属性,以及main()、start()、processInputLines()等方法,负责输入处理、请求添加和程序启动,起到流程控制的作用

  • 3.复杂度分析
    image
    每类方法数:4.67
    每方法平均语句数:10.64
    最大方法复杂度:18,出现在Dianti.chooseRequest()方法,该方法的逻辑分支,条件判断、循环嵌套较多,是代码复杂度的核心点
    平均复杂度:5.14
    最大块深度:6,因为存在较深的嵌套结构,多层循环或条件嵌套
  • 4.题目分析
    该电梯调度程序设计,核心工作是构建一个“电梯类”,将电梯的核心属性与行为整合其中。其中,属性需涵盖电梯的最大楼层、最小楼层、实时状态,像当前楼层、运行方向、工作状态;行为则包括开门、关门、请求处理等核心操作,实现电梯功能的完整抽象
    程序需支持两类乘客请求的处理:一是电梯内部乘客的请求,二是电梯外部乘客的请求。针对这些请求,需设计对应的存储结构进行管理,程序自动识别并按照规则运行
    电梯运行的规则:当电梯处于某一运行方向时,需优先完成同方向的队首请求,待同方向请求没有时,再切换方向重新判断。电梯以“逐楼层移动”为基础逻辑,每到达一个楼层,均需检查该楼层是否存在待处理请求,若存在则执行“开门”,随后继续移动。当所有请求均处理完毕后,电梯将停留在当前楼层

第二次作业

  • 1.题目要求
    电梯运行规则与前阶段单类设计相同,但是需拆分为多个类符合单一职责性原则,同时要处理如下情况:
    乘客请求楼层数有误,具体为高于最高楼层数或低于最低楼层数,处理方法:程序自动忽略此类输入,继续执行
    乘客请求不合理,具体为输入时出现连续的相同请求,例如<3><3><3>或者<5,DOWN><5,DOWN>,处理方法:程序自动忽略相同的多余输入,继续执行,例如<3><3><3>过滤为<3>
  • 2.类图设计
    image
    请求类体系:抽象类Request封装请求的核心属性,楼层,子类有内部请求和外部请求
    电梯类:仅负责自身状态,当前楼层、方向、状态、最大 / 最小楼层和基础行为,移动、开关门,职责单一
    请求队列类:管理内部和外部请求的存储、去重、查询,如获取首个请求、判断是否包含重复请求,专注于队列的 “数据管理” 职责
    控制类:是调度逻辑的核心,依赖Elevator和RequestQueue,实现请求的选择、方向的决策、逐楼层移动等复杂业务逻辑
    Main类:负责输入处理、程序启动,是程序的入口
    本次代码在上一次的基础上进行了重新设计
  • 3.复杂度分析
    image
    每类方法数:4.00,
    每方法平均语句数:6.40
    平均复杂度:3.48
    平均块深度:2.65
    最大方法复杂度:18,仍出现在Dianti.chooseRequest()方法,但整体平均复杂度(3.48) 显著降低,说明大部分方法的逻辑分支已得到优化
    最大块深度:6,虽仍存在嵌套,但平均块深度(2.65) 降低,代码的嵌套层级更合理,可读性提升
  • 4.题目分析
    第二次作业的运行逻辑等与第一次的相同,只是添加以一些异常处理,对重复的,超出边界的请求进行过滤
    设计方面对比第一次单类包揽的形式,此次将电梯的状态管理、请求存储、调度逻辑、输入输出,拆分为多个类,每个类仅聚焦一个核心职责,使代码可读性更高

第三次作业

  • 1.题目要求
    对之前电梯调度程序再次进行迭代性设计,加入乘客类(Passenger),取消乘客请求类,类设计要求遵循单一职责原则(SRP),要求必须包含但不限于设计电梯类、乘客类、队列类以及控制类
    电梯运行规则与前阶段相同,但有如下变动情况:
    乘客请求输入变动情况:外部请求由之前的<请求楼层数,请求方向>修改为<请求源楼层,请求目的楼层>
    对于外部请求,当电梯处理该请求之后(该请求出队),要将<请求源楼层,请求目的楼层>中的请求目的楼层加入到请求内部队列(加到队尾)

  • 2.类图设计
    image
    乘客类:封装乘客的初始楼层、目的楼层和方向,将外部请求从单纯的楼层 + 方向升级为乘客的起点——终点模型,使业务逻辑更具象化,同时支持外部请求处理后将目的楼层加入内部队列的新规则。
    电梯类:职责保持单一,仅管理自身状态和基础行为,移动、开关门,新增startFloor属性用于记录初始楼层或乘客与初始楼层关联
    请求队列类:存储结构调整为LinkedList,分别管理内部请求和外部请求,并新增 “按方向筛选请求”“获取同方向最远请求” 等方法,强化队列的请求管理能力。
    控制类:依赖Elevator和RequestQueue,核心调度逻辑需适配‘乘客源 - 目的’的新模型,例如处理外部请求时需将乘客的目的楼层加入内部队列,对规则的适配能力。
    Main类:仍负责输入解析和程序启动,输入格式需适配 “外部请求为源楼层 + 目的楼层” 的新规则

  • 3.复杂度分析
    image
    每类方法数:4.58
    每方法平均语句数:5.37
    平均复杂度:3.13,相比之前版本进一步降低,说明整体代码的逻辑分支更简单,可读性提升
    平均块深度:2.50,嵌套层级更合理,代码的理解成本降低。
    尽管Dianti.chooseRequest()方法的最大复杂度仍为 18,但整体复杂度的均衡化和降低,体现了类拆分对复杂度的稀释作用,也说明设计的迭代优化是有效的

  • 4.题目分析
    输入方式改变,从以前楼层+方向,改为,初始楼层+目标楼层,使解析输入更加容易
    新增Passenger类是乘客请求具象化,使 “源 - 目的” 的请求模型更贴合实际
    相较于上一次使类的拆分更加合理,容易,通过新增的乘客类进行连接各个类,以达到解决问题的目的

三、踩坑心得
三次作业中最常见的坑是,代码写了一半,发现对题目规则的理解有误,导致前功尽弃,正确的流程应是先将业务规则拆解为可执行的步骤,再按步骤实现代码

  • 1.对题目问题的理解,最开始没有仔细研究附带的pdf而是以正常的电梯运行模式来写,导致运行的方式完全不一样,后来仔细分析才发现逻辑是处理两请求队首的其中一个
    所以,写代码前应该认真了解了问题再动手去写,着急忙慌得写反而是浪费时间
  • 2.设计的总体思想,第一次只为了完成作业,所以很多方法都杂糅在一起,导致后面两次迭代要求拆分成多个类的时候无从下手,不得不重头写一遍,太浪费时间了
    经过反思以后写代码应该按照老师讲得那样,在写代码前,先想好要写什么,把类图设计出来,再根据类图去完善代码
  • 3.类间依赖的耦合过紧,第二次作业控制类直接操作电梯类和队列类的私有属性(,而非通过公有方法(elevator.moveTo(5));队列类持有控制类的引用并直接调用其方法,导致类间形成循环依赖,修改一个类的实现会牵连多个类,导致运行结果有问题时去修改太麻烦了,足见满足单一职责性的重要

image

  • 4.代码结构混乱,导致修改麻烦

第二次时的:

点击查看代码
private boolean isDirectionMatch(Request req, Direction elevatorDir) {
    if (req instanceof ExternalRequest) {
        return ((ExternalRequest)req).getDirection() == elevatorDir;
    } else {
        return (elevatorDir == Direction.UP && req.getFloor() >= elevator.getCurrentFloor()) ||
               (elevatorDir == Direction.DOWN && req.getFloor() <= elevator.getCurrentFloor());
    }
}

第三次时:

点击查看代码
private boolean isDirectionMatch(Passenger passenger, Direction elevatorDir) {
    return (elevatorDir == Direction.UP && passenger.getSourceFloor() >= elevator.getCurrentFloor()) ||
           (elevatorDir == Direction.DOWN && passenger.getSourceFloor() <= elevator.getCurrentFloor());
}
第三次作业时仍沿用第二次作业的方向匹配逻辑,未考虑外部请求 “源楼层——目的楼层” 的方向,如乘客在 5 楼请求到 3 楼,其乘梯方向为下行,导致电梯在处理外部请求时方向判断错误,这个问题导致运行测试用例的时候,总是与预期结果不符,所以写代码最好多写注释,这样后续修改迭代的时候不至于因为结果混乱,重新捋自己之前怎么想的,这会节约很多时间,所以还是勤写注释的好

四、改进建议

  • 1.注释缺失和命名不规范是三次作业中 “迭代时重新捋逻辑” 的主要原因
    改进:强制使用 Javadoc 注释,比如:
点击查看代码
/**
 * 匹配乘客乘梯方向与电梯运行方向
 * @param passenger 乘客对象,第三次作业需要
 * @param elevatorDir 电梯当前运行方向
 * @return 方向匹配返回true,否则false
 * @note 该方法基于“目的楼层-源楼层”计算乘客方向,替代第二次的预设方向逻辑
 */
private boolean isDirectionMatch(Passenger passenger, Direction elevatorDir) {}
  • 2.三次作业的需求迭代(从单类设计→多类拆分→乘客源 - 目的楼层模型)证明,电梯调度程序的需求会持续细化。需在代码中预留扩展接口,避免后续需求变更时 “推倒重写”,优化策略如下:
    针对核心规则的接口化设计
    将易变的规则(如方向匹配、请求调度策略)抽象为接口,通过不同实现类适配不同规则,例如:
点击查看代码
public interface SchedulingStrategy {
    void processRequest(Elevator elevator, RequestQueue queue, Passenger passenger);
    boolean isDirectionMatch(Elevator elevator, Passenger passenger);
}
public class PresetDirectionStrategy implements SchedulingStrategy {}
public class SourceDestFloorStrategy implements SchedulingStrategy {}

五、总结

  • 1.学习收获
    编程能力提升,会按职责拆分类、掌握规则拆解、养成代码规范
  • 2.知识点应用
    Java基础,用枚举定义方向/状态,LinkedList实现队列,HashSet去重
    面向对象,用抽象类+继承封装共性,按单一职责拆分模块
  • 3.需进一步学习的方向
    练用接口+实现类切换电梯调度规则;代码重构,掌握拆分长方法、优化类间依赖的具体技巧;补基础,解决并发请求下的队列线程安全问题。
  • 4.具体改进建议
    作业与课程
    a.提供简化类图:仅展示类名和关联关系
    b.分享常见错误:贴典型错误代码(如方向判断错)并分析原因。
    课上与课下
    a.课上结合作业讲知识点;作业中期集中答疑;
    b.课下整理作业FAQ,汇总常见问题及解决方案

最后:经过这三次电梯调度的迭代开发,与其说是完成作业,不如说是一场从写能跑的代码到写好维护的代码的思维蜕变。最初写单类电梯时,只想着把功能堆出来,却在第二次作业的类拆分中陷入无处下手的困境,每一个坑踩过的都在印证:好代码的核心,是让自己和别人都能看懂、能改。
现在回头看,那些曾让我熬夜返工的问题,本质上都是对 “设计” 和 “规范” 的忽视。这场迭代更让我懂得,软件开发从来不是一劳永逸的工作。

posted on 2025-11-21 23:57  qz9  阅读(7)  评论(0)    收藏  举报