一、前言

这三次电梯调度作业算是一场循序渐进的“编程修炼”——从最初的“能跑就行”到后来的“规范设计”,每一次迭代都带着明确的目标和新的挑战。

从知识点覆盖来看,第一次作业聚焦基础功能落地,核心是Java集合(LinkedList)的使用、输入解析与简单调度逻辑;第二次作业强调职责拆分与异常处理,引入单一职责原则(SRP)的初步实践,还要处理无效请求(如超限楼层、重复请求);第三次作业则升级到数据模型优化与全链路解耦,用Passenger类替代传统请求类,加入泛型队列和控制层,彻底拆分复杂职责。

题量上虽都是单题,但代码量从200行左右增至400行,类数量从3个扩展到5个;难度上,第一次是“实现逻辑”,第二次是“优化细节”,第三次是“设计架构”,一步一个台阶,没有突然的断层,但每次都要跳出之前的舒适区。

二、设计与分析

(一)题目集1:基础功能实现(first.java)——“能用,但不优雅”

1. 类设计与职责

核心类只有三个:ExternRequest(封装外部请求的楼层和方向)、Lift(电梯核心类)、Main(输入处理)。

这里最大的问题是Lift类职责过载——它既要管理内部/外部请求队列(addInnerReq、addOuterReq),又要处理方向决策(decideDir)、逐层移动(stepMove)、停站判断(needStop),还要执行开关门逻辑。相当于让一个“司机”同时兼管“乘客登记”“路线规划”和“车辆维护”,代码耦合度很高。

比如checkOuterStop()方法,既要判断当前楼层是否匹配外部请求,又要关联方向决策(hasSameDirReqs),嵌套了3层if-else,后续想改“停站规则”,很容易牵一发而动全身。

2. 调度逻辑与局限

调度上实现了“同方向优先”的基础规则:电梯先处理当前方向上的所有请求,无同方向请求时再切换方向。但细节上有缺陷——比如外部请求的方向判断仅看“首元素”,如果队列中后面有同方向请求,一旦首元素是反方向,就会提前切换方向,导致同方向请求被延后。

另外,输入处理比较粗糙:无效格式(如<3,UP,DOWN>)直接静默跳过,用户无法知晓输入是否生效;未过滤重复请求,连续输入<3>会导致电梯在3楼停多次,不符合实际场景。

屏幕截图 2025-11-21 113535

屏幕截图 2025-11-22 103840

(二)题目集2:职责拆分与异常处理(second.java)——“开始规范,但仍有漏洞”

1. 类设计改进

这次迭代有明显进步:一是在Elevator类中拆分出独立的请求过滤逻辑(addInternalRequest、addExternalRequest),专门处理“连续重复请求”和“超限楼层”,比如内部请求会判断“最后一个元素是否与当前请求相同”,外部请求会校验“楼层+方向”的连续重复;二是Main类增加了无效格式提示(输出“Wrong Format”),用户体验更友好。

但职责拆分仍不彻底——Elevator类还是要同时管“请求队列存储”和“电梯运行”,比如processRequests()既要遍历队列,又要调用move()方法,两者的依赖关系没有完全解耦。

2. 数据结构与逻辑问题

这次用了ArrayList存储请求队列,但ArrayList的remove(0)操作时间复杂度是O(n)(底层需要移动数组元素),如果请求队列较长(比如100个请求),会明显影响效率。后来在第三次作业中换成LinkedList,才解决了这个问题。

另外,方向决策逻辑有漏洞:当内部请求和外部请求方向冲突时(比如电梯UP方向,内部请求在当前楼层下方,外部请求在上方),代码会优先处理内部请求,导致“同方向优先”规则失效。比如电梯在5楼(方向UP),内部队列有[3],外部队列有[7,UP],实际会先DOWN到3楼,再UP到7楼,而按规则应该先处理同方向的7楼请求。

屏幕截图 2025-11-21 113546

屏幕截图 2025-11-22 103909

(三)题目集3:数据模型升级与全解耦(third.java)——“符合设计原则,贴近实际”

1. 类设计突破

这次彻底贯彻了单一职责原则,拆分为5个核心类,各司其职:

  • Passenger类:用“源楼层+目的楼层”替代传统的“楼层+方向”,更贴近实际乘客需求(乘客不会只说“要上行”,而是“从5楼到9楼”),还重写了equals方法,支持请求去重;

  • RequestQueue泛型队列:专门负责队列管理,实现“无连续重复元素”的逻辑,可复用至内部/外部请求,不用重复写过滤代码;

  • Elevator类:只管“运行”——移动、停站判断、方向决策,不碰请求存储,通过依赖RequestQueue获取请求;

  • ElevatorController类:控制层,负责协调输入、请求队列和电梯,接收请求后校验合法性,再转发给对应队列,解耦了“输入处理”和“电梯运行”;

  • Main类:仅处理输入解析,不参与业务逻辑。

这种设计的好处是“改一处不影响其他”——想改队列去重规则,只动RequestQueue;想调调度逻辑,只动Elevator;想优化输入校验,只动Main或Controller,维护成本大幅降低。

2. 调度与请求流转优化

请求流转更合理:外部请求(Passenger)被处理时(电梯到源楼层),会自动将“目的楼层”加入内部队列,比如乘客从5楼到9楼,电梯到5楼开门后,9楼会自动进入内部队列,不用手动添加。

方向决策逻辑也更完善:结合乘客的“隐含方向”(目的楼层>源楼层即UP,反之DOWN)和电梯当前方向,确保“同方向优先”不失效。比如乘客从5楼到3楼(隐含DOWN),电梯在5楼(方向UP),会先处理完UP方向的所有请求,再DOWN到3楼。

屏幕截图 2025-11-21 113556

屏幕截图 2025-11-22 105328

三、采坑心得:那些卡壳的瞬间与解决方法

(一)题目集1:方向判断逻辑“绕晕”

坑点:电梯在1楼(方向UP),输入<3,DOWN>,预期会先UP到3楼处理请求,实际直接切换到DOWN方向,没处理3楼请求。

原因:Lift类中checkOuterStop()hasSameDirReqs()的逻辑依赖没理清,误以为“外部请求方向与电梯方向不同时,直接切换方向”,忽略了“先处理同方向请求”的规则。

解决:画了一张逻辑流程图,明确“只有无同方向请求时,才切换方向”,再重构decideDir()方法,先调用hasSameDirReqs()判断,再决定是否切换方向,测试了5组不同场景,才彻底解决。

(二)题目集2:重复请求过滤“不彻底”

坑点:输入<3><5><3>,预期内部队列是[3,5],实际是[3,5,3],电梯在3楼停两次。

原因:当时的过滤逻辑只判断“最后一个元素是否相同”,没考虑“非连续重复”。但后来仔细看题目要求,发现只需要过滤“连续相同请求”,非连续重复是允许的——这才明白是自己误解了需求。

心得:写代码前一定要把需求边界划清,不然花时间做了“多余的优化”,反而不符合要求。

(三)题目集3:Passenger类equals“忘重写”

坑点:连续输入<5,4><5,4>,RequestQueue没过滤掉重复请求,电梯在5楼停两次。

原因:默认的equals方法比较的是对象地址,而不是“源楼层+目的楼层”的内容,两个内容相同的Passenger对象,会被判定为不同。

解决:重写equals方法,判断source == passenger.source && destination == passenger.destination,同时重写hashCode(虽然这次作业没用到,但规范上要配套),之后重复请求就能被正确过滤。

(四)输入读取“格式适配”问题

坑点:第三次作业中,用input.nextLine()读取最小/最大楼层,用户如果输入“1 20”(空格分隔),会抛出NumberFormatException——因为nextLine()会把“1 20”当成一个字符串,无法转成整数。

解决:要么在代码里处理“空格分隔”的情况(用split(" ")拆分),要么在输入提示里明确“每行输入一个楼层”。最后选择了后者,同时在代码里加了try-catch,捕获转换异常时输出“Wrong Format”,避免程序崩溃。

四、改进建议:从“能用”到“好用”的优化方向

(一)代码层面

  1. 题目集1:拆分Lift类,新增RequestManager类,专门管理内部/外部请求队列,Lift类只负责运行逻辑,降低耦合;增加无效请求提示,比如楼层超限时输出“Floor out of range”,格式错误时输出“Wrong Format”。

  2. 题目集2:将ArrayList换成LinkedList,优化队列删除首元素的效率;重构方向决策逻辑,分“仅内部请求”“仅外部请求”“两者都有”三种场景,明确优先级,避免规则失效。

  3. 题目集3:引入电梯状态枚举(STOPPED、MOVING_UP、MOVING_DOWN、OPENING、CLOSING),替代仅用“方向”表示状态——比如电梯开门时,状态是OPENING,此时即使方向是UP,也不会移动,避免逻辑混淆;增加“非连续重复请求”过滤(可选),比如内部队列已有3,再输入3时直接忽略,更符合实际电梯逻辑。

(二)设计模式应用

  1. 策略模式:将调度算法(同方向优先、就近优先、SCAN算法)抽象成Strategy接口,不同算法实现成具体类(SameDirStrategy、NearestStrategy),电梯可动态切换算法——比如高峰期用SCAN算法,低峰期用就近优先,灵活性更高。

  2. 观察者模式:让RequestQueue作为被观察者,Elevator作为观察者,当队列有新请求时,主动通知电梯,不用电梯每次移动都去“轮询”队列,减少冗余判断。

(三)用户体验

  1. 输入容错:支持多种输入格式,比如最小/最大楼层可以空格分隔,也可以换行输入;外部请求格式支持<5,9>,也支持<5 , 9>(允许空格),降低用户输入门槛。

  2. 运行日志:增加请求加入队列的提示,比如“Add internal request: 5”“Add external request: 3→7”,用户能清楚知道请求是否被正确接收。

五、总结

(一)学到的东西

  1. 面向对象设计的“落地感”:以前只知道“单一职责原则”是“一个类干一件事”,但通过三次迭代,才真正明白“怎么拆分职责”——比如第三次作业中,把“请求存储”“运行逻辑”“协调控制”拆成三个类,不是凭空想的,而是踩了前两次“耦合太高”的坑后,才找到合理的拆分方式。

  2. 异常处理的“全面性”:从第一次“静默忽略无效请求”,到第二次“提示格式错误”,再到第三次“捕获转换异常、校验楼层范围”,逐渐明白“健壮的程序不是不报错,而是能友好地处理错误”。

  3. 需求理解的“精准度”:第二次作业中“重复请求过滤”的误解,让我学会了“先划清需求边界,再写代码”——比如题目说“过滤连续相同请求”,就不要多做“非连续过滤”,避免做无用功。

(二)还要深入学习的方向

  1. 复杂调度算法:目前只实现了“同方向优先”,但实际电梯还有SCAN(电梯调度算法)、LOOK等更高效的策略,需要研究这些算法的逻辑,并用代码实现。

  2. 并发处理:三次作业都是“串行处理请求”,但真实场景中,电梯会同时收到多个请求(比如5楼和8楼同时按上行),需要学习多线程,让“请求接收”和“电梯运行”并行,模拟真实场景。

  3. 单元测试:目前靠手动输入测试用例,效率低且容易漏场景,需要学习JUnit等单元测试框架,写自动化测试用例,比如测试“同方向优先”“重复请求过滤”等核心逻辑,确保改代码后不回归。

这三次作业虽然踩了不少坑,但每次解决问题后,都能明显感觉到自己在“从代码搬运工向设计者转变”——从一开始只关心“怎么跑通”,到后来会思考“怎么设计更优雅、更易维护”。这种成长比单纯掌握某个知识点更有价值,也为后续更复杂的项目打下了基础。

posted on 2025-11-22 11:45  林大帅  阅读(2)  评论(0)    收藏  举报