NCHU_单部电梯调度程序大作业

一、前言
本次大作业是围绕着单部电梯调度问题展开迭代编程,我认为这三次题目集主要考察我们对类之间关系的理解,以及设计使用,还有对look算法的应用。三次题目集的核心知识点聚焦于面向对象编程,也就是让我们从C语言的面向过程的思维转换到面向对象,这一点非常重要,同时三次大作业中也涉及到了队列数据结构应用、电梯调度算法设计、请求处理逻辑优化等重要设计模块,同时涉及输入校验、重复请求过滤、状态机管理等辅助技能,三次大作业以单部电梯调度的迭代展开编程,每次大作业都会新增类与方法服务于功能需求,难度确实是逐步上升
第一次大作业 7-5 NCHU_单部电梯调度程序
知识点:第一次题目集需要实现基础的内外请求响应与简单方向控制,核心规则是队列的入队出队操作,还有同方向优先的规则;
难度:对我来说现在还带着点面向过程的写法去完成它,对于思维上的转换还是有点困难
题量:一般(万事开头难)
第二次大作业7-3 NCHU_单部电梯调度程序(类设计)
知识点:第二次题目集在遵守队首处理(先进先出),同方向近距离优先的电梯运行规则下新增重复请求过滤、控制器与请求队列的类设计,要求决方向匹配与优先级排序问题;
难度:在多个类的设计下,我需要考虑他们之间怎么样能做到职责单一化,尽量不让他们像许多乱七八糟的线缠绕在一起(这对程序来说很容易出问题)。
题量:比上一次大作业多加了许多类,所以题量有点大
第三次大作业NCHU_单部电梯调度程序(类设计-迭代)
知识点:第三次题目集需要我们引入乘客对象类,将外部请求与目标楼层绑定,需处理“乘客上车后生成内部请求”的联动逻辑,调度算法的完整性要求和难度显著提高。
难度:个人认为比前几次作业要难,尽管是在前两次的作业基础上去完成,但是它加上了对目标楼层和源楼层的考虑,要比前两次作业考虑的更多
题量:一般
三次题目集不断迭代完善,从最初的单一类中,将所有功能逻辑堆砌在一起,就像把所有零件杂乱地塞进一个盒子,再到新增控制器、请求队列、电梯实体等类,最后到引入乘客类关联请求上下文,要求类执行“单一功能职责”原则,呈现面向对象设计中“高内聚、低耦合”的核心思想。
二、设计与分析
(一)第一次题目集:

  1. 类设计结构
    第一次题目集采用“主类+电梯类”的简单结构:
    Main类:负责输入读取、请求解析与对电梯对象进行初始化;
    Elevator类:封装电梯核心属性(当前楼层、运行方向、最小/最大楼层)与请求队列(内部请求队列(inQueue)、外部请求队列(outsideQueue) ),同时还有请求添加、停靠判断、移动逻辑等方法;
    image
    内部类 OutsideRequest :外部请求的楼层与方向信息。
    image
    第一次大作业类图:
    image

  2. 调度算法核心逻辑
    本次调度算法的核心是 findNextDis() 方法,我采用“队头优先+距离最近”策略:
      空闲状态(IDLE)时,比较内部队列与外部队列的队头请求,选择距离当前楼层更近的作为目标;
      运行状态(UP/DOWN)时,优先筛选同方向的队头请求(内部或外部),选择距离最近的作为下一个目标;
    停靠判断仅检查队头请求是否与当前楼层一致且方向匹配。

  3. 源码分析与问题
    image
    从SourceMonitor报表来看,本次代码存在明显短板:
    workElevator()方法圈复杂度达7,包含多重条件判断与循环嵌套,逻辑冗余;而业务逻辑过度集中在Elevator类,最好进行多类拆分,降低Elevator类的职责。
    (二)第二次题目集:
    在第一次大作业基础上,新增常量类(Direct)、电梯外部类(OutsideNeed),请求队列类(RequestQueue)、控制类(Controller)

  4. 类设计结构
    常量类 Direct :封装运行方向常量(UP/DOWN/IDLE);
    image
    外部需求类 OutsideNeed :不同于第一次大作业的内部类,我这次将外部请求信息独立封装;
    image
    请求队列类 RequestQueue :独立管理内部请求队列与外部请求队列,新增重复请求过滤逻辑;
    image
    Elevator类:仅封装电梯的状态属性(当前楼层、方向、楼层范围)与移动方法(moveUp()/moveDown()),不再直接管理请求队列,让类变得职责单一化,不会那么复杂交错;
    image
    Controller类: 关联Elevator与RequestQueue,就像一个大的容器盒子,通过对类的调度将主要功能放在一起,实现请求添加、停靠判断、目标查找、运行控制等逻辑,让程序更加清晰化。
    image
    第二次大作业类图:
    image

  5. 核心优化点与算法改进
    重复请求过滤:RequestQueue类通过记录上一个请求(lastInternalFloor/lastExternalRequest),过滤连续重复的无效请求,避免队列冗余,这样就不会造成最开始时我对整个队列进行遍历,把所有相同楼层都给过滤了;
    调度算法优化:空闲状态时,不再单纯比较距离,而是优先选择与当前楼层同向的队头请求,严格执行电梯队首处理,同方向优先的运行规则。
    类职责分离:电梯只负责状态管理与移动,控制器负责调度逻辑,请求队列负责请求存储与过滤,初步实现“单一职责原则”。

  6. 源码分析与进步
    image
    SourceMonitor报表显示,本次代码质量显著提升:
    类职责拆分策略初见成效:Elevator类仅负责电梯状态与移动,方法复杂度普遍≤2,实现了“单一职责”;Controller负责调度、RequestQueue负责请求管理,核心逻辑分散到多个类,降低了集中风险。但仍存在局部复杂点,RequestQueue.addInNeed(),它的复杂度32,因为我的代码里面if-else较多这是一个不大好的点;addInNeed().findNextflag()它的复杂度为12也是因为if-else的分支太多,调度目标查找存在嵌套冗余。虽然代码块深度与整体复杂度较第一次有所优化,但还需要对复杂度高的部分再进行进一步的优化,以满足面向对象中“低耦合、高内聚”的可维护设计需求。
    (三)第三次题目集:乘客模型与联动逻辑

  7. 类设计结构
    本次题目集引入乘客对象,完善请求上下文关联:
    乘客类Passenger:封装外部请求的出发楼层、目标楼层,提供 getDirection() 方法获取乘客需求方向;
    image
    电梯实体类ElevatorCar:再次分离Elevator类的职责,做到类职责单一化,仅保留核心状态与移动方法;
    image
    请求管理类 RequestManager:替代RequestQueue,管理内部请求队列与外部乘客队列;
    image
    控制系统类ElevatorControlSyste:关联 ElevatorCar与RequestManager,实现完整的调度逻辑,新增“外部请求转内部请求”联动。
    image
    第三次大作业类图:
    image

  8. 调度算法核心逻辑升级
    本次大作业的核心改进是“请求联动”与“同方向扫描”,这些在我上面画的类图可以很直观感受到类职责单一化的重要性:

  • 外部请求处理:外部请求(乘客)被电梯接收后,会将其目标楼层自动加入内部请求队列,实现“上车→目的地”的联动;
  • 目标查找优化: findNextTarget() 方法在运行状态时,会检查队头请求是否同方向,若队头不同向,仍会扫描后续请求(虽未完全实现,但已具备雏形);
  • 停靠处理: handleStop() 方法在处理外部乘客时,会移除外部队列中的该乘客,并添加其目标楼层到内部队列,确保乘客能到达目的地。
  1. 源码分析与亮点
    image
    SourceMonitor报表显示,本次代码比较上一次来说类的复杂度已经降低了许多,这些类基本也实现了职责单一的类需求,也更满足面向对象程序要求的“低耦合、高内聚”设计需求;
    三、采坑心得
    (一)第一次题目集:基础逻辑与耦合陷阱
  2. 核心问题与数据支撑
  • 问题1:
    image
    image
    刚开始的时候,我测试这个样例跟答案输出一样我以为是正确的,但是当我交上去的时候发现还是错的
    image
    后来找了朋友要了一个特殊的样例来测试
    image
    错误结果我就不放了,对比了正确的结果有不同的点在于我的getNextTarget()方法中的空闲状态优先级固定为 “外部向上请求→外部向下请求→内部请求”,且不考虑距离,运行状态时,优先选择同方向外部队列的队头,再选择同方向内部队列的队头,在不复杂的楼层需求,其实我的是没有问题的,但是过于复杂的请求就会导致了其实他不是按照两个队列队首进行比较,再按照同方向近距离优先的电梯运行规则去运行电梯。
  • 问题2:非常容易就会出现运行超时,有时候经常会陷入某一个楼层的死循环
  1. 问题根源分析:
    对程序需求理解不透彻,情况考虑得不够全面,没有严格遵守“外部请求需方向匹配才能停靠”的核心规则,只考虑楼层了一致;
    这就是设计思维缺失,不会对类进行模块拆分,老是用着面向过程的思维直接采用“堆砌式”编码,导致耦合度太强,非常容易出现死循环。
    (二)第二次题目集:重复过滤与调度逻辑陷阱
  2. 核心问题与数据支撑
    问题1:重复请求过滤逻辑漏洞。 RequestQueue 类仅过滤“连续重复”请求,非连续重复请求会被移除队列。例如,输入1 10 <3><5><3> end,第三个请求会被过滤,导致电梯不会前往3层,如图
    image
    这是因为我的addoutsideNeed()的方法遍历整个外部队列检查重复,他会把所有楼层一致的请求 ,全部给过滤了
    错误代码:
点击查看代码
 public void addoutsideNeed(int floor, String direction) {
        // 过滤无效楼层和无效方向
        if (floor < min || floor > max || 
            (!Direct.up.equals(direction) && !Direct.down.equals(direction))) {
            return;
        }
        // 过滤重复请求
        OutsideNeed newNeed = new OutsideNeed(floor, direction);
        if (outsideQueue.isEmpty() || isLstatOutsideQueueNeed(newNeed)==0) {
            outsideQueue.offer(newNeed);
        }
}
正确代码:
点击查看代码
public void addoutsideNeed(int floor, String direction) {
        // 过滤无效楼层和无效方向
        if (floor < min || floor > max || 
            (!Direct.up.equals(direction) && !Direct.down.equals(direction))) {
            lastExternalRequest = null; // 无效请求不参与重复检查
            return;
        }        
        // 检查是否连续重复
        if (lastExternalRequest != null && 
            lastExternalRequest.floor == floor && 
            lastExternalRequest.direction.equals(direction)) {
            return; // 连续重复,忽略
        }
        OutsideNeed newNeed = new OutsideNeed(floor, direction);
        outsideQueue.offer(newNeed);
        lastExternalRequest = newNeed; // 更新上一个请求
    }

- 问题2:当其中一个队列的请求处理完以后,另一个队列也会结束,不会再继续按照先进先出(也就是队首处理去处理该队列中剩余的请求)
点击查看代码
if (flagIn == null && flagoutside == null) return null;
     
        if ("IDLE".equals(direction)) {
            // 比较两个队首,取距离最近的
            if (flagIn == null) return flagoutside;
            if (flagoutside == null) return flagIn;
            
            int inDis = Math.abs(flagIn - nowFloor);
            int ouDis = Math.abs(flagoutside - nowFloor);
            if(inDis<=ouDis) {
            return flagIn;
        }
            else {
                return flagoutside;
            }
2. 问题根源分析 * 在逻辑设计方面我考虑得不是很好,重复请求过滤考虑连续场景,采用了遍历整个队列用于过滤连续的场景,忽略了电梯要串行处理的要求; * 对于电梯运行情况,我对于两个队列的设计运行情况考虑得不是很全面,忘了处理当其中一个队列为空时另一个队列要继续按照队首处理的原则去运行电梯

(三)第三次题目集:联动逻辑与状态管理陷阱

第三次大作业与前两次大作业稍有不同,因为这次实验的外部请求将楼层方向改成了目标楼层,这也为本次大作业增添了难度,在开始的时候我分析样例认为外部请求队列的队首必须要把目标楼层的请求执行完,才能移除队首,执行下一队首的目标楼层,后来经过跟已经通过这道题的同学沟通,他说,这道题的不考虑目标楼层是否到达,只在源楼层到了以后,就会去进行新一轮的队首比较,所以本题也不能非常依赖于测试用例,这道题出现的情况十分复杂,有的同学通过这道题的测试点是两个测试用例都跟给的测试结果不一样,有的是对了一道,还有的同学的测试用例结果都对上了,但是测试点就是全错(没错就是最开始的我),所以测试用例很重要,但也没有那么重要,他只能作为一个参考,进一步帮助我们理解编程思路,接下来我会放出我的测试结果跟测试输出的结果作为对照:
测试用例 :

点击查看代码
1
20
<5,9>
<8>
<9,3>
<4>
<2>> 
End
测试样例输出:
点击查看代码
Current Floor: 1 Direction: UP
Current Floor: 2 Direction: UP
Current Floor: 3 Direction: UP
Current Floor: 4 Direction: UP
Current Floor: 5 Direction: UP
Open Door # Floor 5
Close Door
Current Floor: 6 Direction: UP
Current Floor: 7 Direction: UP
Current Floor: 8 Direction: UP
Open Door # Floor 8
Close Door
Current Floor: 9 Direction: UP
Open Door # Floor 9
Close Door
Current Floor: 8 Direction: DOWN
Current Floor: 7 Direction: DOWN
Current Floor: 6 Direction: DOWN
Current Floor: 5 Direction: DOWN
Current Floor: 4 Direction: DOWN
Open Door # Floor 4
Close Door
Current Floor: 3 Direction: DOWN
Current Floor: 2 Direction: DOWN
Open Door # Floor 2
Close Door
Current Floor: 3 Direction: UP
Current Floor: 4 Direction: UP
Current Floor: 5 Direction: UP
Current Floor: 6 Direction: UP
Current Floor: 7 Direction: UP
Current Floor: 8 Direction: UP
Current Floor: 9 Direction: UP
Open Door # Floor 9
Close Door
Current Floor: 8 Direction: DOWN
Current Floor: 7 Direction: DOWN
Current Floor: 6 Direction: DOWN
Current Floor: 5 Direction: DOWN
Current Floor: 4 Direction: DOWN
Current Floor: 3 Direction: DOWN
Open Door # Floor 3
Close Door
自己代码的测试结果:
点击查看代码
Current Floor: 1 Direction: UP
Current Floor: 2 Direction: UP
Current Floor: 3 Direction: UP
Current Floor: 4 Direction: UP
Current Floor: 5 Direction: UP
Open Door # Floor 5
Close Door
Current Floor: 6 Direction: UP
Current Floor: 7 Direction: UP
Current Floor: 8 Direction: UP
Open Door # Floor 8
Close Door
Current Floor: 7 Direction: DOWN
Current Floor: 6 Direction: DOWN
Current Floor: 5 Direction: DOWN
Current Floor: 4 Direction: DOWN
Open Door # Floor 4
Close Door
Current Floor: 3 Direction: DOWN
Current Floor: 2 Direction: DOWN
Open Door # Floor 2
Close Door
Current Floor: 3 Direction: UP
Current Floor: 4 Direction: UP
Current Floor: 5 Direction: UP
Current Floor: 6 Direction: UP
Current Floor: 7 Direction: UP
Current Floor: 8 Direction: UP
Current Floor: 9 Direction: UP
Open Door # Floor 9
Close Door
Current Floor: 8 Direction: DOWN
Current Floor: 7 Direction: DOWN
Current Floor: 6 Direction: DOWN
Current Floor: 5 Direction: DOWN
Current Floor: 4 Direction: DOWN
Current Floor: 3 Direction: DOWN
Open Door # Floor 3
Close Door

四、改进建议

(一)优化类结构设计,降低耦合度
前三次大作业的我对于类设计缺乏“高内聚、低耦合”的意识——在第一次大作业的过程中,我就将电梯调度、请求管理、物理移动等功能全部堆砌在Elevator类中,类间关联性极强,导致后续在进行第二次大作业时开始大改。第二次作业虽尝试拆分Controller和RequestQueue,但因为第一次大作业的设计缺陷,我开始花费很多时间大改(跟重写也没有什么区别了)。
所以后续代码中,我觉得必须要坚持“单一职责原则”,在写代码前需要先明确每个类的的作用,通过画类图划分职责并且将他们的关系,这对于我们的思路也非常重要方便后续的代码的编写,接下来我拿第二次大作业作为例子,认识一下画类图的重要性:

  • ElevatorCar:只封装属性(当前楼层、方向)和基础操作(移动、状态获取),不参与任何调度决策;
  • RequestManager:只负责电梯请求的存储、校验、去重,提供标准化接口(比如addRequest()),隐藏内部实现细节;
  • ElevatorControlSystem:通过调用其他类的接口实现调度逻辑,不直接操作其他类的私有属性。
    通过这种拆分,修改某一模块(比如请求去重规则)时,只需要调整RequestManager,就不需要改动ElevatorCar类或ControlSystem类,大幅提升代码可维护性,为后续复杂功能的迭代铺路。
    (二)精简冗余代码,提升可读性
    第二次作业中存在明显的代码冗余问题,例如判断请求方向时使用了许多的‘if-else’(所以的它的复杂的也很高)分支语句来判断下一个方向,代码不够简洁。
    后续优化可从两方面入手,:
  • 在写代码前的工作:不要忙着先写代码,可以先画类图,总结出类之间的关系和类的作用,整理自己的算法思路,要不会想着什么写什么,会导致代码非常复杂也非常多和杂乱无章。
  • 减少if-else语句的使用:将功能一致的条件判断、数据校验逻辑提取为私有辅助方法(比如isValidRequest()统一校验楼层范围),或者多使用switch语句来避免多段重复情况;
    (三)统一代码风格,规范注释格式
    三次作业中注释格式混乱问题突出:部分注释为中文、部分为英文,还带有的关键部分自己偷懒不想写注释,导致后续调试时难以快速理解逻辑。
    后续需制定并遵循统一的代码风格规范:
  • 注释规范:类头部添加中文注释,说明类的核心职责;方法头部注明主要功能;关键逻辑(比如第二次大作业中调度优先级、去重规则)旁添加单行注释,来解释设计思路;
  • 格式统一:变量命名尽量采用能看懂的英文、循环语句的大括号单独换行,代码要行行分明,整整齐齐提升代码整洁度,方便调试的时候更见清晰明了。

五、总结
通过三次电梯调度大作业,我在编程基础技能与工程化思维上都获得了一些成长:不仅认识到了面向对象于面向过程的区别,也有在写代码时思维的转换,还学会了通过测试样例排查问题、跟已经通过测试点的同学交流解题思路等高效学习方法。例如,在第一次大作业的时候我的测试样例通过了但是却依然测试错误,跟同学交流了,用了他给的测试用例最终知道代码出现什么问题了,还有第二次大作业时我发现他会过滤掉一切相同的楼层,不管请求的先后顺序。
但同时,我也清晰认识到自身的不足:

  • 设计思维薄弱:仍习惯“想到什么写什么”的面向过程的编程,缺乏前期设计类图的意识,前两次作业均都没有画类图和顺序图就直接开始写代码,导致类结构混乱、后续迭代困难;
  • 代码质量待提升:存在冗余逻辑、注释不规范等问题,还有自己的一些小懒惰,懒得写注释,且对面向对象的核心思想(封装、继承、多态)的应用只停留在表层,尚未形成系统的设计思路;
  • 问题预判能力不足:对边缘场景(如请求冲突、方向死锁)的考虑不全面,往往需要通过测试样例反向修正逻辑。
    值得庆幸的是,我积累了诸多实用的学习方法:通过交流来获特殊且有效的测试样例,向同学请教调度逻辑的设计思路,积累这些方法将成为我后续学习的重要助力。
    随着大作业难度提升(即将涉及继承、多态等知识点),我将重点改进学习方法:
  • 强化前期设计:每次编码前,先用工具绘制类图和顺序图,明确类的职责、属性、方法及模块间的交互关系;
  • 深化知识应用:主动学习设计模式(如单例模式、策略模式),将其融入电梯调度等场景,提升代码的灵活性与扩展性;
  • 加强自主练习:通过刷题、复现经典项目等方式,巩固面向对象编程思想,提升代码熟练度与问题预判能力。
    最后,感谢老师在课上强调“先设计后编码”的重要性,这一建议为我指明了改进方向。未来我将持续积累、查漏补缺,通过多看书、多练代码、多交流,夯实编程基础,为后续更复杂的项目迭代打下坚实基础。
posted @ 2025-11-22 14:36  niosal  阅读(24)  评论(1)    收藏  举报