NCHU OOP BLOG-1
目录
1.前言
2.设计与分析
2.1. practice 1
2.1.1.题目要求:
2.1.2.题目需求分析
2.1.3.算法设计
2.1.4.算法实现步骤
2.1.5.SourceMonitor分析
2.2. practice 2
2.2.1.题目要求:
2.2.2.题目需求分析
2.2.3.算法设计
2.2.4.算法实现步骤
2.2.5.SourceMonitor分析
3.踩坑心得
4.改进建议
5.总结
1.前言
第一次使用blog记录我的第一次Java大作业
这是一个极具挑战性的任务,我的编码水平很低,从大一上学期学习的C语言,便开始了显现。所以当我看到这个题目时,让我虎躯震了又震。不过,从这三次作业中,我学到了很多,不仅仅是有关Java语法的知识,更重要的是编程思维完成了潜移默化的变化。在C语言相关阶段,大部分作业仅仅通过少量的语法拼凑就能完成,但是到了Java,就不仅仅是知道语法就能完成这么简单的事情了,更多的是对于该问题的解构与思考,对于思维上的要求是极大的。
对于前三次的Java作业:
第一次的作用是让我们熟悉这道问题的逻辑,也就是让我们研究解决这道题目的算法;
第二次的作用是让我们完成解决电梯类职责过多的问题,类设计要求遵循单一职责原则(SRP),熟悉课程上的内容;
第三次的作用是让我们新增加一个类,进行迭代设计。
2.设计与分析
2.1. practice 1
2.1.1.题目要求:
设计一个电梯类,具体包含电梯的最大楼层数、最小楼层数(默认为1层)当前楼层、运行方向、运行状态,以及电梯内部乘客的请求队列和电梯外部楼层乘客的请求队列,其中,电梯外部请求队列需要区分上行和下行。
电梯运行规则如下:电梯默认停留在1层,状态为静止,当有乘客对电梯发起请求时(各楼层电梯外部乘客按下上行或者下行按钮或者电梯内部乘客按下想要到达的楼层数字按钮),电梯开始移动,当电梯向某个方向移动时,优先处理同方向的请求,当同方向的请求均被处理完毕然后再处理相反方向的请求。电梯运行过程中的状态包括停止、移动中、开门、关门等状态。当电梯停止时,如果有新的请求,就根据请求的方向或位置决定移动方向。电梯在运行到某一楼层时,检查当前是否有请求(访问电梯内请求队列和电梯外请求队列),然后据此决定移动方向。每次移动一个楼层,检查是否有需要停靠的请求,如果有,则开门,处理该楼层的请求,然后关门继续移动。
使用键盘模拟输入乘客的请求,此时要注意处理无效请求情况,例如无效楼层请求,比如超过大楼的最高或最低楼层。还需要考虑电梯的空闲状态,当没有请求时,电梯停留在当前楼层。
请编写一个Java程序,设计一个电梯类,包含状态管理、请求队列管理以及调度算法,并使用一些测试用例,模拟不同的请求顺序,观察电梯的行为是否符合预期,比如是否优先处理同方向的请求,是否在移动过程中处理顺路的请求等。为了降低编程难度,不考虑同时有多个乘客请求同时发生的情况,即采用串行处理乘客的请求方式(电梯只按照规则响应请求队列中当前的乘客请求,响应结束后再响应下一个请求),具体运行规则详见输入输出样例。
输入格式:
第一行输入最小电梯楼层数。
第二行输入最大电梯楼层数。
从第三行开始每行输入代表一个乘客请求。
- 电梯内乘客请求格式:<楼层数>
- 电梯外乘客请求格式:<乘客所在楼层数,乘梯方向>,其中,乘梯方向用UP代表上行,用DOWN代表下行(UP、DOWN必须大写)。
- 当输入“end”时代表输入结束(end不区分大小写)。
输出格式:
模拟电梯的运行过程,输出方式如下:
- 运行到某一楼层(不需要停留开门),输出一行文本:
Current Floor: 楼层数 Direction: 方向 - 运行到某一楼层(需要停留开门)输出两行文本:
Open Door # Floor 楼层数
Close Door
2.1.2.题目需求分析
本题目要求实现一个电梯调度模拟系统,能够处理用户输入的内部(目标楼层)和外部(楼层呼梯方向)请求。电梯需遵循以下规则:
- 优先响应与当前运行方向相同的请求
- 无同方向请求时切换方向处理反向请求
- 自动处理沿途顺路请求
- 实时输出电梯运行状态(移动、开关门等)
我们可以有以下操作:
内部请求队列:ArrayList<Integer>存储目标楼层
外部请求队列:ArrayList<ExternalRequest>存储楼层及方向
电梯通过以下三个方面运行:
- 位置状态
currentFloor:实时记录当前所在楼层
minFloor/maxFloor:电梯服务范围约束
- 运动状态
direction:枚举类型(UP/DOWN/IDLE)
- 请求状态
内部请求队列处理标记:到达目标楼层时移除
外部请求处理条件:需同时满足楼层匹配和方向一致
2.1.3.算法设计
Main类
职责:输入解析与电梯初始化
关键步骤:
while(!data.equals("End")) {
// 解析请求类型
if(request.contains(",")) {
// 处理外部请求
elevator.addExternalRequest(...);
} else {
// 处理内部请求
elevator.addInternalRequest(...);
}
}
Elevator类
核心属性:
private ArrayList<Integer> internalRequests;
private ArrayList<ExternalRequest> externalRequests;
private Direction direction = IDLE;
关键方法:
|
方法名 |
功能说明 |
|---|---|
|
processRequests() |
主处理循环,驱动电梯运行 |
|
determineMovement() |
根据请求分布决策运行方向 |
|
checkDemand() |
检测当前方向是否还有待处理请求 |
2.1.4.算法实现步骤
- 初始化阶段
读取楼层范围参数
建立空请求队列
- 请求接收阶段
解析输入到对应队列
有效性检查(楼层范围、格式)
- 调度运行阶段
- 检查当前楼层是否处理请求
- 确定移动方向
- 执行单步移动
- 输出状态信息
- 循环直到所有请求处理完毕
- 异常处理机制
无效输入检测:if (!request.matches...)
楼层越界过滤:addInternalRequest中的范围检查
2.1.5.SourceMonitor分析

从这份代码分析结果瞧,Main.java 没特复杂的结构。咱写代码时一直把 “单一职责原则” 揣在心里,就想着一个模块、一个方法就干好一件事儿,别搅和得乱七八糟。看这代码行数、语句数,还有方法啥的统计,感觉还挺规整。注释比例也合适,以后自己或者别人看代码,也能轻松明白啥意思。现在把基础代码规范好,就像盖房子把地基打牢,以后要是代码要更新、加功能,也能顺顺当当,不担心牵一发而动全身,省不少事儿呢
2.1.6.心得
对电梯运行逻辑不是非常清晰,编写过程非常坎坷,在一开始没有深入分析题目需求,思路十分混乱,对于算法设计几乎没有,处于想到什么就写什么的状态,所以以后写代码时,开始的需求分析是重中之重。
2.2. practice 2
2.2.1. 题目要求:
电梯运行规则与前阶段单类设计相同,但要处理如下情况:
乘客请求楼层数有误,具体为高于最高楼层数或低于最低楼层数,处理方法:程序自动忽略此类输入,继续执行
乘客请求不合理,具体为输入时出现连续的相同请求,例如<3><3><3>或者<5,DOWN><5,DOWN>,处理方法:程序自动忽略相同的多余输入,继续执行,例如<3><3><3>过滤为<3>
注意:本次作业类设计必须符合如上要求(包含但不限于乘客请求类、电梯类、请求队列类及控制类,其中控制类专门负责电梯调度过程)
2.2.2. 题目需求分析:
系统通过分层设计实现核心功能:
|
类名 |
核心职责 |
关键属性/方法说明 |
|---|---|---|
|
ExternalRequest |
封装外部请求数据 |
floor记录楼层,direction记录方向 |
|
Elevator |
维护电梯物理状态 |
currentFloor实时位置,direction移动方向 |
|
RequestQueue |
管理请求队列与过滤逻辑 |
cleanRequests()清理无效请求,removeDuplicateExternals()去重 |
|
Controller |
实现LOOK调度算法 |
collectTargets()收集同方向请求,processFloor()处理到达事件 |
重复请求处理
外部请求:通过链表遍历去重(removeDuplicateExternals())
内部请求:通过TreeSet收集时自动去重(collectTargets())
2.2.3. 算法设计:

类图如上
关键算法说明
- 请求过滤流程
// RequestQueue.java
public void cleanRequests(int min, int max) {
// 移除越界请求(处理无效输入)
internalRequests.removeIf(f -> f < min || f > max);
externalRequests.removeIf(req -> req.getFloor() < min || req.getFloor() > max);
removeDuplicateExternals(); // 处理重复外部请求
}
- LOOK调度算法实现
// Controller.java
public void processRequests() {
// 获取当前方向的目标楼层集合
List<Integer> targets = queue.collectTargets(currentDir, elevator.getCurrentFloor());
// 逐层移动处理请求
for (int target : targets) {
move(target, currentDir);
processFloor(elevator.getCurrentFloor(), currentDir);
}
}
2.2.4. 算法实现步骤:
- 初始化阶段
读取minFloor/maxFloor参数(Main类)
创建电梯对象与空请求队列
- 请求接收阶段
解析输入并分类存储(内部/外部请求)
执行cleanRequests()过滤无效数据
- 调度运行阶段
|
步骤 |
动作描述 |
对应代码片段 |
|---|---|---|
|
1 |
确定初始运行方向 |
determineDirection() |
|
2 |
收集同方向目标楼层 |
collectTargets() |
|
3 |
逐层移动并处理请求 |
move()+processFloor() |
|
4 |
方向反转扫描反向请求 |
currentDir.reverse() |
- 异常处理机制
无效输入:通过正则表达式匹配格式,cleanRequests()过滤非法楼层
运行时错误:电梯移动时自动修正越界值(move()方法中的边界检查)
2.2.5. SourceMonitor分析:

这次分析发现代码复杂度有所提升,看来之前单一职责原则落实得还不够到位呢。可能有些方法或者模块承担了过多功能,导致逻辑交织。接下来可以试着把大功能拆分成小的、更专注的功能模块,让每个部分都干好自己的 “分内事” 。
3.踩坑心得
在本次电梯调度系统的开发过程中,我深刻体会到软件工程中需求分析和架构设计的重要性。初期由于对电梯运行机制理解不够深入,直接进入编码导致设计反复重构,特别是在处理连续相同请求时,本应在输入阶段就进行过滤,却不得不在RequestQueue类中增加额外的去重逻辑。这种"先编码后思考"的做法让开发过程变得异常坎坷,也让我明白必须先用流程图明确"同方向优先处理→顺路捎带→方向反转"的业务逻辑再开始编码的重要性。通过采用分层设计,将控制逻辑、电梯状态和请求管理解耦,不仅提升了代码的可维护性(如新增功能只需扩展特定类),也使测试更加便捷。
这次开发经历让我完成了从"功能实现优先"到"质量管控优先"的思维转变,通过增加前置校验、优化数据结构选择(如用TreeSet替代链表)和埋点日志等措施,不仅提高了系统健壮性,也为性能优化奠定了基础。这些经验教训让我确立了"需求分析画透业务场景→类图设计明确交互关系→测试用例驱动功能实现"的三阶段工作法,避免再走"推倒重来"的弯路。
4.改进建议
- 动手前先画图
别急着写代码!先把电梯怎么跑的流程画明白。比如:电梯闲着的时候咋办?遇到最高层怎么掉头?把这些特殊情况都在纸上理清楚,后面写代码能少踩一半坑。 - 分家要彻底
现在请求都混在一个队列里,就像把袜子和内裤塞同一个抽屉。建议把内部按钮(要去几楼)和外部呼叫(有人按上下键)分成两个独立队列,各管各的,不容易乱。 - 多写测试保平安
重点测试这些情况:连续按相同楼层(比如<5><5>)
乱按不存在的楼层(比如按了-1层)
电梯走到顶楼突然有人从1楼呼叫
这些测试能帮你提前发现很多bug
4.像小学生作文
每个方法只干一件事,比如:
移动电梯就专心移动
开门关门就别掺和计算方向
方法长度最好不要超过手机屏幕(20行左右),太长了肯定有问题
记住:好代码是改出来的!第一版写得烂没关系,但要边写边总结,下次肯定能做得更好~
5.总结
虽然这次大作业没有很好完成,但是我从中学习到了许多。开始我总想着赶紧把功能写出来,但总是问题不断,代码改了又改。后来明白,只有耐心做好需求分析、画好流程图,才能少走弯路。比如,电梯在不同楼层之间的移动逻辑,还有如何判断处理顺路请求,这些都要先想清楚。还有就是代码结构设计很重要,之前把所有功能都堆在一个类里,导致代码混乱,维护起来费劲。后来把请求管理、电梯状态和调度逻辑分开,清晰多了。我以后一定要先规划好再动手,多写注释和测试,这样代码质量才能一步步提高,遇到复杂问题也能更有条理地解决,不再像之前那样手忙脚乱了。
浙公网安备 33010602011771号