电梯blog总结

前言

三次题目集围绕电梯调度问题的迭代开发展开,逐步从单一类设计过渡到多类协作的复杂系统设计,重点考察面向对象设计原则的应用能力、复杂逻辑的编码实现能力以及对异常场景的处理能力。

知识点
第一次题目要求将电梯状态(当前楼层、方向、运行状态)、请求队列(内部请求、外部上行/下行请求)及调度逻辑集中在一个类中。
第二次题目强制拆分职责,设计独立的电梯类(管理移动)、请求队列类(管理请求存储)、控制类(管理调度。
第三次题目引入外部请求与内部请求的动态转换(外部请求处理后生成内部请求)。
三次题目都要处理无效楼层、重复请求等异常输入,涉及正则表达式解析、数据校验及集合操作。

题量与难度
虽然不多,但写起来还是比较困难

设计与分析

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

  class Elvtr {  
      private int curFlr;  
      private Dir dir; // 方向(UP/DOWN/IDLE)  
      private TreeSet<Integer> inReq; // 内部请求队列  
      private TreeSet<Integer> ouUp, ouDn; // 外部上行/下行请求  
  }  

使用TreeSet存储请求,自动排序去重。
调度算法 :
方向决策(setDir()):
合并当前楼层以上的内部请求和外部上行请求,计算最近的同方向目标。
若同方向无请求,则选择反方向的最近请求。
移动处理 (processUpDirection()processDownDirection()):
按方向遍历目标楼层,逐个处理停靠并更新队列。

SourceMontor报表分析 :

PowerDesigner类图 :

问题总结:
Elvtr类的方法(如setDir()、processUpDirection())因承担方向决策和移动逻辑,圈复杂度(Cyclomatic Complexity)超过20
方法行数过长,可读性差。
改进:
将状态管理(当前楼层、方向)独立为ElevatorState类,请求队列管理独立为RequestPool类,调度逻辑抽离为BasicScheduler类。

第二次题目:
对之前电梯调度程序进行迭代性设计,目的为解决电梯类职责过多的问题,类设计要求遵循单一职责原则(SRP),要求必须包含但不限于设计电梯类、乘客请求类、队列类以及控制类。
乘客请求楼层数有误,具体为高于最高楼层数或低于最低楼层数,处理方法:程序自动忽略此类输入,继续执行
乘客请求不合理,具体为输入时出现连续的相同请求,例如<3><3><3>或者<5,DOWN><5,DOWN>,处理方法:程序自动忽略相同的多余输入,继续执行,例如<3><3><3>过滤为<3>
类结构拆分 :
Elevator类 :

class Elevator {  
    private int currentFloor;  
    private Dir direction;  
    public void moveTo(int target); // 移动至目标楼层  
}  
 仅负责楼层移动和方向管理,无请求处理逻辑。  

RequestQueue类 :

class RequestQueue {  
    private TreeSet<Integer> internal;  
    private TreeSet<Integer> externalUp;  
    private TreeSet<Integer> externalDown;  
}  
 独立管理请求的存储与过滤(如去重、无效楼层过滤)。  

Controller类 :

class Controller {  
    private void determineDirection(); // 根据请求分布确定方向  
    private void processUp();          // 处理上行请求  
    private void processDown();        // 处理下行请求  
}  
 实现调度算法,控制电梯的移动逻辑。      

SourceMontor报表分析 :

PowerDesigner类图 :

问题总结:
determineDirection()方法可能因合并多类请求(内部、外部上行/下行)导致逻辑嵌套过深,圈复杂度达15+。
Controller频繁调用Elevator和RequestQueue的方法(如moveTo()、getExternalUp()),类耦合度(Coupling Between Objects)仍较高。
使用TreeSet存储请求时,频繁的tailSet()和addAll()操作可能导致性能瓶颈(如O(n)时间复杂度)。
改进:
引入LOOK算法,电梯仅移动到最远请求楼层后反向,减少无效移动。

Integer farthestRequest = queue.getExternalUp().last();  
if (fartheastRequest != null) elevator.moveTo(farthestRequest);  

使用优先级队列(PriorityQueue)按请求时间排序。

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

 class RequestQueue {  
     private TreeSet<Integer> externalUpSources; // 外部上行请求源  
     private Map<Integer, Queue<Integer>> upDestinations; // 源楼层→目的楼层队列  
     // 下行同理  
 }  

使用HashMap存储每个外部请求源对应的目的楼层队列,确保请求按顺序处理。

调度流程调整 :
外部请求处理 :
若外部请求为<5,9>,则将其加入externalUpSources,并将9存入upDestinations.get(5)队列。
停靠时转换请求 :
当电梯到达5层(外部上行请求源),将upDestinations.get(5)中的所有目的楼层(如9)加入内部队列。
SourceMontor报表分析 :

PowerDesigner类图 :

问题总结:
方向切换逻辑未彻底解决,圈复杂度突破25。
upDestinations中的队列在请求处理后未及时清理,长期运行可能占用过多内存。
改进:
在processStop()方法中增加upDestinations.remove(floor).clear(),确保处理后的请求队列及时释放。
采坑心得

第一次题目:
问题场景 :
输入样例:<5,UP>(3楼外部上行请求)和内部请求7。电梯处理完3楼后,未继续上行至7楼,而是直接下行。
原因分析 :
setDir()方法在计算同方向请求时,未合并内部和外部请求,导致方向误判。
解决方案 :
修改setDir()逻辑,合并内部和外部同方向请求:

 TreeSet<Integer> allUp = new TreeSet<>(inReq.tailSet(curFlr));  
 allUp.addAll(ouUp.tailSet(curFlr));  

第二次题目:重复请求过滤失效
问题场景 :
输入多个<5>,期望过滤为单个请求,实际队列中仍存在多个。
原因分析 :
使用LinkedList存储内部请求,未去重。
解决方案 :
改用TreeSet存储请求,利用其自动去重特性:

 class RequestQueue {  
     private TreeSet<Integer> internal = new TreeSet<>();  
 }  

第三次题目:目的楼层未正确入队
问题场景 :
外部请求<5,9>处理后,内部队列中未加入9。
原因分析 :
processStop()中未将目的楼层从upDestinations转移到内部队列。
解决方案 :

 Queue<Integer> dests = upDestinations.remove(floor);  
 if (dests != null) {  
     internal.addAll(dests);  
 }

改进建议 :

电梯仅移动到最远请求楼层后反向,避免无效移动。

    // 在Controller中实现  
    Integer farthestUp = queue.getExternalUp().last();  
    if (farthestUp != null) elevator.moveTo(farthestUp); 

将调度算法封装为接口,支持动态切换。

    interface SchedulingStrategy {  
        void schedule(Elevator e, RequestQueue q);  
    }  
    class PeakHourStrategy implements SchedulingStrategy { ... }  

针对Controller的调度逻辑编写测试用例,验证不同请求组合的输出。
记录电梯移动路径和请求处理状态,便于调试复杂场景。
输入处理增强 :
允许从文件读取多组测试用例,提升测试效率。
支持命令行实时输入请求并查看电梯状态。

总结
通过三次迭代,深入理解了单一职责原则的实际应用,掌握了通过类拆分降低复杂度的技巧。
从基础的方向优先算法到动态请求转换,提升了复杂逻辑的编码与调试能力。
通过处理无效输入、重复请求等边界场景,增强了代码的健壮性。

课程建议 :
感觉上课内容与每周对应的题目不是很符合,就像是先接触题再由题讲知识点,这样感觉做题很迷茫,希望可以渗透一些知识点再去做题,很希望学好java

posted @ 2025-04-20 20:05  菠夢吹雪  阅读(68)  评论(0)    收藏  举报