面向对象总结Blog_01:单步电梯调度

一、前言
知识点:题目集 5 - 7 主要围绕面向对象编程的核心知识点展开,涵盖了类与对象、封装、继承、多态等基础概念的深入应用。题目集 5 重点考查了基础类的设计与封装,要求根据具体业务场景设计类的属性和方法;题目集 6 在此基础上引入了单一职责原则(SRP),强调类的职责划分,避免类的功能过于臃肿;题目集 7 进一步深化,涉及到复杂的调度算法和状态管理,同时需要合理运用集合框架来处理数据。

题量:每次题目集包含 3 - 4 道题目,其中单部电梯调度问题作为核心题目贯穿了题目集 5 和 6 ,占据了较大的分值比重。

难度:这几次整体难度呈递进趋势。题目集 5 属于基础入门级别,帮助巩固基础概念;题目集 6 在设计上提出了更高的要求,需要深入理解设计原则并应用到实际代码中;题目集 7 则更加深入,不仅需要处理复杂的逻辑,还对代码的性能和扩展性有一定要求。

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

第一次写,我只写了一个电梯类和主类,把所有功能都塞入电梯类中,然后直接在主类中调用
public class Main {
public static void main(String[] args) {
Scanner input = new Scanner(System.in);
String data; // 输入数据:字符串
int floor = 0;
Elevator.Direction direction = null;
int minFloor, maxFloor; // 电梯最低楼层、最高楼层
String request = ""; // 提取出的乘客请求字符串
ArrayList list = new ArrayList<>(); // 用于接收用户输入的数组
data = input.next();
while (!data.equalsIgnoreCase("End")) { // 输入以“end”结束
list.add(data);
data = input.next();
}

    minFloor = Integer.parseInt(list.get(0));// 第一行,电梯最低楼层
    maxFloor = Integer.parseInt(list.get(1));// 第二行,电梯最高楼层

    Elevator elevator = new Elevator(minFloor, maxFloor);// 创建电梯对象

    for (int i = 2; i < list.size(); i++) {// 从第三行开始为乘客请求
        request = list.get(i);
        if (request.contains(",")) {// 外部请求
            if (!request.matches("<\\d+,\\s*(UP|DOWN)>")) {
                System.out.println("Wrong Format");
            }

            String[] parts = request.replaceAll("[<>]", "").split(",");
            floor = Integer.parseInt(parts[0].trim());
            direction = Elevator.Direction.valueOf(parts[1].trim().toUpperCase());
            elevator.addExternalRequest(floor, direction);// 外部请求入队,格式为<楼层数,方向>
        } else {
            if (!request.matches("<\\d+>")) {// 内部请求
                System.out.println("Wrong Format");
            }
            floor = Integer.parseInt(request.replaceAll("[<>]", ""));
            elevator.addInternalRequest(floor);// 内部请求入队,格式为<楼层数>
        }
    }

    while (!elevator.getInternalRequests().isEmpty()
            || !elevator.getExternalRequests().isEmpty()) {
        elevator.processRequests(); // 一次性处理所有的内、外部乘客请求
    }

    input.close();
}

}
考虑到电梯运行方向问题,我设置了向上和向下两个队列,并使用了正则表达式来判断请求应该进入哪个队列,最后处理队列中的所有请求。

题目集6:对之前电梯调度程序进行迭代性设计,目的为解决电梯类职责过多的问题,类设计要求遵循单一职责原则(SRP),要求必须包含但不限于设计电梯类、乘客请求类、队列类以及控制类,具体设计可参考如下类图。
电梯迭代1类图.png
电梯运行规则与前阶段单类设计相同,但要处理如下情况:

乘客请求楼层数有误,具体为高于最高楼层数或低于最低楼层数,处理方法:程序自动忽略此类输入,继续执行
乘客请求不合理,具体为输入时出现连续的相同请求,例如<3><3><3>或者<5,DOWN><5,DOWN>,处理方法:程序自动忽略相同的多余输入,继续执行,例如<3><3><3>过滤为<3>
注意:本次作业类设计必须符合如上要求(包含但不限于乘客请求类、电梯类、请求队列类及控制类,其中控制类专门负责电梯调度过程)

这次我定义了枚举类、外部请求类 ExternalRequest、电梯类 Elevator、请求队列类 RequestQueue、控制器类 Controller和主类,下面是类图:

通过多个类的使用,代码初步完成了单一职责原则的要求

ExternalRequest、RequestQueue
两个请求类表示外部对电梯的请求,包含请求的楼层和方向还有内部的请求
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;
}

}

电梯类 Elevator定义了电梯的属性,比如说运动状态和楼层上限
class Elevator {
private int currentFloor;
private Direction direction;
private State state;
private int minFloor;
private int maxFloor;

控制器类Controller控制电梯的运行,根据请求队列中的请求来调度电梯的移动。
class Controller {
private Elevator elevator;
private RequestQueue queue;

public Controller(Elevator elevator, RequestQueue queue) {
    this.elevator = elevator;
    this.queue = queue;
}

public void move() {
    // 处理内部请求
    while (!internal.isEmpty()) {
        // 省略了一些逻辑
    }

    // 处理外部请求
    while (!external.isEmpty()) {
        // 省略了一些逻辑
    }
}

}

主类,负责读取用户输入,创建电梯、请求队列和控制器对象,并根据用户输入添加请求,最后调用控制器的 move 方法来运行电梯。

三、采坑心得
题目集 5
在处理电梯外部请求时,没有对上行和下行请求进行有效区分,导致电梯调度逻辑混乱,在一些测试用例中出现错误的运行方向判断。同时,由于Elevator类职责过多,代码中出现了大量的条件判断,使得代码的可读性极差,在调试过程中很难定位问题。而且我之前没有写代码时写注释的习惯,然后这次代码又全放在一起,找半天找不到。
题目集 6
在遵循单一职责原则进行类设计时,类之间的交互关系处理不当,例如ElevatorController类在获取请求队列时出现空指针异常,原因是没有正确初始化相关对象。另外,在处理无效请求和重复请求过滤时,逻辑存在漏洞,导致部分异常情况没有被正确处理。
我输入了一组数据
20
❤️,UP>
<5>
<6,DOWN>
<7>
<3>
end
结果:

将请求加入列表时,由于逻辑是只加入与前面不同的请求,所以导致第二个三楼没有加入,然后电梯就没有下去,之后我改善了逻辑,改为了与前一个请求不同时加入,就不再出现这种情况

然后我在这次作业写了注释,修改代码时方便很多。
题目集7
这一次作业给了类图,这样我写代码时思路很清晰,节省了很多不必要的步骤,这让我想起前面两次作业都是想到哪写到哪,这样效率低下也很容易出错。

这一次是题目给了,我觉得以后写代码前都要去规划一个流程图或类图,这样代码功能可以更全面。

四、改进建议
1、职责分离与单一职责原则深化:
就像题目集6中要求的那样,继续遵循单一职责原则,确保每个类的功能单一且明确。对于 Elevator 类,可进一步拆分其中过于复杂的功能,例如将电梯的调度算法、状态管理、请求处理等功能分别封装到不同的类中。
对于电梯的外部请求处理,单独创建一个类(如 ExternalRequestHandler 类),专门负责处理外部请求的逻辑,包括区分上行和下行请求、添加请求到队列、检查请求的有效性等。这样可以使电梯类专注于自身的运行和状态管理。
2、无效请求和重复请求处理逻辑完善:
对无效请求(如楼层超出范围、请求格式错误等)和重复请求的处理逻辑进行全面的测试和优化。可以使用单元测试来覆盖各种可能的情况,确保所有异常情况都能得到正确的处理。
在处理重复请求时,不仅仅考虑与前一个请求的比较,而是采用更全面的去重策略。例如,在添加请求到队列之前,遍历整个队列检查是否存在相同的请求(根据请求楼层和方向来判断)。
3、代码可读性与注释规范:
养成良好的代码注释习惯,在关键的代码段、方法和变量声明处添加详细的注释,解释代码的功能、逻辑和用途。注释应该简洁明了,能够帮助其他人包括未来的自己)快速理解代码的含义。
4、类图和流程图的运用:
在开始编写代码之前,根据需求设计详细的类图和流程图。类图可以帮助明确类之间的关系和职责,流程图则有助于梳理算法的逻辑和执行步骤。这不仅可以提高代码的质量和完整性,还能在开发过程中节省时间,减少错误的发生。

五、总结
通过完成题目集 5 - 7 的一系列电梯调度问题,我对面向对象编程有了更深刻的理解与掌握。从最初将所有功能堆积在单一类中,导致代码混乱、可读性差、调试困难,到逐步遵循单一职责原则拆分功能,优化类与类之间的交互。
在这几次作业中,每一次的 “踩坑” 都是宝贵的经验积累。题目集 5 中因职责不清晰、逻辑混乱而出现的错误,让我意识到合理划分功能模块的必要性;题目集 6 里类交互问题与请求处理逻辑漏洞,使我更加注重细节与逻辑的严谨性;题目集 7 借助类图高效完成代码编写,让我体会到前期设计规划对开发效率与代码质量的提升作用。最后就是学会了要养成给代码写注释的习惯,方便以后阅读代码。

给课程和作业的建议:可以设置需要用到前面知识点的综合题目,防止学了后面忘了前面。

posted @ 2025-04-20 22:49  gylx  阅读(30)  评论(0)    收藏  举报