题目集5~7的总结性Blog

一、前言

在题目集5~7里,通过迭代的方式实现了单部电梯的调度,全方面考察了学生的多方面能力和知识点。对类的设计与抽象,思考类与类之间的关系很好的锻炼了面向对象的编程思想,队列的运用以及电梯调度算法的设计加强了算法的应用的能力,通过需求迭代考验学生理解需求变化、处理边界情况则极大地培养了问题分析与解决能力,最后,还注重学生代码规范的遵循以及调试能力的运用 ,以此全面提升学生的编程综合素养。

题目集05要求实现电梯调度的基本要求,使用look算法,队列知识以及正则表达式等,题目集06在题目集05的基础上进行迭代,为解决电梯职责过多的问题,要求使用单一职责原则进行类设计与划分,而题目集07在题目集06的基础上再一次迭代,取消了乘客请求类,加入了乘客类,添加了对于外部请求,当电梯处理该请求之后(该请求出队),要将<请求源楼层,请求目的楼层>中的请求目的楼层加入到请求内部队列(加到队尾)这一新规则。三次题目集迭代的方式使电梯调度更完善,难度也在逐渐递增。

二:设计与分析

1.第一次作业(题目集05)

题目要求:

题目集05的要求是设计一个电梯类,包含状态管理(每次运行过程中电梯的状态:上行、下行、停止)、请求队列管理(分为内部队列和外部队列,分别对应电梯内部的请求和电梯外部的请求)以及调度算法,并使用一些测试用例,模拟不同的请求顺序,观察电梯的行为是否符合预期,比如是否优先处理同方向的请求,是否在移动过程中处理顺路的请求,是否能够处理停靠请求,同时要考虑处理无效请求情况和无请求情况等。为考虑编程难度,采用串行处理乘客的请求方式(电梯只按照规则响应请求队列中当前的乘客请求,响应结束后再响应下一个请求)。

类图:

 

 类的设计思路:

方向枚举类:使用 <<Enum>> 构造型定义了 Direction 枚举类,包含 UP(向上 )、DOWN(向下 )、IDLE(空闲 )三个枚举常量,用于明确电梯运行方向相关状态。

Elevator类:

  • isValid(int floor):判断传入楼层是否在电梯可运行楼层范围内,确保操作合法性。
  • run():电梯运行的核心方法,负责协调处理内外部请求,控制电梯运行逻辑。
  • getTargetFloor(Integer inner, Elevator.ExternalRequest outer):根据内外部请求确定电梯要前往的目标楼层。
  • moveElevator():实现电梯移动的具体逻辑。
  • handleStop():处理电梯到达目标楼层时的停靠逻辑,如开门、处理乘客进出等。

外部请求类:ExternalRequest记录请求楼层和方向信息,方便电梯类处理外部请求。

复杂度分析:

Metrics Details For File 'Main.java'
--------------------------------------------------------------------------------------------

Parameter                Value
=========                =====
Project Directory            D:\develop\eclipse\elevator1\src\elevator1\
Project Name                elevator1
Checkpoint Name                Baseline
File Name                Main.java
Lines                    177
Statements                112
Percent Branch Statements        25.9
Method Call Statements            45
Percent Lines with Comments        0.0
Classes and Interfaces            4
Methods per Class            2.50
Average Statements per Method        8.60
Line Number of Most Complex Method    78
Name of Most Complex Method        Elevator.getTargetFloor()
Maximum Complexity            24
Line Number of Deepest Block        160
Maximum Block Depth            6
Average Block Depth            2.84
Average Complexity            5.50

--------------------------------------------------------------------------------------------
Most Complex Methods in 3 Class(es):    Complexity, Statements, Max Depth, Calls

Elevator.addInner()            2, 2, 3, 2
Elevator.addOuter()            2, 2, 3, 2
Elevator.Elevator()            1, 4, 2, 0
Elevator.getTargetFloor()        24, 31, 5, 6
Elevator.handleStop()            5, 6, 3, 8
Elevator.isValidFloor()            2, 1, 2, 0
Elevator.moveElevator()            3, 5, 3, 1
Elevator.run()                6, 9, 4, 7
ExternalRequest.ExternalRequest()    1, 2, 3, 0
Main.main()                9, 24, 6, 17

--------------------------------------------------------------------------------------------

 

主要问题:

1. 可以看出Avg Complexity、Avg Depth比较高,这反映在代码中控制结构嵌套深:在 Elevator.getTargetFloor() 和 Main.main() 方法中,存在多层嵌套的 if - else 语句,导致代码的最大深度较大。例如在 Elevator.getTargetFloor() 中,多层嵌套的条件判断使得代码的逻辑深度增加。

2.从代码度量可知,Elevator.getTargetFloor() 复杂度最高为 24,该方法中存在大量的 if - else 嵌套,逻辑分支众多。在处理内部请求和外部请求的组合情况时,嵌套了多层条件判断,使得代码难以理解和维护,且过多的逻辑判断使得代码结构混乱不利于代码的维护。Main.main() 复杂度为 9,作为程序的入口方法,承担了过多的功能,包括读取用户输入、解析输入、创建电梯对象和启动电梯运行等。这违背了单一职责原则,使得代码难以理解和测试,且代码冗长,包含了复杂的输入处理逻辑,如对输入字符串的解析和异常处理,使得代码行数较多,不利于代码的维护和扩展。

3.雷达图显示注释百分比为 0.0,说明代码中缺乏必要的注释。代码中没有注释来解释关键逻辑和功能,很难理解代码的实现细节,对于进一步的完善加大了难度。

改进心得:

第一次题目集没有了解透彻,只是将实现的功能全部集中到一个类里,导致方法的混乱,过多的使用了循环嵌套,结构变得复杂。因此我期望在后续的代码里可以改进以下方面。

1.拆分复杂方法:将 Elevator.getTargetFloor() 和 Main.main() 等复杂方法拆分成多个小方法,每个小方法只负责一个明确的功能,提高代码的可读性和可维护性。

2.降低控制结构嵌套深度:通过优化逻辑,减少 if - else 语句的嵌套,使代码结构更加清晰。

 

2.第二次作业(题目集06)

题目要求:

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

 

电梯运行规则与前阶段单类设计相同,但要处理如下情况:

    • 乘客请求楼层数有误,具体为高于最高楼层数或低于最低楼层数,处理方法:程序自动忽略此类输入,继续执行
    • 乘客请求不合理,具体为输入时出现连续的相同请求,例如<3><3><3>或者<5,DOWN><5,DOWN>,处理方法:程序自动忽略相同的多余输入,继续执行,例如<3><3><3>过滤为<3>

类图:

 

 

类的设计思路:

Elevator类

  • 属性:通过 currentFloordirectionstatemaxFloorminFloor 记录电梯当前楼层、运行方向、状态、可到达的最大和最小楼层。
  • 方法:isValidFloor 用于判断目标楼层是否在合法范围内;moveUp 和 moveDown 实现电梯的上下移动逻辑,并在移动时更新状态。体现了对电梯基本状态管理和操作的封装 。

 RequestQueue(请求队列类)

  • 属性:使用三个 LinkedList 分别存储内部请求(internalRequests )、外部请求(externalRequests )和所有请求(allRequests ),方便管理不同类型的电梯请求。
  • 方法:addInternalRequest 和 addExternalRequest 用于添加内外部请求,同时更新所有请求列表;isEmpty 用于判断请求队列是否为空;各种 remove 方法用于根据楼层移除相应请求。实现了对电梯请求的组织和管理 。

Controller类

  • 属性:Elevator 和 RequestQueue ,用于协调电梯和请求队列之间的交互。
  • 方法:processRequests 是核心方法,通过循环处理请求队列中的请求,调用其他方法完成方向确定、移动电梯、判断是否停靠等操作;determineDirection 根据请求和电梯当前方向确定运行方向;move 控制电梯移动到目标楼层;shouldStop 判断电梯是否应在当前楼层停靠;getNextFloor 确定下一个要到达的目标楼层;getFloorFromRequest 从请求对象中获取楼层信息;getClosest 用于在两个楼层中选择离当前楼层更近的;openDoors 处理电梯到达楼层时开门关门及状态更新。

ExternalRequest类

  • 属性:floor 和 direction 记录外部请求的楼层和方向。
  • 方法:构造方法用于初始化属性,getter 方法用于获取属性值,用于封装外部请求的信息 。

State类:包含 MOVING 和 STOPPED 两个枚举值,用于表示电梯的运行状态,使电梯状态管理更清晰、规范 。

较上次添加了如上的类,保留了Direction枚举类

复杂度分析:

Metrics Details For File 'elevator2.java'
--------------------------------------------------------------------------------------------                                 

Parameter                Value
=========                =====
Project Directory            D:\电梯代码\elevator2\
Project Name                elevator2
Checkpoint Name                Baseline
File Name                elevator2.java
Lines                    407
Statements                238
Percent Branch Statements        20.6
Method Call Statements            91
Percent Lines with Comments        0.0
Classes and Interfaces            7
Methods per Class            6.43
Average Statements per Method        3.82
Line Number of Most Complex Method    205
Name of Most Complex Method        Controller.determineDirection()
Maximum Complexity            18
Line Number of Deepest Block        286
Maximum Block Depth            6
Average Block Depth            2.35
Average Complexity            2.47

--------------------------------------------------------------------------------------------
Most Complex Methods in 5 Class(es):    Complexity, Statements, Max Depth, Calls

Controller.Controller()            1, 2, 2, 0
Controller.Controller()            1, 0, 0, 0
Controller.determineDirection()        18, 23, 5, 9
Controller.getClosest()            3, 8, 2, 3
Controller.getElevator()        1, 1, 2, 0
Controller.getFloorFromRequest()    1, 1, 2, 1
Controller.getNextFloor()        16, 16, 6, 7
Controller.getQueue()            1, 1, 2, 0
Controller.move()            6, 11, 5, 8
Controller.openDoors()            1, 4, 2, 4
Controller.processRequests()        4, 10, 4, 10
Controller.setElevator()        1, 1, 2, 0
Controller.setQueue()            1, 1, 2, 0
Controller.shouldStop()            5, 7, 4, 9
Elevator.Elevator()            1, 5, 2, 0
Elevator.Elevator()            1, 0, 0, 0
Elevator.getCurrentFloor()        1, 1, 2, 0
Elevator.getDirection()            1, 1, 2, 0
Elevator.getElevatorInstance()        1, 1, 2, 0
Elevator.getMaxFloor()            1, 1, 2, 0
Elevator.getMinFloor()            1, 1, 2, 0
Elevator.getState()            1, 1, 2, 0
Elevator.isValidFloor()            2, 1, 2, 0
Elevator.moveDown()            2, 3, 3, 0
Elevator.moveUp()            2, 3, 3, 0
Elevator.setCurrentFloor()        1, 1, 2, 0
Elevator.setDirection()            1, 1, 2, 0
Elevator.setState()            1, 1, 2, 0
ExternalRequest.ExternalRequest()    1, 2, 2, 0
ExternalRequest.getDirection()        1, 1, 2, 0
ExternalRequest.getFloor()        1, 1, 2, 0
Main.main()                9, 31, 5, 20
RequestQueue.addExternalRequest()    1, 3, 2, 2
RequestQueue.addInternalRequest()    1, 2, 2, 2
RequestQueue.getAllRequests()        1, 1, 2, 0
RequestQueue.getExternalRequests()    1, 1, 2, 0
RequestQueue.getInternalRequests()    1, 1, 2, 0
RequestQueue.getQueueInstance()        1, 1, 2, 0
RequestQueue.isEmpty()            1, 1, 2, 1
RequestQueue.removeAllRequest()        6, 10, 5, 5
RequestQueue.removeExternalRequest()    3, 4, 4, 4
RequestQueue.removeInternalRequest()    3, 4, 4, 3
RequestQueue.RequestQueue()        1, 0, 0, 0
RequestQueue.setExternalRequests()    1, 1, 2, 0
RequestQueue.setInternalRequests()    1, 1, 2, 0

--------------------------------------------------------------------------------------------

主要问题:

1.方法复杂度高:Controller.determineDirection() 复杂度达 18,Controller.getNextFloor() 复杂度为 16 ,Main.main() 复杂度为 9 。高复杂度方法内逻辑复杂,包含大量条件判断和嵌套结构,可读性差,维护困难。修改时易引入新问题,且难以调试。

2.代码结构嵌套深:最大深度 6 ,部分方法如 Controller.determineDirection() 最大深度 5 ,Controller.getNextFloor() 最大深度 6 。深层嵌套使代码逻辑难以理解和追踪,增加理解和修改代码的难度。

3.同样缺乏注释

相比较第一次的改进点:

1.在方法拆分或逻辑简化上有一定改进,使得部分方法复杂度相对合理(多数方法复杂度为 1 )。

2.结构更清晰:之前代码结构混乱,此次类与类之间职责相对明确,如 Elevator 类负责电梯状态管理,RequestQueue 类负责请求管理,Controller 类负责协调控制,整体结构上有所优化。

改进心得:

1.对一些复杂方法再一次进行拆分:对 Controller.determineDirection()Controller.getNextFloor() 等高复杂度方法,按功能拆分为多个小方法。如 Controller.determineDirection() 中判断同方向和反方向请求的逻辑可独立成方法,提高代码可读性和可维护性。

2.优化代码结构:减少不必要的嵌套,通过重构逻辑使代码结构更扁平。如在条件判断和循环嵌套处,思考能否通过调整逻辑顺序或使用更合适的数据结构来避免深层嵌套。

 

3.第三次作业(题目集07)

题目要求:

对之前电梯调度程序再次进行迭代性设计,加入乘客类(Passenger),取消乘客请求类,类设计要求遵循单一职责原则(SRP),要求必须包含但不限于设计电梯类、乘客类、队列类以及控制类,具体设计可参考如下类图。

电梯运行规则与前阶段相同,但有如下变动情况:

    • 乘客请求输入变动情况:外部请求由之前的<请求楼层数,请求方向>修改为<请求源楼层,请求目的楼层>
    • 对于外部请求,当电梯处理该请求之后(该请求出队),要将<请求源楼层,请求目的楼层>中的请求目的楼层加入到请求内部队列(加到队尾)

类图:

 

类的设计思路:

 Passenger类

  • 属性:通过 sourceFloor(起始楼层 )和 destinationFloor(目标楼层 )记录乘客的请求信息。
  • 方法:多个构造方法用于根据不同参数创建乘客请求对象,getter 和 setter 方法用于获取和设置乘客的起始楼层和目标楼层,实现了对乘客请求信息的封装。

控制器属性:在 Controller 类中新增 currentExternalRequest : Passenger 属性,用于记录当前外部请求的乘客信息。这使得控制器对外部请求的管理更加细化和精准,在处理外部请求逻辑时,能更方便地获取和操作相关乘客信息。

保留了第二次的电梯类,请求对列类,控制器类。

复杂度分析:

Metrics Details For File  'elevator3.java'
--------------------------------------------------------------------------------------------

Parameter               Value
=========               =====
Project Directory           D:\电梯代码\elevator3\
Project Name                elevator3
Checkpoint Name             Baseline
File Name               elevator3.java
Lines                   493
Statements              293
Percent Branch Statements       24.6
Method Call Statements          135
Percent Lines with Comments     0.0
Classes and Interfaces          7
Methods per Class           6.43
Average Statements per Method       4.98
Line Number of Most Complex Method  204
Name of Most Complex Method     Controller.getNextFloor()
Maximum Complexity          44
Line Number of Deepest Block        269
Maximum Block Depth         7
Average Block Depth         2.78
Average Complexity          3.11

--------------------------------------------------------------------------------------------
Most Complex Methods  in 5 Class(es):    Complexity, Statements, Max Depth, Calls

Controller.Controller()         1, 3, 2, 0
Controller.Controller()         1, 3, 2, 0
Controller.determineDirection()     6, 11, 4, 6
Controller.findExternalRequest()    4, 6, 4, 5
Controller.findInitialNextFloor()   8, 16, 4, 6
Controller.getElevator()        1, 1, 2, 0
Controller.getNextFloor()       44, 68, 7, 47
Controller.getQueue()           1, 1, 2, 0
Controller.move()           5, 15, 5, 7
Controller.openDoors()          2, 4, 3, 4
Controller.processRequests()        6, 10, 4, 11
Controller.removeRequests()     1, 1, 2, 2
Controller.setElevator()        1, 1, 2, 0
Controller.setQueue()           1, 1, 2, 0
Controller.shouldStop()         8, 7, 4, 8
Elevator.Elevator()         1, 3, 2, 0
Elevator.Elevator()         1, 0, 0, 0
Elevator.getCurrentFloor()      1, 1, 2, 0
Elevator.getDirection()         1, 1, 2, 0
Elevator.getElevatorInstance()      1, 1, 2, 0
Elevator.getMaxFloor()          1, 1, 2, 0
Elevator.getMinFloor()          1, 1, 2, 0
Elevator.getState()         1, 1, 2, 0
Elevator.isValidFloor()         2, 1, 2, 0
Elevator.moveDown()         2, 3, 3, 0
Elevator.moveUp()           2, 3, 3, 0
Elevator.setCurrentFloor()      1, 1, 2, 0
Elevator.setDirection()         1, 1, 2, 0
Elevator.setState()         1, 1, 2, 0
Main.main()             14, 37, 6, 35
Passenger.getDestinationFloor()     1, 1, 2, 0
Passenger.getDirection()        5, 6, 4, 0
Passenger.getSourceFloor()      1, 1, 2, 0
Passenger.Passenger()           1, 2, 2, 0
Passenger.Passenger()           1, 2, 2, 0
Passenger.Passenger()           1, 0, 0, 0
Passenger.setDestinationFloor()     1, 1, 2, 0
Passenger.setSourceFloor()      1, 1, 2, 0
RequestQueue.addExternalRequest()   1, 1, 2, 1
RequestQueue.addInternalRequest()   1, 1, 2, 1
RequestQueue.getExternalRequests()  1, 1, 2, 0
RequestQueue.getInternalRequests()  1, 1, 2, 0
RequestQueue.RequestQueue()     1, 0, 0, 0
RequestQueue.setExternalRequests()  1, 1, 2, 0
RequestQueue.setInternalRequests()  1, 1, 2, 0

--------------------------------------------------------------------------------------------

主要问题:

1.方法复杂度高:Controller.getNextFloor() 复杂度达到 44 ,代码行数 68 ,最大深度 7 。方法内嵌套大量条件判断和循环,逻辑极为复杂,可读性极差,理解和维护难度极大。修改此方法极易引入新问题,且难以调试。

2.代码结构嵌套深:最大深度为 7 ,部分方法如 Controller.getNextFloor() 嵌套层次多。深层嵌套使代码逻辑走向难以追踪,理解和修改代码困难,也不利于代码的扩展。

3.还是同样缺乏注释,不利于代码的阅读和改正。

相较于第二次的改进点:

1.方法的复杂度降低:如 Controller.determineDirection() 复杂度从较高值降为 6 ,逻辑得到简化,相比第二次代码在方法复杂度控制上有一定进步。

2.逻辑处理细化:新增 findExternalRequestfindInitialNextFloor 等方法,将 Controller.getNextFloor() 等方法中的部分逻辑拆分出来,一定程度上提高了代码的模块化程度,使方法功能相对更单一。

但是还是有注释和方法的复杂度分布不均的遗留问题存在。

改进心得:

在 Controller.processRequests 方法中,可以通过迭代器处理外部请求队列,逻辑上更清晰地处理外部请求转换为内部请求的过程,使请求处理逻辑的实现上更有条理。

三:踩坑心得

在编写电梯系统代码时,我满不断踩坑。从类图设计到代码实现,复杂度、可维护性等方面状况频出。这一路的摸爬滚打,让我积累了不少经验教训,也有诸多感悟想分享。

1.在一开始读题目的时候没有理解题目的意思,把过多的电梯实现功能搞到了一起,导致了运行超时,程序一直运行不出来。

 后来通过与同学讨论以及老师的提醒,才知道运行超时大部分是由于代码的逻辑思路有问题。这也告诉了我,一开始不要忙着写代码,要仔细地阅读好题目的意思,了解电梯的正确运行逻辑,先进行类的设计,画出相应的类图,才是首先要干的事。

2.在知道了逻辑错误后,阅读老师给的有关电梯的运行过程详解,我试图去解决运行超时的问题,但是尽管输出与给的测试用例一模一样,还是得到的结果还是答案错误。这时我又拿了其他测试用例去试,结果发现答案不对,让我明白我的代码逻辑还是有一点问题,只是满足给出的测试用例,让我以后再遇到相同的问题可以试试再找几个测试用例来试,而不是只是死磕代码里的测试用例。

 3.在题目集06里写的时候,经过前面的坑,但是又遇到了输出为空的情况,经过一步步的调试和排查,后来知道了原来是电梯的方向都为IDLE,以及下一楼层的判断也是null的情况。让我将程序的方向先设置为UP的方向,找有没有这个的同方向请求,这个问题得到了初步解决。

 4.到了题目集07的时候,鉴于前几次的坑,运行超时的问题基本解决,主要还是运行结果与答案不符,一开始的时候两个测试用例,一个会提前在4楼停,一个会提前在9楼停,通过添加代码的调试语句,我找到了是外部请求出队时,目的地没有正确加入到内部请求队尾的问题。

错误的代码:

 改正后的代码

 于是我改正了处理请求和移除请求的两个方法,成功解决了把外部请求的目的地移到内部请求的问题。

 

四:改进建议

1.优化代码结构:进一步拆分复杂方法,像Controller.getNextFloor()这类复杂度高、逻辑复杂的方法,按功能拆分成多个小方法,减少条件判断和循环的嵌套,使代码结构更扁平、易读。例如,将判断不同请求类型和确定目标楼层的逻辑分开,各自形成独立方法。

2.添加注释:在关键代码处添加注释,解释方法的功能、逻辑思路以及重要变量的作用。比如在电梯运行核心方法、请求处理方法中,详细注释每一步操作的目的,提高代码可读性,方便他人理解和维护。

3.改进方法设计:在设计方法时,尽量遵循单一职责原则,让每个方法功能更单一明确。避免一个方法承担过多功能,降低方法的复杂度。例如,Controller.processRequests()方法可将外部请求转换为内部请求的逻辑进一步细化,使请求处理流程更清晰。

4.完善测试用例:除了使用题目给定的测试用例,主动设计更多边界情况和异常情况的测试用例。像输入非法楼层、连续相同请求、不同方向请求组合等情况,全面测试代码的正确性。

5.合理使用数据结构:根据请求处理的特点,选择更合适的数据结构来存储和管理请求。比如,在请求队列类中,考虑使用更高效的数据结构,提高请求的添加、移除和查找效率,优化电梯调度性能。

五:总结

1.学习收获

通过设计电梯类、乘客类、队列类和控制类等,我深入掌握了类的设计与抽象,学会根据单一职责原则划分类的职责,清晰界定各模块功能。我还掌握了正则表达式的正确应用,队列的相关知识,深入理解电梯调度算法,对自己的思维逻辑得到了进一步的提升,而面对运行超时、答案错误、输出为空等诸多问题,我也学会运用调试工具和多种测试用例排查错误。通过分析问题根源,不断调整代码逻辑,有效解决实际编程难题,逐步积累调试经验,提高问题解决能力。其次这几次的迭代作业也很好的锻炼了我的心态,从一开始的不敢做,不会做到后来的逐步尝试,分析问题,不放弃,渐渐克服了畏难情绪,这正是这次作业带给我的宝贵精神财富。

2.进一步需要学习研究的地方

尽管已完成了基本功能,但是部分方法的复杂度还是很高,代码结构嵌套很深,以后我要学习代码的优化技巧,对代码再一次细分,减少条件判断和循环嵌套,提示代码性能和可维护性。同时,要更好的运用学习面向对象的设计原则,对代码进行进一步的完善。

 

 

posted @ 2025-04-19 20:01  种自己的太阳  阅读(33)  评论(0)    收藏  举报