我的三次电梯题目Blog
在最近完成的三次单部电梯调度程序作业中,我经历了从“一头雾水”开始的探索过程。尽管到最后仍没能让代码完美实现所有功能,但每一次尝试都像解锁新关卡,填补了知识盲区。今天就来复盘这段充满挑战与收获的编程之旅。也以此激励我以后的学习。
一、对三次作业题目进行分析、理解。
1. NCHU_单部电梯调度程序:搭建基础框架
核心要求:设计一个包含基础属性(最大 / 最小楼层、当前楼层、运行方向、状态)的电梯类,管理内部和外部请求队列(区分上行 / 下行)。电梯默认停在 1 层,优先处理同方向请求,处理无效楼层请求(如越界),空闲时保持静止。
作业目的:让我们掌握基础类设计和电梯调度逻辑,理解请求队列管理与方向优先级策略,为后续迭代打下基础。
这是老师给出的电梯系统详解中的LOOK算法,主要让我们知道电梯运行规则,队列的正确使用。设计一个电梯类,包含状态管理、请求队列管理以及调度算法,并使用一些测试用例,模拟不同的请求顺序,观察电梯的行为是否符合预期,比如是否优先处理同方向的请求,是否在移动过程中处理顺路的请求等。为了降低编程难度,不考虑同时有多个乘客请求同时发生的情况,即采用串行处理乘客的请求方式,电梯只按照规则响应请求队列中当前的乘客请求,响应结束后再响应下一个请求。

2. NCHU_单部电梯调度程序(类设计):优化职责划分
核心要求:解决电梯类职责过重问题,强制要求设计乘客请求类、电梯类、队列类及控制类,遵循单一职责原则(SRP)。需过滤楼层越界请求和连续重复请求(如<3><3><3>仅保留一个)。
作业目的:训练我们拆分复杂功能的能力,通过类间协作实现高效调度,提升代码可维护性和扩展性。
类图


3. NCHU_单部电梯调度程序(类设计 - 迭代):深化功能迭代
核心要求:引入乘客类,取消乘客请求类,调整外部请求格式为<请求源楼层,请求目的楼层>,处理后需将目的楼层加入内部队列。进一步细化类设计,确保各模块职责清晰。
作业目的:模拟真实场景下的乘客行为,强化类设计的灵活性,同时考验对复杂请求处理流程的实现能力。
对题目给出的测试样例进行分析:


import java.util.*; import java.util.regex.*; // 方向枚举类 enum Direction { UP, DOWN, IDLE } // 状态枚举类 enum State { MOVING, STOPPED } // 外部请求类 class ExternalRequest { private Integer floor; private Direction direction; public ExternalRequest(Integer floor, Direction direction) { this.floor = floor; this.direction = direction; } public Integer getFloor() { return floor; } public Direction getDirection() { return direction; } } // 电梯类 class Elevator { private int currentFloor; private Direction direction; private State state; private int maxFloor; private int minFloor; public Elevator(int minFloor, int maxFloor) { this.minFloor = minFloor; this.maxFloor = maxFloor; this.currentFloor = minFloor; this.direction = Direction.IDLE; this.state = State.STOPPED; } public static Elevator getElevatorInstance(int minFloor, int maxFloor) { return new Elevator(minFloor, maxFloor); } public int getCurrentFloor() { return currentFloor; } public void setCurrentFloor(int currentFloor) { this.currentFloor = currentFloor; } public Direction getDirection() { return direction; } public void setDirection(Direction direction) { this.direction = direction; } public State getState() { return state; } public void setState(State state) { this.state = state; } public int getMaxFloor() { return maxFloor; } public int getMinFloor() { return minFloor; } public boolean isValidFloor(int floor) { return floor >= minFloor && floor <= maxFloor; } } // 请求队列类 class RequestQueue { private LinkedList<Integer> internalRequests = new LinkedList<>(); private LinkedList<ExternalRequest> externalRequests = new LinkedList<>(); private Elevator elevator; public RequestQueue(Elevator elevator) { this.elevator = elevator; } public LinkedList<Integer> getInternalRequests() { return internalRequests; } public void setInternalRequests(LinkedList<Integer> internalRequests) { this.internalRequests = internalRequests; } public LinkedList<ExternalRequest> getExternalRequests() { return externalRequests; } public void setExternalRequests(LinkedList<ExternalRequest> externalRequests) { this.externalRequests = externalRequests; } public void addInternalRequest(int floor) { if (!internalRequests.contains(floor)) { internalRequests.add(floor); } } public void addExternalRequest(int floor, Direction direction) { ExternalRequest request = new ExternalRequest(floor, direction); if (!externalRequests.contains(request)) { externalRequests.add(request); } } public void addRequest(String input) { try { if (input.matches("<\\d+>")) { int floor = Integer.parseInt(input.substring(1, input.length() - 1)); if (elevator.isValidFloor(floor)) { addInternalRequest(floor); } } else if (input.matches("<\\d+,UP|DOWN>")) { String[] parts = input.substring(1, input.length() - 1).split(","); int floor = Integer.parseInt(parts[0]); Direction direction = "UP".equals(parts[1])? Direction.UP : Direction.DOWN; if (elevator.isValidFloor(floor)) { addExternalRequest(floor, direction); } } else { throw new IllegalArgumentException("Invalid request format"); } } catch (IllegalArgumentException e) { // 忽略无效请求 } } } // 控制类 class Controller { private Elevator elevator; private RequestQueue queue; public Controller(Elevator elevator, RequestQueue queue) { this.elevator = elevator; this.queue = queue; } public Elevator getElevator() { return elevator; } public void setElevator(Elevator elevator) { this.elevator = elevator; } public RequestQueue getQueue() { return queue; } public void setQueue(RequestQueue queue) { this.queue = queue; } public void processRequests() { while (!queue.getInternalRequests().isEmpty() ||!queue.getExternalRequests().isEmpty()) { if (elevator.getState() == State.STOPPED) { determineDirection(); } move(); if (shouldStop(elevator.getCurrentFloor())) { openDoors(); removeRequests(elevator.getCurrentFloor()); } } } private void determineDirection() { if (!queue.getInternalRequests().isEmpty()) { int targetFloor = queue.getInternalRequests().getFirst(); if (targetFloor > elevator.getCurrentFloor()) { elevator.setDirection(Direction.UP); } else if (targetFloor < elevator.getCurrentFloor()) { elevator.setDirection(Direction.DOWN); } else { elevator.setDirection(Direction.IDLE); } } else if (!queue.getExternalRequests().isEmpty()) { ExternalRequest request = queue.getExternalRequests().getFirst(); if (request.getFloor() > elevator.getCurrentFloor()) { elevator.setDirection(Direction.UP); } else if (request.getFloor() < elevator.getCurrentFloor()) { elevator.setDirection(Direction.DOWN); } else { elevator.setDirection(Direction.IDLE); } } else { elevator.setDirection(Direction.IDLE); } } private void move() { if (elevator.getDirection() == Direction.UP) { elevator.setCurrentFloor(elevator.getCurrentFloor() + 1); System.out.printf("Current Floor: %d Direction: %s\n", elevator.getCurrentFloor(), elevator.getDirection()); elevator.setState(State.MOVING); } else if (elevator.getDirection() == Direction.DOWN) { elevator.setCurrentFloor(elevator.getCurrentFloor() - 1); System.out.printf("Current Floor: %d Direction: %s\n", elevator.getCurrentFloor(), elevator.getDirection()); elevator.setState(State.MOVING); } else { elevator.setState(State.STOPPED); } } private boolean shouldStop(int floor) { if (queue.getInternalRequests().contains(floor)) { return true; } for (ExternalRequest request : queue.getExternalRequests()) { if (request.getFloor() == floor && request.getDirection() == elevator.getDirection()) { return true; } } return false; } private Integer getNextFloor() { if (!queue.getInternalRequests().isEmpty()) { return queue.getInternalRequests().getFirst(); } else if (!queue.getExternalRequests().isEmpty()) { return queue.getExternalRequests().getFirst().getFloor(); } return null; } private Integer getClosest(Integer a, Integer b) { if (a == null) { return b; } if (b == null) { return a; } int currentFloor = elevator.getCurrentFloor(); int diffA = Math.abs(currentFloor - a); int diffB = Math.abs(currentFloor - b); return diffA < diffB? a : b; } private void openDoors() { System.out.printf("Open Door # Floor %d\n", elevator.getCurrentFloor()); System.out.println("Close Door"); elevator.setState(State.STOPPED); } private void removeRequests(int currentFloor) { queue.getInternalRequests().remove(Integer.valueOf(currentFloor)); Iterator<ExternalRequest> iterator = queue.getExternalRequests().iterator(); while (iterator.hasNext()) { ExternalRequest request = iterator.next(); if (request.getFloor() == currentFloor) { iterator.remove(); } } } } // 请求类 class Request { private int floor; private String direction; // "UP", "DOWN" or null (for inside requests) private boolean isInside; public Request(String input, int minFloor, int maxFloor) { parseInput(input, minFloor, maxFloor); } private void parseInput(String input, int minFloor, int maxFloor) { Pattern pattern = Pattern.compile("<(\\d+)(?:,(UP|DOWN))?>"); Matcher matcher = pattern.matcher(input); if (matcher.find()) { this.floor = Integer.parseInt(matcher.group(1)); this.direction = matcher.group(2); this.isInside = (this.direction == null); // Validate floor range if (floor < minFloor || floor > maxFloor) { throw new IllegalArgumentException("Invalid floor"); } } else { throw new IllegalArgumentException("Invalid request format"); } } public int getFloor() { return floor; } public String getDirection() { return direction; } public boolean isInside() { return isInside; } @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null || getClass() != obj.getClass()) return false; Request request = (Request) obj; return floor == request.floor && Objects.equals(direction, request.direction); } @Override public int hashCode() { return Objects.hash(floor, direction); } // 新增方法,将Request对象转换为符合要求的String形式 public String toRequestString() { if (isInside()) { return "<" + getFloor() + ">"; } else { return "<" + getFloor() + "," + getDirection() + ">"; } } } public class Main { public static void main(String[] args) { Scanner scanner = new Scanner(System.in); int minFloor = Integer.parseInt(scanner.nextLine()); int maxFloor = Integer.parseInt(scanner.nextLine()); Elevator elevator = Elevator.getElevatorInstance(minFloor, maxFloor); RequestQueue queue = new RequestQueue(elevator); Controller controller = new Controller(elevator, queue); while (true) { String input = scanner.nextLine().trim(); if (input.equalsIgnoreCase("end")) break; try { Request request = new Request(input, minFloor, maxFloor); // 将Request对象转换为String后再调用addRequest方法 queue.addRequest(request.toRequestString()); } catch (IllegalArgumentException e) { // 忽略无效请求 } } controller.processRequests(); } }

二、代码实现:从 “照葫芦画瓢” 到自己思考进步。
初次尝试:第一次电梯作业作业的 “手忙脚乱”
拿到题目时,我非常恐惧,因为我完全是 “新手模式”,这么长的题目以及内容叙述让我开始紧张、退却。但是摸索下我先定义了Elevator类,把楼层、方向、状态等属性一股脑塞进去,再用两个ArrayList分别存储内外请求。调度逻辑更是简单粗暴:循环检查请求队列,遇到同方向请求就移动,没有就切换方向。
while (!requestQueue.isEmpty()) {
Request request = requestQueue.poll();
if (isSameDirection(request)) {
moveTo(request.getFloor());
} else {
changeDirection();
}}
这段代码虽然能跑通基础功能,但漏洞百出:没有处理无效请求,队列操作没有线程安全保护,甚至连电梯开关门状态都没记录,导致输出结果与预期结果大不相同。
第一次迭代升级作业的再次打击
吃了第一次的亏,第二次作业我决心下功夫攻克。根据类图,我开始尝试拆分职责。新建PassengerRequest类专门校验请求合法性,RequestQueue类负责去重和存储,Controller类统筹调度。
java
// PassengerRequest类检查楼层合法性public boolean isValid(int floor, int minFloor, int maxFloor) {
return floor >= minFloor && floor <= maxFloor;}
但在实现过程中,类之间的依赖关系乱成 “一团麻”:Controller需要频繁调用RequestQueue和PassengerRequest的方法,代码耦合度极高。为了解决这个问题,我参考了工厂模式,通过RequestFactory类统一创建和处理请求对象,勉强让代码结构清晰了一些。
又一次的迭代更新: 显然我第三次作业的 再次挫败
第三次作业加入乘客类后,请求处理逻辑变得也是更加复杂。我尝试用 “中转站” 思路处理外部请求:先将乘客从源楼层接到电梯,再把目的楼层加入内部队列。但实际编码时,发现类间协作出现断层:乘客类与队列类的交互逻辑混乱,电梯调度时无法准确判断何时该处理新加入的目的楼层请求。
这次我分析了题目要求
java
// 理想中的请求处理流程(未完全实现)Passenger passenger = new Passenger(srcFloor, destFloor);
externalQueue.remove(passenger);
internalQueue.add(destFloor);
尽管反复调整代码结构,最终还是没能在截止时间前实现完整功能,提交的版本存在漏处理请求的严重问题。
三、“踩坑” 实录:那些让人崩溃的瞬间
线程安全的 “隐形炸弹”
在第一次作业中,我直接使用ArrayList存储请求队列,完全没意识到多线程访问的风险。直到测试时发现偶尔出现请求丢失,才明白需要加锁保护。
java
// 错误示范:未加锁的队列操作
public Request getNextRequest() {
return requestList.remove(0); // 多线程下可能抛出IndexOutOfBoundsException
}
这让我意识到线程安全与效率的平衡有多重要。
类设计的 “过度设计” 与 “设计不足”
第二次作业拆分类时,我陷入了 “为了拆分而拆分” 的误区。比如把简单的请求去重逻辑单独写成一个RequestFilter类,反而增加了不必要的类间调用。而在第三次作业中,又因为时间紧张,没有充分规划乘客类与其他类的交互,导致代码后期难以维护。
调度算法的 “顾此失彼”
为了优化调度效率,我尝试在第三次作业中实现 “动态方向调整”:电梯每到达一层,就重新评估当前方向是否最优。但这个逻辑与原有的 “同方向优先” 策略冲突,导致电梯在处理多个请求时频繁来回移动,效率反而更低。
四、“取经” 之路:与同学的思维碰撞
代码互审:从他人身上找灵感
在互评环节,我发现有同学用 “状态机” 模式管理电梯状态(停止、移动、开门、关门),代码简洁且逻辑清晰。例如,他们定义了一个ElevatorState枚举类,通过switch语句处理不同状态下的行为:
java
switch (currentState) {
case MOVING:
if (reachDestination()) {
changeState(ElevatorState.STOPPED);
}
break;
// 其他状态处理}
这个思路让我意识到,复杂逻辑可以通过抽象模型简化,而不是盲目堆砌条件判断。
算法讨论:群策群力破难题
虽然在老师发布的题目讨论区里,只有少数人发言,但是在班级内部宿舍宿舍之间大家围绕展开了激烈讨论。有思路的同学提出不同的想法供大家参考,有同学提出 :每次选择距离当前楼层最近的同方向请求;还有人建议用 “优先级队列” 动态排序请求。虽然这些方案最终没有完全应用到我的代码中,但拓宽了我的解题思路。
调试互助:“众人拾柴火焰高”
当我的第三次作业出现请求丢失问题时,一位同学帮我检查代码,发现是队列同步锁的范围设置错误。原本我只在addRequest方法上加锁,而removeRequest方法没有保护,导致多线程环境下数据不一致。这个教训让我明白:闭门造车容易陷入思维死角,及时求助能少走很多弯路。
五、虽败犹荣:在失败中积累 “隐性财富”
尽管三次作业都没有拿到满分,甚至第三次还存在严重 BUG,但我反而觉得收获颇丰:
知识补漏:从线程安全到设计模式,从队列操作到状态机模型,许多之前模糊的概念变得清晰。
调试能力提升:学会用out.println打印关键变量,用 IDE 的断点调试追踪代码逻辑,这些技能在后续学习中至关重要。
团队协作意识:通过与同学交流,认识到众人拾柴火焰高,团队协作的力量,开放分享才能共同进步。
六、总结与展望:带着 “遗憾” 重新出发
经验总结重视前期设计:宁可多花时间画类图、梳理逻辑,也不要急于编码,否则后期修改成本极高。
测试先行:这次作业吃了测试不足的亏,后续一定要编写单元测试和边界测试用例,提前暴露问题。
拥抱 “不完美”:编程是不断试错的过程,失败不可怕,关键是要从错误中提炼经验。
单一职责:每个类只做一件事,比如队列类不关心调度逻辑,只负责数据操作。
封装变化:将易变的部分(调度策略、请求格式)封装在独立类中,修改时只需调整局部,不影响整体。
面向接口编程:第二次作业的控制类依赖调度接口,第三次作业的请求处理依赖统一的Request接口,为后续扩展留足空间。
未来计划
深入学习设计模式:研究工厂模式、观察者模式在电梯调度中的应用,提升代码的扩展性。
加强算法训练:学习经典调度算法(如 Scan 算法、LOOK 算法),优化电梯运行效率。
实战巩固:多找些题目进行训练,小题不懈怠,大题更认真。拓宽编程视野。
编程就像搭积木,每一次失败都是在为下一次成功积累 “零件”。期待在下一阶段的学习中,我能带着这些宝贵经验,真正 “驾驭” 电梯调度的复杂逻辑!
浙公网安备 33010602011771号