我的三次电梯题目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();
    }
}
View Code

 

 

 

 

二、代码实现:从 “照葫芦画瓢” 到自己思考进步。

初次尝试:第一次电梯作业作业的 “手忙脚乱”

拿到题目时,我非常恐惧,因为我完全是 “新手模式”,这么长的题目以及内容叙述让我开始紧张、退却。但是摸索下我先定义了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 算法),优化电梯运行效率。

实战巩固:多找些题目进行训练,小题不懈怠,大题更认真。拓宽编程视野。

 

编程就像搭积木,每一次失败都是在为下一次成功积累 “零件”。期待在下一阶段的学习中,我能带着这些宝贵经验,真正 “驾驭” 电梯调度的复杂逻辑!

 

posted @ 2025-04-20 10:52  情绪的雾  阅读(54)  评论(0)    收藏  举报