面向对象程序设计-题目集5~7总结

一、 前言:


第一次大作业的确是给我来了个下马威。这三次题目集的题量不大,在做前几题时感觉还不错,也没花多少时间。但是,开始着手最后的电梯题目的时候感觉不对劲了,花了几个小时但收效甚微。这三次电梯题以迭代的形式设置,下一次的题目要在上一次的代码结构上进行修改。总体上来看,电梯调度题运用了正则表达式的知识,因为是通过键盘模拟请求,格式上对于电梯内外请求会有所不同,所以通过正则表达式来处理分析用户对于电梯的请求。然后,使用了列表来存储这些请求,方便后期的增加和删除请求,定义一个方向的枚举类。虽然花了大量的时间去写这道题目,但是依然没有做出来,刚开始都有些道心崩溃了,第一次题目狗急跳墙借鉴了别人和Ai的源码过了测试点,后面我痛定思痛尽自己最大的努力去写,虽然也没写出来,但也锻炼了我的思维能力。

二、 设计与分析:


1. 第一次电梯题目:

程序要求:

按照特定的输入格式,输入楼层数和用户请求(串行处理乘客的请求方式,电梯只按照规则响应请求队列中当前的乘客请求,响应结束后再响应下一个请求),当输入end(不区分大小写)的时候结束输入,在电梯运行到目标楼层之前,输出经过的楼层和电梯运行方向,到达目标楼层,输出特定格式的“开门”和“关门”,然后继续处理下一请求。


设计的类图:

一个方向枚举类,Direction;请求类,Request;处理需求类,DualQueueElevator;主类,Main
image

代码分析:

(参考SourceMontor的生成报表内容)
image

参考报表内容,查询度量标准我发现:

  • 最大复杂度为29,平均复杂度为5.07。这说明我的代码中存在一些较为复杂的方法,但整体复杂度处于中等水平,不是很好。
  • 源码中包含四个类,每个类平均包含3.75个方法,方法数量适中。
  • 最复杂方法的行数为86行,最深块的行数为106行。这说明存在一些较长的方法和深层嵌套的代码块,可读性和可维护性不高。
  • 最大块深度为5,平均块深度为2.33。这表明代码中存在一些深层嵌套的代码块,但整体嵌套深度是适中的。
  • 分支语句占总语句的22.8%,方法调用语句为39句,还是在较为正常的范围内。
  • 注释占比为0.0,这表明代码中没有注释,会影响代码的可读性和可维护性。
  • 再观察Kiviat图,能够很直观的显示各项指标的分布情况,现在看来只有少部分指标处于绿色圆环内,代码质量不高。直方图显示,2深度块中的语句数量最多,1,3次之。

心得体会:

一通分析下来,我认为有两点是印象深刻且现在可以着手解决的:一是代码的最大复杂度,来到了29,约为平均复杂度的六倍,是代码中频繁使用if/else的嵌套导致。二是注释的编写,注释占比低至0.0,没有写一句注释。这样的结果就是,过几天再来看代码,很难快速的理清代码的逻辑,不知道自己写的是什么。


2.第二次电梯题目:

程序要求:

主体要求不变,新增用户请求不合理情况:如重复输入同一楼层请求<3><3><3>,但只处理一次<3>请求。


设计的类图:

image
与题目所给的类图基本一致。

代码分析:

(参考SourceMontor的生成报表内容)
image
参考报表内容,

  • 语句数 (Statements): 158,代码中总共有158条语句。
  • 分支语句百分比 (Percent Branch Statements): 16.5%
  • 代码中有16.5%的语句是分支语句(如if、switch等)。这表明代码中的分支逻辑不多,这有助于减少代码的复杂性。
  • 方法调用语句数 (Method Call Statements): 58,代码中有58条方法调用语句。表明代码模块化程度较高,但需确认是否存在过度依赖外部方法的情况。
  • 注释行百分比 (Percent Lines with Comments): 7.7%,代码中有7.7%的行包含注释。注释较少,可能会影响代码的可读性。
  • 类和接口数 (Classes and Interfaces): 7,代码中包含7个类或接口。和总行数233行相比,这表明项目的代码结构较为复杂,有多个不同的功能模块。
  • 每个类的方法数 (Methods per Class): 5.29,平均每个类包含5.29个方法。这表明方法数量适中,但需要注意有没有哪个类的方法过多,功能过于集中。
  • 每个方法的语句数 (Average Statements per Method): 2.89,平均每个方法包含2.89条语句。符合单一职责原则,但需确认是否存在过度碎片化(多个简单方法)。
  • 最复杂方法的行数 (Line Number of Most Complex Method): 193,最复杂的方法是Main.main(),它在193行。这是程序的入口方法,可能包含了过多的逻辑处理,复杂度变得很高,不利于后期的维护,应进行优化,降低其复杂度。
  • 最大复杂度 (Maximum Complexity): 10;平均复杂度 (Average Complexity): 1.78
    最大复杂度为10,平均复杂度为1.78,表明代码整体复杂度较低,但存在一些复杂度较高的方法(如Main.main())存在高嵌套或复杂条件逻辑,需优先重构以降低维护成本。
  • 最深层块的行数 (Line Number of Deepest Block): 209
    最深层块有209行。
  • 最大块深度 (Maximum Block Depth): 5
    最大块深度为5。这表明代码中有略深的嵌套结构,可能增加代码的理解和维护难度。
  • 平均块深度 (Average Block Depth): 1.68
    平均块深度为1.68。表明代码中存在一定程度的嵌套,但整体上比较好控制。

心得体会:

代码结构和第一次相比有比较大的变化,分支语句占比明显减少,最大复杂度同比也大幅下降。可能是因为题目给出了类图,根据设计来编写代码确实会对代码的质量有个很大的提升。所以,现在从面向过程的编码转向面向对象的编码,流程上有很大不同,更加要注重设计规划,此外,代码的注释占比依然低下,不利于代码的可读与维护,要养成多写注释的习惯。但是,代码的逻辑上仍有较大的错误,输出结果与预期也很不相同,image
但这次留给我的时间并不充裕逻辑变得有些混乱了,并没有通过这次题目,但值得肯定的是转变了思想,锻炼了思维。


3.第三次电梯题目:

程序要求:

电梯运行规则与前阶段相同,但有如下变动情况:
乘客请求输入变动情况:外部请求由之前的<请求楼层数,请求方向>修改为<请求源楼层,请求目的楼层>
对于外部请求,当电梯处理该请求之后(该请求出队),要将<请求源楼层,请求目的楼层>中的请求目的楼层加入到请求内部队列(加到队尾)。


设计类图:

题目所给的类图:
image
最后参考类图实际设计出来的类图:
image
二者相比,发现,实际操作时我还是修改了一些内容,主要是增加了getTargetFloor()和findClosestTarget()方法,想着获取一个目标楼层,在到目标楼层之前输出电梯日志的逻辑,然后又增加了判断请求队列是不是为空的方法isEmpty()。

代码分析:

(参考SourceMontor的生成报表内容)

image

  • 行数(Lines):333,文件有333行代码,在这个题目里是比较大的规模。
  • 语句数 (Statements): 195,代码中总共有195条语句。数值适中,但需结合功能判断是否冗余或缺失核心逻辑。
  • 分支语句百分比 (Percent Branch Statements): 16.4%,代码中有16.4%的语句是分支语句(如if、switch等)。这表明代码中的分支逻辑不多,这有助于减少代码的复杂性。
  • 方法调用语句数 (Method Call Statements): 77,代码中有77条方法调用语句。表明代码模块化程度较高,但需确认是否存在过度依赖外部方法的情况。
  • 注释行百分比 (Percent Lines with Comments): 0.6%,代码中有0.6%的行包含注释。注释极少,极大可能会影响代码的可读性。
  • 类和接口数 (Classes and Interfaces): 7,代码中包含7个类或接口。这表明项目的代码结构较为复杂,有多个不同的功能模块。
  • 每个类的方法数 (Methods per Class): 6.00,平均每个类包含6.00个方法。数值较高说明类承担了多种功能,但如果过高可能违反单一职责原则,导致类的功能过于复杂;数值低则可能表示类的功能过于单一。这表明方法数量适中,但需要注意有没有哪个类的方法过多,功能过于集中。
  • 每个方法的语句数 (Average Statements per Method): 3.10,平均每个方法包含3.10条语句。符合单一职责原则,但需确认是否存在过度碎片化(多个简单方法)。
  • 最复杂方法的行数 (Line Number of Most Complex Method): 188,可能存在多层条件分支或循环,需拆分为子方法或使用设计模式优化
  • 最复杂的方法是Controller.determineDirection(),它在188行。这是个确定方向的方法,可能嵌套了过多的条件判断语句,导致其复杂度大幅上升。
  • 最大复杂度 (Maximum Complexity): 12,数值(>10),过于复杂,应该优先考虑重构,以降低后期维护成本。
  • 平均复杂度 (Average Complexity): 1.95,整体代码的平均圈复杂度。数值低(<2),说明大部分方法逻辑简单,但局部高复杂度方法需重点关注
  • 最深层块的行数 (Line Number of Deepest Block): 253,最深层块在209行。
  • 最大块深度 (Maximum Block Depth): 5,最深代码块嵌套层数(第253行)。深度≥5时,可读性和维护性显著下降,建议拆分。
  • 平均块深度 (Average Block Depth): 1.68-,平均嵌套深度。数值合理,但需检查局部深度异常部分(如深度≥4的代码块)。

心得体会:

代码结构,有些复杂,圈复杂度太高(12),这导致逻辑并不清晰。运行并不能满足普遍需求,虽然能运行特定测试样例,但并不能通过测试点。image
image
不能只朝着一个结果去编程,要具有普遍性,要不然在编译器里,为了过这一个样例调式几个小时都是徒劳。代码的注释在这次编码中又没有体现,这是一个很大的问题,要尽快改正。注意逻辑上的可行性,广度和深度。


三、踩坑心得:


  1. 在变量较多时,采用驼峰命名法,这样做有利于编码时变量名编写的正确率。要不然在代码行数较多时,很容易就会编写错误,虽然在编译器里会有所提示,但有助于代码的美观和规范。大小写的差异也会引发错误!image
  2. 注意逻辑的可行性和逻辑能否退出。在写作业的过程中,我遇到的最大问题就是非零返回和运行超时。image
    非零返回,大概率是因为我的逻辑自身有问题,有个很典型的例子就是,列表越界会抛出ArrayIndexOutOfBoundsException异常。而运行超时就很显而易见了,我最开始以为是在我的某个while循环或者for循环里判断条件出了问题,但是在eclipse里运行,发现它是在两个或者三个楼层之间循环运行输出。想了很久,最后发现是在删除请求的方法上出了问题,删除逻辑不够完善,导致有些请求没有删除,不能退出程序。
  3. 在编码前提前设计,不设计就直接编码很容易导致逻辑错误,东补西凑出的逻辑实在不耐打。后两次题目花了很多时间,但得不到分。

四、改进建议:

点击查看代码
import java.util.*;
import java.util.LinkedList;
import java.util.List;

enum Direction {
    UP,
    DOWN,
    IDLE
}

enum State {
    MOVING,
    STOPPED
}

class Elevator {
    private int currentFloor;
    private Direction direction = Direction.UP;
    private State state = State.STOPPED;
    private final int maxFloor;
    private final int minFloor;

    public Elevator(int minFloor, int maxFloor) {
        this.minFloor = minFloor;
        this.maxFloor = maxFloor;
        this.currentFloor = 0;
    }

    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 Passenger {
    private Integer sourceFloor;
    private Integer destinationFloor;

    public Passenger() {
    }

    public Passenger(Integer sourceFloor, Integer destinationFloor) {
        this.sourceFloor = sourceFloor;
        this.destinationFloor = destinationFloor;
    }

    public Passenger(Integer destinationFloor) {
        this.destinationFloor = destinationFloor;
    }

    public Integer getSourceFloor() {
        return sourceFloor;
    }

    public void setSourceFloor(Integer sourceFloor) {
        this.sourceFloor = sourceFloor;
    }

    public Integer getDestinationFloor() {
        return destinationFloor;
    }

    public void setDestinationFloor(Integer destinationFloor) {
        this.destinationFloor = destinationFloor;
    }
}

class RequestQueue {
    private LinkedList<Passenger> internalRequests = new LinkedList<>();
    private LinkedList<Passenger> externalRequests = new LinkedList<>();

    public RequestQueue() {
    }

    public static RequestQueue getQueueInstance() {
        return new RequestQueue();
    }

    public LinkedList<Passenger> getInternalRequests() {
        return internalRequests;
    }

    public void setInternalRequests(LinkedList<Passenger> internalRequests) {
        this.internalRequests = internalRequests;
    }

    public LinkedList<Passenger> getExternalRequests() {
        return externalRequests;
    }

    public void setExternalRequests(LinkedList<Passenger> externalRequests) {
        this.externalRequests = externalRequests;
    }

    public void addInternalRequest(Passenger passenger) {
        internalRequests.add(passenger);
    }

    public void addExternalRequest(Passenger passenger) {
        externalRequests.add(passenger);
    }

    public boolean isEmpty() {
        return internalRequests.isEmpty() && externalRequests.isEmpty();
    }
}

class Controller {
    private Elevator elevator;
    private RequestQueue queue;
    private Integer firstExternalTarget = null;
    private boolean isTargetFromExternal = false;

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

    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.isEmpty()) {
            determineDirection();
            int nextFloor = getNextFloor();

            if (!elevator.isValidFloor(nextFloor)) {
                elevator.setDirection(elevator.getDirection() == Direction.UP ? Direction.DOWN : Direction.UP);
                continue;
            }

            elevator.setCurrentFloor(nextFloor);
            move();
        }
    }

    private void determineDirection() {
        int current = elevator.getCurrentFloor();
        boolean hasUp = false, hasDown = false;

        for (Passenger p : queue.getInternalRequests()) {
            int dest = p.getDestinationFloor();
            hasUp |= dest > current;
            hasDown |= dest < current;
        }

        for (Passenger p : queue.getExternalRequests()) {
            int src = p.getSourceFloor();
            hasUp |= src > current;
            hasDown |= src < current;
        }

        Direction currentDir = elevator.getDirection();
        if (currentDir == Direction.UP) {
            elevator.setDirection(hasUp ? Direction.UP : hasDown ? Direction.DOWN : Direction.IDLE);
        } else if (currentDir == Direction.DOWN) {
            elevator.setDirection(hasDown ? Direction.DOWN : hasUp ? Direction.UP : Direction.IDLE);
        } else {
            elevator.setDirection(hasUp ? Direction.UP : hasDown ? Direction.DOWN : Direction.IDLE);
        }
    }

    private void move() {
        int currentFloor = elevator.getCurrentFloor();
        System.out.printf("Current Floor: %d Direction: %s%n", currentFloor, elevator.getDirection());

        if (shouldStop(currentFloor)) {
            openDoors();
            removeRequests(currentFloor);
        }
    }

    private boolean shouldStop(int floor) {
        return getTargetFloor() == floor;
    }

    private int getTargetFloor() {
        if (elevator.getState() == State.STOPPED && !queue.getExternalRequests().isEmpty()) {
            firstExternalTarget = queue.getExternalRequests().getFirst().getSourceFloor();
            isTargetFromExternal = true;
            elevator.setState(State.MOVING);
            return firstExternalTarget;
        }

        if (!queue.getInternalRequests().isEmpty()) {
            return findClosestTarget();
        }

        elevator.setState(State.STOPPED);
        return elevator.getCurrentFloor();
    }

    private int findClosestTarget() {
        int current = elevator.getCurrentFloor();
        Direction dir = elevator.getDirection();

        if (dir == Direction.UP) {
            int closest = Integer.MAX_VALUE;
            for (Passenger p : queue.getInternalRequests()) {
                int dest = p.getDestinationFloor();
                if (dest >= current && dest < closest) {
                    closest = dest;
                }
            }
            return closest != Integer.MAX_VALUE ? closest : current;
        } else {
            int closest = Integer.MIN_VALUE;
            for (Passenger p : queue.getInternalRequests()) {
                int dest = p.getDestinationFloor();
                if (dest <= current && dest > closest) {
                    closest = dest;
                }
            }
            return closest != Integer.MIN_VALUE ? closest : current;
        }
    }

    private void removeRequests(int currentFloor) {
        if (isTargetFromExternal) {
            List<Passenger> toRemove = new ArrayList<>();
            for (Passenger p : queue.getExternalRequests()) {
                if (p.getSourceFloor().equals(currentFloor)) {
                    toRemove.add(p);
                    queue.addInternalRequest(new Passenger(p.getDestinationFloor()));
                }
            }
            queue.getExternalRequests().removeAll(toRemove);
            firstExternalTarget = null;
            isTargetFromExternal = false;
        } else {
            queue.getInternalRequests().removeIf(p -> 
                p.getDestinationFloor().equals(currentFloor));
        }
       // openDoors();
    }

    private void openDoors() {
        System.out.printf("Open Door # Floor %d%n", elevator.getCurrentFloor());
        System.out.println("Close Door");
    }

    private int getNextFloor() {
        switch (elevator.getDirection()) {
            case UP: return elevator.getCurrentFloor() + 1;
            case DOWN: return elevator.getCurrentFloor() - 1;
            default: return elevator.getCurrentFloor();
        }
    }
}

public class Main {
    public static void main(String[] args) {
        Scanner scanner = new Scanner(System.in);
        int minFloor = scanner.nextInt();
        int maxFloor = scanner.nextInt();
        scanner.nextLine();

        Elevator elevator = new Elevator(minFloor, maxFloor);
        RequestQueue queue = new RequestQueue();
        Controller controller = new Controller(elevator, queue);

        while (scanner.hasNextLine()) {
            String line = scanner.nextLine().trim();
            if (line.equalsIgnoreCase("END")) break;

            if (line.startsWith("<") && line.endsWith(">")) {
                String content = line.substring(1, line.length() - 1);
                if (content.contains(",")) {
                    String[] parts = content.split(",");
                    int src = Integer.parseInt(parts[0].trim());
                    int dest = Integer.parseInt(parts[1].trim());
                    queue.addExternalRequest(new Passenger(src, dest));
                } else {
                    int dest = Integer.parseInt(content.trim());
                    queue.addInternalRequest(new Passenger(dest));
                }
            }
        }
        controller.processRequests();
        scanner.close();
    }
}
1. 方法拆分与复杂度优化

Controller 类的 determineDirection() 和 findClosestTarget() 方法圈复杂度高(分别为12和5),嵌套层级深。简化 determineDirection() 中的多层嵌套条件,把复杂度降下来,也有利于理清逻辑。

  1. Controller 类职责过重,应进行结构上的优化
    选择认真研习LOOK算法在代码上的具体实现,优化运行逻辑。我的代码实际上并不是真正的LOOK算法,没有做到它最核心最关键的技术。

  2. 明确单一职责,做好方法间的调用
    在我设计代码的过程中,在getNextFloor()方法中,开始想的过于简单,只是单纯的加或减楼层,后来在这个方法里加入了findClosestTarget()方法,这两个方法结合在一起。但在调用过程中,可能是由于逻辑问题,导致程序异常循环或者提前退出。后来,还是把两者拆分开。

  3. 重视注释的重要性,学会写注释和规范命名
    目前代码注释稀少,应在关键方法、复杂逻辑处添加注释。比如 determineDirection 方法逻辑复杂,可添加注释说明算法思路,帮助理解代码意图。部分变量命名可更具描述性,如 firstExternalTarget 可改为 nextExternalSourceFloor 等,更清晰表达变量含义。


总结:


得:在接触了这种可迭代式的作业后,深刻感受到了设计的重要性,将思想由面向过程转变为面向对象编程。学会了使用正则表达式,它使用单个字符串来描述、匹配一系列符合某个句法规则的字符串,能够很好的解析输入的字符串。在代码调试过程中,学会了选择优质的关键断点,直击问题所在。此外,在经历了三次作业的锻炼后,我的逻辑思维较以往有了很大的提升。
不足:三次题目实际上并没有得到分。特别是第一次被查重,这给我敲醒了警钟,锤炼自身才是当下最重要的。后两次题目,尽了自己最大努力,也算无怨无悔。究其原因,对于电梯算法(像LOOK)不熟悉,逻辑不清晰,后续学习过程中还是需要训练自己的思维能力,研习解决当下问题的算法(如果有,需要这样的算法来优化代码)。最后,学习设计可持续,维护成本低的代码,在设计结构上下一定功夫。

少许建议:

1.增加测试点
特别是第一次作业,这一次作业我认为是非常关键的,多几个测试点也能帮助我判断代码逻辑是全错还是部分正确。
2.第一次作业结束后,解释核心代码逻辑
作为迭代式作业,第一次没写出来,后期的题目也很难写出来。很有可能,有些人的逻辑已经往另一个方向去了,虽然这样的逻辑可以通过一个样例,但也只限于这一个。然后,用别的样例测试发现错误,在自己认为正确的逻辑上很难找出漏洞,最后就很容易把逻辑弄混乱。

posted @ 2025-04-20 13:09  該赱了罢  阅读(56)  评论(0)    收藏  举报