第一次blog作业 令人头秃的电梯

前言

第一次java大作业可算是结束了,感觉经历了这三次的洗礼,我从一个java小白变成了一个头发掉光光的java小白大概对面向对象编程有初步认知的java小白。
总的来说,三次题目集以单部电梯调度功能为中心,不断增加新的功能,难度自然也是逐级递增。当然,虽然我在三次题目集中没有把这个程序设计的很好(虽然前两次在pta上是能过的,但还是有几个Bug),但肯定还是学到了一些东西的:

  1. 熟悉了java基本语法。
  2. 对面向对象程序设计的思路流程有了基本认知。
  3. 认识到代码可拓展性、可延伸性的重要。
  4. 明白了不要拖延的重要性(说多了都是泪)。
    好了,废话不多说,接下来我得好好复盘一些这个磨人的小电梯,毕竟它也是陪我折腾了n个日日夜夜、让我日不能食,夜不能寐的。

设计与分析

第一次迭代

题目详情我就不在这里赘述了,光是看到那一串字我都脑门子直抽抽。简而言之,最初的题目要求大概是这样的:

  1. 设计电梯类:设计一个 Java 电梯类,涵盖最大楼层数、最小楼层数(默认 1 层)、当前楼层、运行方向、运行状态,还有电梯内乘客请求队列、电梯外乘客请求队列(区分上行和下行)。
  2. 遵循运行规则:电梯默认停在 1 层且静止,有请求时开始移动,优先处理同方向请求,同方向请求处理完再处理反方向请求。运行状态有停止、移动中、开门、关门等。运行到某楼层时检查是否有请求并决定移动方向,每次移动一层,有停靠请求就开门处理后关门继续移动。
  3. 处理请求输入:用键盘模拟输入乘客请求,要处理无效请求,如超过最大或最小楼层的请求。考虑电梯空闲状态,无请求时停在当前楼层。
源码搁这儿呢,就不直接展示出来了,有点辣眼睛
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

// 定义电梯运行方向的枚举类
enum dir {
    UP, DOWN
}

// 外部请求类
class extreq {
    private final int floor;
    private final dir direction;

    extreq(int floor, dir direction) {
        this.floor = floor;
        this.direction = direction;
    }

    int getfloor() {
        return floor;
    }

    dir getdirection() {
        return direction;
    }
}

// 电梯类
class elv {
    private final int minfloor;
    private final int maxfloor;
    private int curfloor;
    private dir curdir;
    private ArrayList<Integer> intreqs;
    private ArrayList<extreq> extreqs;

    elv(int minfloor, int maxfloor) {
        this.minfloor = minfloor;
        this.maxfloor = maxfloor;
        this.curfloor = minfloor;
        this.curdir = dir.UP;
        this.intreqs = new ArrayList<>();
        this.extreqs = new ArrayList<>();
    }

    void addintreq(int floor) {
        intreqs.add(floor);
    }

    void addextreq(int floor, dir direction) {
        extreqs.add(new extreq(floor, direction));
    }

    ArrayList<Integer> getintreqs() {
        return intreqs;
    }

    List<extreq> getextreqs() {
        return extreqs;
    }

    void procreqs() {
        System.out.println("Current Floor: " + curfloor + " Direction: " + curdir);
        while (hasRequests()) {
            move();
        }
    }

    private boolean hasRequests() {
        return!intreqs.isEmpty() ||!extreqs.isEmpty();
    }

    private void detdir() {
        int tgtfloor = getTargetFloor();
        if (tgtfloor == -1) return;

        if (tgtfloor < curfloor) {
            curdir = dir.DOWN;
        } else if (tgtfloor > curfloor) {
            curdir = dir.UP;
        }
    }

    private int getTargetFloor() {
        if (!intreqs.isEmpty()) {
            return intreqs.get(0);
        } else if (!extreqs.isEmpty()) {
            return extreqs.get(0).getfloor();
        }
        return -1;
    }

    private void move() {
        if (curdir == dir.UP) {
            curfloor++;
        } else {
            curfloor--;
        }
        System.out.println("Current Floor: " + curfloor + " Direction: " + curdir);
        if (shouldstop()) {
            System.out.println("Open Door # Floor " + curfloor);
            System.out.println("Close Door");
        }
        detdir();
    }

    private boolean shouldstop() {
        return instop() || exstop();
    }

    private boolean instop() {
        if (intreqs.isEmpty()) return false;
        if (intreqs.get(0) == curfloor) {
            intreqs.remove(0);
            return true;
        }
        return false;
    }

    private boolean exstop() {
        if (extreqs.isEmpty() || extreqs.get(0).getfloor() != curfloor) return false;

        if (!intreqs.isEmpty()) {
            extreq firstReq = extreqs.get(0);
            if (firstReq.getdirection() == curdir
                    || (firstReq.getdirection() == dir.UP && intreqs.get(0) > firstReq.getfloor())
                    || (firstReq.getdirection() == dir.DOWN && intreqs.get(0) < firstReq.getfloor())) {
                int firstfloor = firstReq.getfloor();
                int idx = 0;
                while (idx < extreqs.size() && extreqs.get(idx).getfloor() == firstfloor) {
                    idx++;
                }
                curdir = firstReq.getdirection();
                extreqs.subList(0, idx).clear();
                return true;
            }
        } else {
            int firstfloor = extreqs.get(0).getfloor();
            int idx = 0;
            while (idx < extreqs.size() && extreqs.get(idx).getfloor() == firstfloor) {
                idx++;
            }
            extreqs.subList(0, idx).clear();
            for (int i = 1; i < idx; i++) {
                System.out.println("Open Door # Floor " + curfloor);
                System.out.println("Close Door");
            }
            return true;
        }
        return false;
    }
}

// 主类
public class Main {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in);
        ArrayList<String> list = new ArrayList<>();
        String data;

        while (true) {
            data = input.next();
            if (data.equalsIgnoreCase("End")) {
                break;
            }
            list.add(data);
        }

        int minfloor = Integer.parseInt(list.get(0));
        int maxfloor = Integer.parseInt(list.get(1));
        elv elevator = new elv(minfloor, maxfloor);

        for (int i = 2; i < list.size(); i++) {
            String req = list.get(i);
            if (req.contains(",")) {
                String[] parts = req.replaceAll("[<>]", "").split(",");
                int floor = Integer.parseInt(parts[0].trim());
                dir direction = "UP".equals(parts[1].trim().toUpperCase())? dir.UP : dir.DOWN;
                elevator.addextreq(floor, direction);
            } else {
                int floor = Integer.parseInt(req.replaceAll("[<>]", ""));
                elevator.addintreq(floor);
            }
        }

        while (!elevator.getintreqs().isEmpty() ||!elevator.getextreqs().isEmpty()) {
            elevator.procreqs();
        }
        input.close();
    }
}
这段shishan的基本设计思路是这样的:将电梯的各种属性和行为封装在 elv 类中,通过枚举类和外部请求类来辅助管理电梯的运行方向和请求信息。主类负责输入处理和程序启动。流程如下图:

image

一、核心类设计

  1. 枚举类 dir
    用途:定义电梯的运行方向,包含 UP(上行)和 DOWN(下行)两种状态,便于代码中统一表示和处理电梯的运行方向。
  2. 外部请求类 extreq
    用途:代表电梯外部乘客的请求,包含请求的楼层 floor 以及请求的方向 direction。
    设计思路:通过封装这两个属性,可将外部请求作为一个整体进行管理,方便后续的请求处理和调度。
  3. 电梯类 elv
    属性:
    minfloor 和 maxfloor:分别表示电梯可到达的最小楼层和最大楼层。
    curfloor:电梯当前所在的楼层。
    curdir:电梯当前的运行方向。
    intreqs:存储电梯内部乘客请求的列表。
    extreqs:存储电梯外部乘客请求的列表。
    设计思路:将电梯的各种属性和状态封装在该类中,同时提供相应的方法来管理请求和处理电梯的运行逻辑,实现了电梯运行的模块化设计。

二、核心方法设计

  1. elv 类的方法
    请求添加方法:
    addintreq(int floor):将电梯内部乘客的请求添加到 intreqs 列表中。
    addextreq(int floor, dir direction):将电梯外部乘客的请求添加到 extreqs 列表中。
    请求处理方法:
    procreqs():处理电梯的请求,在有请求时持续调用 move() 方法使电梯移动。
    hasRequests():检查电梯是否还有未处理的请求。
    方向确定方法:
    detdir():根据当前的目标楼层确定电梯的运行方向。
    getTargetFloor():获取当前的目标楼层。
    移动和停靠方法:
    move():根据当前的运行方向移动电梯,并检查是否需要停靠。
    shouldstop():判断电梯是否应该在当前楼层停靠。
    instop():检查电梯内部请求是否需要在当前楼层停靠。
    exstop():检查电梯外部请求是否需要在当前楼层停靠。

三、类图
image

四、复杂度分析
image

分析

我写的这个 elevator.java 代码总共192行,语句有122条,分支语句占了19.7%,逻辑判断不算少。但注释只占 2.1%,这点我做得不好,后续读代码或维护很受影响。代码里有2个类和接口,每个类平均8.5个方法,感觉有点多,有些类职责没分清楚。最复杂的是 Main.main() 方法,在 155 行,复杂度 9,整体平均复杂度也是 9,确实高了些,得优化。块深度最大到 4,平均 1.43,大部分块深度不高,但深层部分得处理。

第二次迭代

主要是对之前电梯调度程序进行迭代性设计,目的为解决电梯类职责过多的问题,类设计要求遵循单一职责原则(SRP),要求必须包含但不限于设计电梯类、乘客请求类、队列类以及控制类。

依旧乐色的代码
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

// 定义电梯运行方向的枚举类
enum dir {
    UP, DOWN // 电梯的运行方向,楼层向上和向下
}

// 外部请求类,表示来自外部的电梯请求
class extreq {
    private final int floor;   // 请求的楼层
    private final dir direction; // 请求的方向

    // 构造函数
    extreq(int floor, dir direction) {
        this.floor = floor;
        this.direction = direction;
    }

    // 获取请求的楼层
    int getfloor() {
        return floor;
    }

    // 获取请求的方向
    dir getdirection() {
        return direction;
    }
}

// 电梯类
class elv {
    private final int minfloor;  // 电梯的最小楼层
    private final int maxfloor;  // 电梯的最大楼层
    private int curfloor;        // 当前楼层
    private dir curdir;         // 当前方向
    private ArrayList<Integer> intreqs; // 内部请求的楼层列表
    private ArrayList<extreq> extreqs; // 外部请求的列表

    // 构造函数
    elv(int minfloor, int maxfloor) {
        this.minfloor = minfloor; // 设置最小楼层
        this.maxfloor = maxfloor; // 设置最大楼层
        this.curfloor = minfloor; // 初始时电梯在最小楼层
        this.curdir = dir.UP;     // 默认方向向上
        this.intreqs = new ArrayList<>(); // 初始化内部请求列表
        this.extreqs = new ArrayList<>(); // 初始化外部请求列表
    }

    // 添加内部请求
    void addintreq(int floor) {
        if (floor < minfloor || floor > maxfloor) {
            return; // 如果请求楼层超出范围,则忽略请求
        }
        if (!intreqs.isEmpty() && intreqs.get(intreqs.size() - 1) == floor) {
            return; // 如果最后一个请求是相同的楼层,则忽略该请求
        }
        intreqs.add(floor); // 将请求的楼层添加到内部请求列表
    }

    // 添加外部请求
    void addextreq(int floor, dir direction) {
        if (floor < minfloor || floor > maxfloor) {
            return; // 如果请求楼层超出范围,则忽略请求
        }
        
        // 处理重复的外部请求
        if (!extreqs.isEmpty() && extreqs.get(extreqs.size() - 1).getfloor() == floor 
                && extreqs.get(extreqs.size() - 1).getdirection() == direction) {
            return; // 如果最后一个请求是相同的楼层和方向,则忽略该请求
        }
        
        extreqs.add(new extreq(floor, direction)); // 将外部请求包装为extreq并添加到外部请求列表
    }

    // 获取所有内部请求
    ArrayList<Integer> getintreqs() {
        return intreqs; // 返回内部请求列表
    }

    // 获取所有外部请求
    List<extreq> getextreqs() {
        return extreqs; // 返回外部请求列表
    }

    // 处理请求
    void procreqs() {
        System.out.println("Current Floor: " + curfloor + " Direction: " + curdir);
        while (hasRequests()) { // 只要有请求就继续处理
            move(); // 移动电梯
        }
    }

    // 检查是否有尚未处理的请求
    private boolean hasRequests() {
        return !intreqs.isEmpty() || !extreqs.isEmpty(); // 如果内部请求或外部请求不为空则返回true
    }

    // 决定电梯的移动方向
    private void detdir() {
        int tgtfloor = getTargetFloor(); // 获取目标楼层
        if (tgtfloor == -1) return; // 如果没有目标楼层,返回

        // 根据目标楼层决定电梯方向
        if (tgtfloor < curfloor) {
            curdir = dir.DOWN; // 目标在当前楼层下方,方向设置为DOWN
        } else if (tgtfloor > curfloor) {
            curdir = dir.UP; // 目标在当前楼层上方,方向设置为UP
        }
    }

    // 获取电梯当前的目标楼层
    private int getTargetFloor() {
        if (!intreqs.isEmpty()) {
            return intreqs.get(0); // 返回第一个内部请求的楼层
        } else if (!extreqs.isEmpty()) {
            return extreqs.get(0).getfloor(); // 返回第一个外部请求的楼层
        }
        return -1; // 如果没有请求返回-1
    }

    // 移动电梯并处理到达目标楼层的情况
    private void move() {
        // 根据当前方向移动电梯
        if (curdir == dir.UP) {
            curfloor++; // 向上移动
        } else {
            curfloor--; // 向下移动
        }
        // 输出当前楼层和方向
        System.out.println("Current Floor: " + curfloor + " Direction: " + curdir);
        if (shouldstop()) { // 检查是否需要停下来
            System.out.println("Open Door # Floor " + curfloor);
            System.out.println("Close Door");
        }
        detdir(); // 重新决定电梯方向
    }

    // 检查电梯是否应该停下来
    private boolean shouldstop() {
        return instop() || exstop(); // 如果内部请求或外部请求满足停靠条件,返回true
    }

    // 检查是否因内部请求而停靠
    private boolean instop() {
        if (intreqs.isEmpty()) return false; // 如果没有内部请求,返回false
        if (intreqs.get(0) == curfloor) { // 如果当前楼层是第一个内部请求的楼层
            intreqs.remove(0); // 移除已处理的请求
            return true; // 返回true表示需要停靠
        }
        return false; // 否则返回false
    }

    // 检查是否因外部请求而停靠
    private boolean exstop() {
        if (extreqs.isEmpty() || extreqs.get(0).getfloor() != curfloor) return false; // 如果没有外部请求或当前楼层不是请求的楼层

        // 检查外部请求与内部请求的关系
        if (!intreqs.isEmpty()) {
            extreq firstReq = extreqs.get(0); // 获取第一个外部请求
            // 检查外部请求方向与内部请求的关系
            if (firstReq.getdirection() == curdir
                    || (firstReq.getdirection() == dir.UP && intreqs.get(0) > firstReq.getfloor())
                    || (firstReq.getdirection() == dir.DOWN && intreqs.get(0) < firstReq.getfloor())) {
                int firstfloor = firstReq.getfloor(); // 获取外部请求的楼层
                int idx = 0;
                // 清除当前楼层的所有外部请求
                while (idx < extreqs.size() && extreqs.get(idx).getfloor() == firstfloor) {
                    idx++;
                }
                curdir = firstReq.getdirection(); // 设置电梯当前方向为外部请求方向
                extreqs.subList(0, idx).clear(); // 移除已处理的外部请求
                return true; // 返回true表示需要停靠
            }
        } else {
            int firstfloor = extreqs.get(0).getfloor(); // 获取外部请求的楼层
            int idx = 0;
            // 清除当前楼层的所有外部请求
            while (idx < extreqs.size() && extreqs.get(idx).getfloor() == firstfloor) {
                idx++;
            }
            extreqs.subList(0, idx).clear(); // 移除已处理的外部请求
            for (int i = 1; i < idx; i++) {
                System.out.println("Open Door # Floor " + curfloor);
                System.out.println("Close Door"); // 对多个请求逐一处理
            }
            return true; // 返回true表示需要停靠
        }
        return false; // 若没有满足停靠条件则返回false
    }
}

// 主类
public class Main {
    public static void main(String[] args) {
        Scanner input = new Scanner(System.in); // 创建扫描器以读取输入
        ArrayList<String> list = new ArrayList<>(); // 用于存储输入的请求数据
        String data;

        // 读取用户输入,直到输入"End"
        while (true) {
            data = input.next();
            if (data.equalsIgnoreCase("End")) {
                break; // 如果输入为"End",则退出循环
            }
            list.add(data); // 否则将输入数据添加到列表中
        }

        // 解析最小和最大楼层
        int minfloor = Integer.parseInt(list.get(0)); // 获取最小楼层
        int maxfloor = Integer.parseInt(list.get(1)); // 获取最大楼层
        elv elevator = new elv(minfloor, maxfloor); // 创建电梯对象

        // 遍历所有请求
        for (int i = 2; i < list.size(); i++) {
            String req = list.get(i);
            if (req.contains(",")) { // 如果是外部请求
                String[] parts = req.replaceAll("[<>]", "").split(","); // 处理请求格式
                int floor = Integer.parseInt(parts[0].trim()); // 获取请求的楼层
                dir direction = "UP".equals(parts[1].trim().toUpperCase()) ? dir.UP : dir.DOWN; // 获取请求方向
                elevator.addextreq(floor, direction); // 将外部请求添加到电梯对象
            } else { // 如果是内部请求
                int floor = Integer.parseInt(req.replaceAll("[<>]", "")); // 处理请求格式
                elevator.addintreq(floor); // 将内部请求添加到电梯对象
            }
        }

        // 处理所有请求
        while (!elevator.getintreqs().isEmpty() || !elevator.getextreqs().isEmpty()) {
            elevator.procreqs(); // 调用电梯处理请求的方法
        }
        input.close(); // 关闭输入流
    }
}

一、迭代部分

现在的代码和最早的版本比,主要在请求处理和类设计上做了不少优化。在添加内外部请求时,增加了楼层范围校验,遇到超出最大或最小楼层的无效请求会直接忽略,可过滤重复的同楼层或同方向请求,避免队列里堆积无效数据。处理外部请求时,现在用遍历的方式清除同楼层的所有相关请求,不像之前用复杂的子列表操作,减少了方向误判和多余移动。另外,移动时会在处理完当前楼层请求后再确定方向,逻辑更清晰。

二、类图

image

三、复杂度分析
image

分析

这个迭代过一次的代码,总共235行,有93条语句,分支语句占22.6%。注释占比37%,这点还挺不错,比之前的2.1%好的多多了,后面看代码也方便些。就两个类和接口,每个类平均 3 个方法,每个方法平均 13 条语句。最复杂的方法是addintreq().addextreq(),在44 行,复杂度才3,整体逻辑不算复杂。最深的块在171行,深度5了,这个得留意下,不过平均块深度1.13,多数代码块还是简单的。总之,注释这块做得可以,但块深度5的地方可以再优化下,让代码更清晰些。

第三次迭代

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

之前的好歹测试点还能全过呢,这次只能过一半了
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

// 定义电梯的运行方向
enum dir {
    UP, DOWN
}

// 乘客类
class pssngr {
    private final int srcflr;
    private final int destflr;

    pssngr(int srcflr, int destflr) {
        this.srcflr = srcflr;
        this.destflr = destflr;
    }

    int getsrcflr() {
        return srcflr;
    }

    int getdestflr() {
        return destflr;
    }
}

// 队列类
class reqq {
    private final List<Integer> intreqs;
    private final List<pssngr> extreqs;

    reqq() {
        this.intreqs = new ArrayList<>();
        this.extreqs = new ArrayList<>();
    }

    void addintreq(int floor) {
        if (!intreqs.contains(floor)) {
            intreqs.add(floor);
        }
    }

    void addextreq(pssngr passenger) {
        extreqs.add(passenger);
    }

    List<Integer> getintreqs() {
        return intreqs;
    }

    List<pssngr> getextreqs() {
        return extreqs;
    }

    boolean hasreqs() {
        return!intreqs.isEmpty() ||!extreqs.isEmpty();
    }
}

// 电梯类
class elv {
    private final int minflr;
    private final int maxflr;
    private int curflr;
    private dir curdir;
    private final reqq reqqueue;

    elv(int minflr, int maxflr) {
        this.minflr = minflr;
        this.maxflr = maxflr;
        this.curflr = minflr;
        this.curdir = dir.UP;
        this.reqqueue = new reqq();
    }

    void addintreq(int floor) {
        if (floor >= minflr && floor <= maxflr) {
            reqqueue.addintreq(floor);
        }
    }

    void addextreq(pssngr passenger) {
        int srcflr = passenger.getsrcflr();
        int destflr = passenger.getdestflr();
        if (srcflr >= minflr && srcflr <= maxflr && destflr >= minflr && destflr <= maxflr) {
            reqqueue.addextreq(passenger);
        }
    }

    // 移动逻辑
    void move() {
        if (!reqqueue.hasreqs()) return;

        int tgtflr = gettgtflr();

        // 根据目标楼层调整方向
        if (tgtflr < curflr) {
            curdir = dir.DOWN;
            curflr--;
        } else if (tgtflr > curflr) {
            curdir = dir.UP;
            curflr++;
        }

        System.out.println("Current Floor: " + curflr + " Direction: " + curdir);

        if (shouldstop()) {
            System.out.println("Open Door # Floor " + curflr);
            System.out.println("Close Door");
        }
    }

    private int gettgtflr() {
        if (!reqqueue.getintreqs().isEmpty()) {
            return reqqueue.getintreqs().get(0);
        } else if (!reqqueue.getextreqs().isEmpty()) {
            return reqqueue.getextreqs().get(0).getsrcflr();
        }
        return -1;
    }

    private boolean shouldstop() {
        // 检查是否有内部请求
        if (!reqqueue.getintreqs().isEmpty() && reqqueue.getintreqs().get(0) == curflr) {
            reqqueue.getintreqs().remove(0);
            return true;
        }

        // 检查是否有外部请求
        for (int i = reqqueue.getextreqs().size() - 1; i >= 0; i--) {
            pssngr passenger = reqqueue.getextreqs().get(i);
            if (passenger.getsrcflr() == curflr) {
                addintreq(passenger.getdestflr());
                reqqueue.getextreqs().remove(i);
                return true;
            }
        }
        return false;
    }

    void procreqs() {
        System.out.println("Current Floor: " + curflr + " Direction: " + curdir);
        while (reqqueue.hasreqs()) {
            move();
        }
    }
}

// 控制类
class elvctrl {
    private final elv elevator;

    elvctrl(elv elevator) {
        this.elevator = elevator;
    }

    void handleinput() {
        Scanner input = new Scanner(System.in);
        List<String> reqs = new ArrayList<>();
        String data;

        // 读取用户输入,直到输入"end"
        while (true) {
            data= input.next();
            if (data.equalsIgnoreCase("end")) {
                break;
            }
            reqs.add(data);
        }
        // 解析最小和最大楼层
        int minflr=Integer.parseInt(reqs.get(0));
        int maxflr=Integer.parseInt(reqs.get(1));
        elv elevator=new elv(minflr, maxflr);
        // 遍历所有请求
        for (int i = 2; i < reqs.size(); i++) {
            String req = reqs.get(i);
            if (req.contains(",")) { // 外部请求
                String[] parts = req.replaceAll("[<>]", "").split(",");
                int srcflr = Integer.parseInt(parts[0].trim());
                int destflr = Integer.parseInt(parts[1].trim());
                elevator.addextreq(new pssngr(srcflr, destflr));
            } else { // 内部请求
                int floor = Integer.parseInt(req.replaceAll("[<>]", "").trim());
                elevator.addintreq(floor);
            }
        }
        // 处理所有请求
        elevator.procreqs();
        input.close();
    }
}
// 主类
public class Main {
    public static void main(String[] args) {
        elv elevator = new elv(1, 10);
        elvctrl controller = new elvctrl(elevator);
        controller.handleinput();
    }
}

一、迭代部分

相较于之前的代码,此版本在类设计上有显著差异。新增了 pssngr 类,用于表示乘客并记录其起始楼层与目标楼层,使请求信息更加完整。引入reqq类对请求队列进行管理,将请求处理逻辑封装其中,增强了代码的模块化程度,提升了代码的可维护性与可读性。
在elv类中,请求添加方法增加了楼层范围校验,可有效避免无效请求进入系统。移动和停靠逻辑依据目标楼层调整方向,逻辑更加清晰,并且在电梯停靠时,会将外部请求中的目标楼层转换为内部请求进行处理。
此外,新添加的elvctrl类负责处理用户输入,将输入处理与电梯控制逻辑分离,使代码结构更加合理,进一步提高了代码的扩展性。

二、类图

image

三、复杂度分析

image

分析

第三次的代码总共 201 行,有 110 条语句,分支语句占 15.5%,注释占 8%,有一定注释但还能再丰富些。就 1 个类,平均下来有 19 个方法,每个方法平均 5.47 条语句。复杂度才 1,整体逻辑特别简单。最深的块在 131 行,深度到 3 了,不过平均块深度 1.01,绝大多数代码块都很简单。Kiviat 图看注释还有提升空间,其他复杂度指标都很低。总之,代码结构简单怪不得只过了一半的测试点,看来还是头脑太简单了,但注释可以再补补,那少量深块也看看能不能优化下,让代码更完美些。

心得

说实话,一步步改这三个电梯模拟代码,真的是摸着石头过河的过程。最开始写的版本,虽然把电梯运行的基本功能搭起来了,但太糙了 —— 啥请求都往里收,也不管合不合理,结果电梯运行老是出岔子,多跑楼层、方向乱转的问题一堆,现在回头看就是逻辑漏洞太多。后面最终提交的代码虽然能过测试点,但面对一些奇奇怪怪但又确实要考虑的情况就会出现问题,属于是从面向过程转到面向对象的领域有些水土不服了(虽然我C学得也不是特别好QAQ)。
到了第二个版本,牢记题目的要求,领导要我干啥我干啥。加了请求校验,把无效楼层和重复请求都筛掉,还重写了外部请求处理逻辑,用更直观的遍历方式替换之前绕来绕去的代码。这么一改,电梯运行总算符合基本规则了,但整体代码还是有点 “铁板一块”,功能都挤在一个类里,改起来牵一发而动全身。
最新这个版本,按照要求,谨遵我们Java界的圣旨————SRP,做了次大重构。拆分出乘客类、请求队列类和控制类,把不同功能独立封装,就像给代码划分了不同的 “车间”,各干各的活儿。这样一来,添加新功能或者改需求都方便多了。不过由于我这个拖延的b毛病,导致把第二次迭代的代码再匆匆忙忙地修改完功能,距离DDL还有一个点儿,面对着重新设计类的艰巨任务,拼尽全力无法战胜,最终提交了一个半成品,只能过一半的测试点。但事已至此,本人已深刻认识到拖延的坏处,尽管这篇Blog是在周日晚上写的,日后将痛定思痛,尽快完成组织上的任务

第一次让我踩了不少基础逻辑的坑,第二次教会我打磨细节的重要性,第三次则让我尝到了SRP设计的甜头。虽然每次交作业前都熬夜改 bug,但回头看,确实能明显感觉到自己从 “写代码能跑就行”,慢慢过渡到开始注重代码的质量和可维护性了。后面还得再啃啃硬骨头,争取下次作业能做得更漂亮。

总结

折腾了三次电梯调度程序的迭代,从一开始对着需求抓耳挠腮,到最后看着代码结构逐渐清爽,像是摸着石头过了一条不算浅的河。这过程里踩过的坑、悟到的道理,实实在在让我对 Java 编程有了新的认识。

第一次开发时,我将所有功能集中在单一电梯类中,导致代码耦合度极高,调度逻辑与状态管理混杂,修改一处便牵一发而动全身。随着需求迭代,遵循 “单一职责原则” 逐步拆分出乘客类(pssngr)、请求队列类(reqq)和控制类(elvctrl),让每个类专注于独立功能:
乘客类封装用户的起止楼层信息,使请求语义更清晰;
请求队列类负责管理内外请求的添加、过滤与调度,隔离了数据操作逻辑;
控制类专注于输入处理与流程控制,实现了人机交互与核心逻辑的解耦。
这种模块化设计让代码结构更清晰,后续扩展时只需聚焦特定类,维护成本大幅降低。

三次迭代中,对无效请求的处理逻辑逐步完善:
输入校验:通过参数检查过滤超出楼层范围的无效请求,避免电梯运行至非法楼层;
重复请求过滤:识别连续相同的内外部请求并去重,确保队列简洁高效;
边界条件处理:在电梯空闲状态、同方向请求处理完毕后的转向逻辑中,增加状态标记与条件判断,避免出现 “方向混乱” 或 “无限循环”。
这些细节的打磨表明程序的可靠性不仅在于核心功能的实现,更取决于对异常场景的覆盖能力,也是其健壮性的一个体现。

第一次大作业的经历让我深刻认识到,编程不仅是实现功能,更是对 “清晰”“高效”“可扩展” 的持续追求。每一次代码迭代都是一次自我审视:是否遵循设计原则?是否有冗余逻辑?能否让后来者更易理解?这些思考将伴随我在后续开发中,始终以 “写出经得起推敲的代码” 为目标,不断打磨技术细节,提升工程素养。
编程之路,道阻且长,唯有在实践中反复迭代、持续反思,才能让每一行代码都折射出思考的光芒。

一些小建议

个人感觉这三次的电梯调度程序的测试点都比较少,导致得分点相当集中,第一次最夸张,一个测试点独占70分,后面逐渐拆成了两个、四个测试点。还是希望能提供比较多的测试点、测试用例(二三十个也是可以接受的),这样不仅能让我们更好的理解题目需求以及程序的运行逻辑,我们能得的分也相对较高一点。不然被一两个测试点一棒子打死很痛苦

posted @ 2025-04-20 23:22  OnlyBlueeeeee  阅读(44)  评论(0)    收藏  举报