Java-Elevator 三次作业分析总结

Java-Elevator 三次作业分析总结🥰

题目来自 NCHU( PTA 平台)

本次电梯调度程序遵守简化的 LOOK 算法

前言

最近完成了一项颇具挑战性的 Java 课程作业 —— 设计一个能模拟真实运行逻辑的电梯类。从拿到需求时对着 “请求队列”“调度算法” 抓耳挠腮,到看着代码一步步让电梯 “听懂” 楼层指令、“学会” 优先响应顺路请求,这个过程像极了一场与代码的 “对话实验”。当我在键盘上敲下最后一个测试用例,看着控制台输出的楼层跳动轨迹时,忽然意识到:> 每一行代码都是一次对现实逻辑的抽象翻译,而电梯在虚拟空间里的上下移动,其实藏着编程思维最本真的模样

总体分析

  • 第一次是对电梯运行的最简单算法实现,侧重逻辑
  • 第二次新增类设计,更符合面向对象程序设计的课程要求
  • 第三次变换类设计要求,迭代设计

设计与分析

第一次作业分析与设计

题目:

设计一个电梯类,具体包含电梯的最大楼层数、最小楼层数(默认为1层)当前楼层、运行方向、运行状态,以及电梯内部乘客的请求队列和电梯外部楼层乘客的请求队列,其中,电梯外部请求队列需要区分上行和下行。
电梯运行规则如下:电梯默认停留在1层,状态为静止,当有乘客对电梯发起请求时(各楼层电梯外部乘客按下上行或者下行按钮或者电梯内部乘客按下想要到达的楼层数字按钮),电梯开始移动,当电梯向某个方向移动时,优先处理同方向的请求,当同方向的请求均被处理完毕然后再处理相反方向的请求。电梯运行过程中的状态包括停止、移动中、开门、关门等状态。当电梯停止时,如果有新的请求,就根据请求的方向或位置决定移动方向。电梯在运行到某一楼层时,检查当前是否有请求(访问电梯内请求队列和电梯外请求队列),然后据此决定移动方向。每次移动一个楼层,检查是否有需要停靠的请求,如果有,则开门,处理该楼层的请求,然后关门继续移动。
使用键盘模拟输入乘客的请求,此时要注意处理无效请求情况,例如无效楼层请求,比如超过大楼的最高或最低楼层。还需要考虑电梯的空闲状态,当没有请求时,电梯停留在当前楼层。

输入格式:

第一行输入最小电梯楼层数。
第二行输入最大电梯楼层数。
从第三行开始每行输入代表一个乘客请求。

  • 电梯内乘客请求格式:<楼层数>
  • 电梯外乘客请求格式:<乘客所在楼层数,乘梯方向>,其中,乘梯方向用UP代表上行,用DOWN代表下行(UP、DOWN必须大写)。
  • 当输入“end”时代表输入结束(end不区分大小写)。

题目分析

本题要求设计一个 Java 程序,通过创建电梯类来模拟电梯的运行过程。程序需处理用户输入的电梯请求,根据预设的电梯运行规则进行调度,最终输出电梯的运行状态,以此来检验电梯是否能按规则优先处理同方向请求以及在移动过程中处理顺路请求

  • 请求队列:可以使用 Java 的 Queue 接口及其实现类(如 PriorityQueue 或 LinkedList)来存储电梯内部和外部的请求。对于外部请求,可使用 Map 来区分上行和下行请求,键为楼层号,值为请求队列。但是老师为了锻炼我们的编程能力,不允许使用集合,则使用数组和链表是可行的数据结构实现方法

  • 状态管理
    设计一个数组对应每个请求用于管理该请求状态,当然也可以考虑用二维数组

  • 调度算法
    大致为look算法,可以自主学习,根据电梯的当前方向和请求队列,优先处理同方向的请求。当同方向请求处理完毕后,检查相反方向的请求队列,若有请求则改变方向继续处理

  • 输入输出
    使用 Scanner 类从键盘读取用户输入,根据输入格式解析出请求信息,并进行有效性检查。
    根据电梯的运行状态和楼层信息,按照指定的输出格式输出相应的信息


Main类

功能:程序的入口点,负责读取用户输入,创建Elevator 对象,并将用户输入的请求传递给 Elevator 对象进行处理,最后调用 Elevator 对象的 progress 方法启动电梯运行。

Elevator 类

功能:模拟电梯的运行,包含电梯的属性和行为,负责处理用户输入的请求,根据请求调度电梯运行,并输出电梯的运行状态。

主要属性

minfloor:电梯可到达的最小楼层。

maxfloor:电梯可到达的最大楼层。

currentfloor:电梯当前所在的楼层。

direction:电梯的运行方向,取值为 “UP”、“DOWN”。

status:电梯的运行状态,取值为 “still” 等。

destination:电梯的目标楼层。

len1、len2:分别表示内部请求和外部请求的数量。

floor1、floor2:分别存储内部请求和外部请求的楼层信息。

direct1、direct2:分别存储内部请求和外部请求的方向信息。

sta1、sta2:分别表示内部请求和外部请求的处理状态。

quest1、quest2:分别存储内部请求和外部请求的原始字符串信息。

quest1Index、quest2Index:分别表示内部请求和外部请求数组的当前索引

主要方法

Elevator(int minfloor, int maxfloor, int currentfloor, String direction, String status):构造函数,初始化电梯的属性。

request(String str):将用户输入的请求分类存储到 quest1 或 quest2 数组中。

moveup():输出电梯上行时的运行状态信息。

movedown():输出电梯下行时的运行状态信息。

open():输出电梯开门时的信息。

close():输出电梯关门时的信息。

up():电梯上行一层。

down():电梯下行一层。

scan():将存储在 quest1 和 quest2 数组中的请求信息解析到 floor1、floor2、direct1、direct2、sta1、sta2 数组中。

method21():判断电梯是否需要改变运行方向。

shuchu():根据电梯的运行方向和目标楼层,输出电梯的运行过程信息,包括移动和停靠信息。

method11(int floor11, int floor22):选择内部请求和外部请求队列头部的一个请求进行处理,并调用 shuchu 方法到达该楼层。

method3demo1():移除内部请求队列的第一个请求。

method3demo2():移除外部请求队列的第一个请求。

method3():根据电梯到达的目标楼层,更新内部请求和外部请求队列。

method22demo1():处理外部请求队列的第一个请求,输出运行信息并更新队列。

method22demo2():处理内部请求队列的第一个请求,输出运行信息并更新队列。

progress():电梯运行的主方法,调用 scan 方法解析请求,根据内部请求和外部请求队列的情况,调用相应的方法处理请求,直到所有请求处理完毕。

算法设计

1. 请求分类算法

在 request 方法中,根据请求字符串是否包含逗号,将请求分类存储到 quest1 或 quest2 数组中。

2. 请求解析算法

在 scan 方法中,遍历 quest1 和 quest2 数组,将请求字符串解析为楼层信息和方向信息,并存储到 floor1、floor2、direct1、direct2 数组中,同时初始化处理状态数组 sta1 和 sta2。

3. 调度算法

在 progress 方法中,根据内部请求和外部请求队列的情况进行调度:
当内部请求和外部请求队列都有请求时,调用 method21 方法判断是否需要改变运行方向,调用 method11 方法选择一个请求进行处理,调用 method3 方法更新队列。
当内部请求队列为空,外部请求队列有请求时,调用 method21 方法判断是否需要改变运行方向,调用 method22demo1 方法处理外部请求队列的第一个请求。
当内部请求队列有请求,外部请求队列为空时,调用 method21 方法判断是否需要改变运行方向,调用 method22demo2 方法处理内部请求队列的第一个请求。

4. 方向判断算法

在 method21 方法中,根据电梯的当前运行方向和内部请求、外部请求的楼层信息,判断是否需要改变运行方向。

SourceMonitor分析:

从分析结果来看代码整体结构相对简单,也没有太多嵌套,小女子头脑简单,也写不出太复杂的结构 在编写过程中牢记老师教诲,单一职责原则,同时希望在规范前期代码的同时可以为以后的迭代打好基础

心得:

首先对于电梯运行的逻辑在一开始并没有很好地理解,导致编写过程中多次重写,不仅磨练心智,也考验人品,对于逻辑能力这一块还要加强训练,对于第一次题目,最首要的就是没有理清题目思路就开始写代码,这样就导致我按着的思路是我以为的思路来写,与题目要求的思路不符,题目要求的是只对队列的队头进行处理,而我不是,所以对于一个问题一上手就开始打代码必然是错的,一定要先进行需求分析


第二次题目分析与设计

第二题的题目就是在一次的基础上进行类设计,要求实现单一职责原则(SRP):

题目:

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

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

输入格式:

同第一次作业

输出格式:

同第一次作业

题目分析:

本次题目是第一次作业的迭代,核心算法及运行规则大致上没有改变,无非传递请求时,要忽略高于最高楼层数或低于最低楼层数的请求及遵循单一职责原则,将原有的Elevator类拆分成电梯类,乘客请求类,队列类以及控制类,符合面向对象程序设计规范。

类图分析:

题目分析

一、类设计:从 “大而全” 到 “职责分离”

  • 上次单类设计(问题)
    单一类承担所有职责:原Elevator类同时包含以下功能:
    电梯状态管理(当前楼层、方向、状态);
    请求队列管理(内部 / 外部请求的存储、解析、去重);
    调度算法(方向判断、楼层选择、移动逻辑);
    输入输出处理(请求解析、结果打印)。
    代码耦合度高:功能混杂导致类复杂度高,难以维护和扩展(如新增请求类型需修改电梯类)。

  • 本次迭代设计(改进)

拆分为 4 个核心类:

电梯类(Elevator):仅负责电梯的状态和基础操作。
职责:维护当前楼层、运行方向、运行状态;实现移动(up()/down())、开门 / 关门(openDoor()/closeDoor())等物理行为。
属性:currentFloor、direction、state、minFloor、maxFloor。
乘客请求类(PassengerRequest):封装请求信息并验证有效性。
职责:解析输入字符串为请求对象(区分内部 / 外部请求);验证楼层是否有效(是否在[minFloor, maxFloor]范围内);标记请求方向(UP/DOWN)。
属性:floor(请求楼层)、direction(乘梯方向,仅外部请求有)、isInternal(是否为内部请求)。
队列类(RequestQueue):管理请求队列并实现去重逻辑。
职责:使用集合存储请求(内部请求用PriorityQueue,外部请求用Map<Integer, Set>);添加请求时过滤重复项(如连续相同请求)。
方法:addRequest(PassengerRequest request)(添加请求,自动去重)、getNextRequest(Direction currentDirection)(根据当前方向获取下一个请求)。
控制类(ElevatorController):协调电梯和队列,实现调度算法。
职责:根据电梯当前方向和队列中的请求,确定目标楼层;驱动电梯移动并处理停靠逻辑;调用队列获取待处理请求。
核心逻辑:
同方向请求优先处理;
处理完同方向请求后切换方向;
移动过程中检查顺路请求。
类间协作示例:

二、新增功能:请求验证与重复过滤

  • 上次设计(缺失)
    未处理以下情况:
    无效楼层请求(如高于maxFloor或低于minFloor);
    连续相同请求(如<3><3><3>或<5,DOWN><5,DOWN>)。

  • 本次迭代(新增逻辑)
    无效楼层验证

三、调度逻辑:从 “电梯自驱” 到 “控制类协调”

  • 上次设计(问题)
    调度逻辑嵌入在Elevator类的run()方法中,逻辑与状态混合,难以复用。
  • 本次迭代(改进)
    控制类主导调度:
    方向确定:
    ElevatorController根据当前方向和队列请求,调用RequestQueue.getNextRequest(direction)获取同方向请求;
    若无同方向请求,则切换方向(UP→DOWN 或 DOWN→UP)。
    移动与停靠:
    控制类驱动电梯逐层移动,每到达一层调用RequestQueue.checkRequests(currentFloor)检查是否有请求;
    若有请求,触发电梯开门、处理请求(从队列移除)、关门,否则继续移动。

SourceMonitor分析:

通过SM分析结果也可以看出一方面处于电梯运行逻辑的复杂度,一方面由于编程经验的不足,代码的最大复杂度平均深度明显增大,主要是因为嵌套过多,代码长度过大,模块复杂度过高导致的,这是需要注意和改进的方向。

心得:

这次的代码相对独立完成(之前有借助AI寻找学习更多知识),原先的代码耦合性不强的话,这次的代码相对好改,但是更加磨练心性,要把很多很多个变量成员属性进行移植,并给他们加上“家族名”,需要更加细心有耐心地去改代码

改进

这一次较上一次的改进有以下几点:

  • 使用了多种功能不同的类来实现

  • 对不合理的数据进行处理,更加符合实际要求

  • 对电梯调度的算法进行完善,使之更完备


第三次题目分析与设计

原题呈现

第三次题目也是再上一题的基础上进行迭代设计,这里是第三次题目提出来的新的要求:

设计与分析

需求分析

仔细查看题目可以知道,第三次题目中,电梯的调度算法与之前一致,但是在类设计上有所不同:

  • 加入了Passenger类

  • 把外部请求变为<请求源楼层,请求目的楼层>形式,更加符合实际生活

  • 对于外部请求,当电梯处理该请求之后(该请求出队),要将<请求源楼层,请求目的楼层>中的<请求目的楼层>加入到请求内部队列(加到队尾)

看上去改动很大,其实只要在理解了基本逻辑的情况下,只要你愿意,就可以把改变后题目的输入改为和之前两次相同的输入形式,加了Passenger类,用它处理内部队列需要对destinationFloor进行出来,sourceFloor为null即可,再考虑重复数据即可,但是对于具体细节问题还有于之前不一样的还需要斟酌

就比如这样的测试点:


按照题目测试点输出结果应该是:

可以看到五楼被执行了两次

然而,按照电梯运行逻辑不变,将输入转化为相同的输入甩到之前的代码中,运行结果如下:

可以看到5楼只执行了一次

思考

原代码中5楼只执行一次是因为遵循要求 继续向上运行,运行到第5层,判断该楼层是否应该停下来处理请求,判断后应处理请求(请求是5,且方向为UP,与电梯同向),状态变为Stopped,输出“Open Door # Floor 5 回车Close Door”,此时将IQ和OQ两个队列中的队头的5层请求全部出队(删除)
即 两个队列的5楼被一次执行,所以如果想要仅靠转换输入来完成题目要求的话,应当修改这一点,因为第三次要求的是对于外部请求,当电梯处理该请求之后(该请求出队),要将<请求源楼层,请求目的楼层>中的请求目的楼层加入到请求内部队列(加到队尾) 此时需要修改代码逻辑,即==只有当此时运行方向和内部序列都与外部序列请求相同时,才清除两个队列请求

SourceMonitor分析:

通过SM分析结果同样可以看出和第二次相同的问题,因为二者在算法逻辑上没有太大的改动,仅仅改了一点数据处理的方式,代码冗长的问题依旧存在

心得

第二次第三次提交代码的时候都存在代码长度超过限制的问题,第二次通过删除注释的方式解决,第三次通过修改变量名的方式解决,虽然通过了测试点,但是同时也反映了一个问题,代码过于冗长,或者还有可以修改的地方

小结💞

  • 单一职责原则 通过本次重构,深刻体会到 “一个类只负责一件事” 的重要性。拆分后,每个类的修改风险降低(如修改请求验证逻辑不影响调度算法),代码可维护性显著提升。

  • 面向对象设计思维从 “过程式编程” 转向 “对象协作式编程”,学会用类图规划职责,为复杂系统设计奠定基础。
  • 问题解决能力提升

  • 问题解决能力提升能从复杂规则中抽象出独立模块,解决编程中遇到的各种问题

展望与改进方向💕

进一步优化:可引入枚举类型规范方向和状态(如Direction.UP替代字符串 “UP”),避免拼写错误;
功能扩展:未来可添加 “电梯负载均衡”“请求优先级” 等功能,得益于当前解耦设计,扩展成本较低;
测试完善:补充单元测试用例,覆盖无效请求、重复请求、边界楼层(如 minFloor 和 maxFloor)等场景,确保代码健壮性。

总结🥳

通过本次迭代设计,不仅完成了作业要求,更重要的是掌握了 “如何用设计原则驱动代码进化” 的思维方式。在后续学习中,将继续践行面向对象设计原则,力求写出更优雅、可维护的代码。

posted @ 2025-04-20 17:31  Assun  阅读(40)  评论(1)    收藏  举报