电梯调度问题以及基础题目集分析
电梯调度问题以及基础题目集分析
前言
-
基础题目集说明
- 题目集5:
- 包括:身份证号校验、求解一元二次方程以及两道正则表达式训练题。
- 涉及知识点:基础Java程序类的设计(属性和方法)、正则表达式的使用
- 难度:⭐
- 题目集6:
- 包括:点与线和雨刷问题。
- 涉及知识点:Java程序类的设计(类间关系设计和控制类的设计)、单一职责原则(SRP)。
- 难度:⭐⭐
- 题目集7:
- 包括:步枪销售问题和蒙特卡罗方法求圆周率问题。
- 涉及知识点:Java程序类的设计(类间关系设计和控制类的设计)、单一职责原则(SRP)。
- 难度:⭐⭐
- 题目集5:
-
迭代题目集说明
-
初始电梯调度问题:
- 需求:设计一个电梯类,包含状态管理、请求队列管理以及调度算法,采用串行处理乘客的请求方式(优先处理同方向的请求)。
- 难度:⭐⭐
-
第一次迭代:
- 新增需求:程序自动忽略高于最高楼层数或低于最低楼层数输入,程序自动忽略相同的多余输入,继续执行。
- 难度:⭐⭐
-
第二次迭代:
- 新增需求:外部请求由之前的
<请求楼层数,请求方向>
修改为<请求源楼层,请求目的楼层>
,对于外部请求,当电梯处理该请求之后(该请求出队),要将<请求源楼层,请求目的楼层>
中的请求目的楼层
加入到请求内部队列(加到队尾)。 - 难度:⭐⭐⭐
涉及知识点:Java程序类的设计(类间关系设计和控制类的设计)、单一职责原则(SRP)、正则表达式、链表。
- 新增需求:外部请求由之前的
-
设计与分析
基础题目集
-
踩坑心得:
1.正则表达式很实用但需要记和背的细节众多,不然容易犯错如:
- 校验键盘输入的 QQ 号是否合格,判定合格的条件如下:
- 要求必须是 5-15 位;
- 0 不能开头;
- 必须都是数字;
错误的表达❎
class Regex { public static boolean juige(String test) { return test.matches("[1-9][0-9]{5,15}"); // 错误的表达式 } }
错误表达式 实际匹配范围 要求范围 问题描述 [1-9][0-9]{5,15}
6-16 位数字 5-15 位数字 范围多算了1位 - 错误逻辑:
- 第一位
[1-9]
匹配 1 位 [0-9]{5,15}
再匹配 5-15 位- 总和为 6-16 位(不符合 5-15 位要求)
- 第一位
正确的表达✅
class regex{ public static boolean juige(String test){ return test.matches("[1-9]{1}[0-9]{4,14}"); } }
2.Java中字符串有单独类型,且有许多可以实现对应操作的方法如:
Java简单实现
String name1 = "xiaoming"; String name2 = "xiaoli"; String name1 = name1 + name2;//实现两个名字合并一起
C语言思路实现
String name1 = "xiaoming"; String name2 = "xiaoli"; char[] result = new char[name1.length() + name2.length()]; // 复制第一个字符串(模拟 strcpy) for(int i = 0; i < name1.length(); i++) { result[i] = name1.charAt(i); } // 查找第一个字符串的结尾位置(模拟 strlen) int endPos = name1.length(); // 这里直接使用length(),实际C用strlen() // 追加第二个字符串(模拟 strcat) for(int i = 0; i < name2.length(); i++) { result[endPos + i] = name2.charAt(i); } // 转换为String对象 name1 = new String(result); System.out.println(name1); // 输出:xiaomingxiaoli
3.方法参数传递误解
值传递/引用传递混淆:
// C语言指针思维导致的误解 void change(String s) { s = "new value"; // ❌ 以为会修改原引用 } String str = "old"; change(str); System.out.println(str); // 仍然输出"old"
- 校验键盘输入的 QQ 号是否合格,判定合格的条件如下:
-
改进建议
1.多训练正则表达式;
2.可以多学习Java许多的库函数,并通过看接口源文件辅助学习;
电梯调度迭代问题
初始问题(未迭代)
需求分析:
要求设计一个电梯类,包含状态管理、请求队列管理以及调度算法,并使用一些测试用例,模拟不同的请求顺序,观察电梯的行为是否符合预期,采用串行处理乘客的请求方式。
设计:
因为输入数据的多少在未输入end前是未知的,故考虑用Link或LinkedList来储存数据。又因为是串行处理乘客请求,即先处理第一个才处理第二个此时处理完第一个便可删除,故有多次的remove头部的操作,考虑运行效率使用LinkedList。考虑到外部请求与内部请求的不同,即有相对于发起楼层的方向与内部请求只有相对于目前电梯楼层的方向[1],故用两个队列分别处理内外请求。考虑到初始电梯移动方向考虑到实际情况,应是先有外部请求发生才有内部请求,故以第一个外部请求的相对方向为起始方向。
-
源码分析
-
SourceMontor报表
1. 极端深度嵌套(紧急问题)
-
最大块深度:9+层(Main.java第303行)
-
深度分布异常:
9+层嵌套语句占比高达46.3%(74/160) 7层以上嵌套占比51.9%(83/160)
2. Main类过载
- main方法:
- 承担了16个语句逻辑
- 包含4层嵌套结构
- 调用17个其他方法
- Elevator类:
- 仅有getter/setter方法(12个)
- 缺乏业务逻辑封装
3. 控制流缺陷
指标 当前值 健康值 风险等级 分支语句占比 5% 10-15% 🔴过低 平均块深度 5.64 ≤3 🔴危险 方法调用/语句 76% 30-50% 🟡偏高 -
-
类图
-
-
踩坑心得
1.初做这题未对此题有过多的理解,且因为未对控制类有过多的理解,并未设置控制类。
2.对类的理解不够深刻,没有考虑到用类来描述输入指令,反倒是使用需要时拆分指令的方法来达到目的[2],使得效率低下。
3.状态管理混乱, 没有建立统一的状态机管理电梯运行状态。
问题现象:
- 在
firstDeal()
和judgeNextCmd()
中重复计算目标楼层 direction
和status
状态变更分散在多个方法中
public void sparateCmd(String cmd){ if(cmd.contains(",")){ outerCmd.add(cmd); }else { innerCmd.add(cmd); } } public int sparateNum(String cmd) { if (cmd.contains(",")) { // 处理外呼命令 <数字,方向> String[] parts = cmd.replaceAll("^<(\\d+),(UP|DOWN)>$", "$1,$2").split(","); return Integer.parseInt(parts[0]); } else { // 处理内呼命令 <数字> return Integer.parseInt(cmd.replaceAll("^<(\\d+)>$", "$1")); } } public Direction sparateDirection(String cmd) { String[] parts = cmd.replaceAll("^<(\\d+),(UP|DOWN)>$", "$1,$2").split(","); return parts[1].equals("UP") ? Direction.UP : Direction.DOWN; }
- 在
-
改进建议:
1.针对重复解析问题
-
解析后存储为
Command
对象 -
使用
PriorityQueue
替代LinkedList
-
实现
Comparable
接口定义调度优先级2.针对"指令处理低效"问题
interface ElevatorCommand {
void execute(Elevator elevator);
}
-
提前解析指令为对象
-
存入队列时即完成格式校验
3.设置控制类提高效率和减少耦合性
第一次迭代
需求分析:(新增需求)
迭代设计要求遵循单一职责原则(SRP),将电梯调度程序分解为多个类,每个类具有单一职责:
- ExternalRequest 类:负责处理外部请求,包括楼层和方向信息。
- Elevator 类:负责电梯的核心功能,包括状态管理、移动、开门、关门等。
- Direction 和 Status 枚举类:定义电梯的方向(UP、DOWN、NONE)和状态(MOVING、STOPPED)。
- Controller 类:作为控制中心,管理外部请求队列和电梯的行为。
设计:(新增设计)
-
ExternalRequest 类:用于存储外部请求的楼层和方向,确保请求的有效性。
-
Elevator 类:包含电梯的状态(如当前楼层、方向、状态)、内部请求队列,以及处理请求的方法。
-
Direction 和 Status 枚举类:提供电梯方向和状态的枚举值,便于状态管理和转换。
-
Controller 类:管理外部请求队列,处理外部请求并将其传递给电梯类。控制器决定电梯的移动方向,并协调电梯的行为。
-
源码分析
-
SourceMontor报表
1. 极端方法复杂度(紧急问题)
-
最高复杂度方法:
Controller.determineDirection()
(复杂度20) -
复杂度分布异常:
10+复杂度方法占比25%(3/12) 15+复杂度方法占比8.3%(1/12)
2. 控制器类职责过载
- Controller类:
- 包含7个业务方法
- 平均方法复杂度4.71
- 承担调度、决策、状态管理等多重职责
- Elevator类:
- 纯数据类(12个getter/setter)
- 业务逻辑外溢到Controller
3. 控制流结构缺陷
指标 当前值 健康范围 风险等级 方法平均复杂度 2.56 1.0-2.0 🔴危险 最大嵌套深度 7层 ≤5层 🔴紧急 分支语句占比 21% 10-15% 🟡偏高 平均语句数/方法 3.82 ≤5 ✅正常 - 类图
-
-
-
踩坑心得
1.我是模仿老师给的类图进行书写的,但对于非getter和setter方法的名称理解产生了理解偏差和以及自己的思路不同,导致代码很少复用。
2.有些方法书写过多有些代码书写过少,没有做好单一职责原则。
-
改进建议
- 针对重复解析问题
问题现象:每次处理请求时都要重新解析字符串
改进方案:
// 创建命令对象存储解析结果
class ElevatorRequest {
int floor;
boolean isExternal;
Direction direction; // 仅外部请求需要
// 解析逻辑只执行一次
public ElevatorRequest(String input) {
if(input.contains(",")) {
String[] parts = input.replaceAll("[<>]", "").split(",");
this.floor = Integer.parseInt(parts[0]);
this.direction = Direction.valueOf(parts[1]);
this.isExternal = true;
} else {
this.floor = Integer.parseInt(input.replaceAll("[<>]", ""));
this.isExternal = false;
}
}
}
2.针对指令处理低效
问题现象:直接操作原始字符串
改进方案:
// 分装基本命令接口(简化版)
abstract class SimpleCommand {
protected int targetFloor;
public abstract void process(Elevator elevator);
}
// 外部命令实现
class ExternalCommand extends SimpleCommand {
private Direction direction;
public void process(Elevator elevator) {
if(elevator.getNowFloor() == targetFloor) {
elevator.openDoor();
}
// 其他处理逻辑...
}
}
第二次迭代
需求分析:(新增需求)
在之前的迭代基础上,进一步加入乘客类(Passenger),并取消乘客请求类,以更精细地遵循单一职责原则(SRP)。乘客类负责处理乘客的上下楼层请求,包括乘客的起点楼层和目的楼层。外部请求由乘客的起点楼层和目的楼层决定,内部请求由电梯内部乘客的目的楼层决定。当电梯处理外部请求后,该请求将从外部请求队列中移除,并将乘客的目的楼层加入到内部请求队列中。
设计:(新增设计)
- Passenger 类:存储乘客的起点楼层和目的楼层,计算乘客请求的方向。
- Elevator 类:管理电梯状态、移动逻辑、内部请求队列及处理请求的方法。
- Controller 类:管理外部请求队列,协调乘客请求的处理,并将外部请求转换为内部请求。
- RequestQueue 类:管理内部和外部请求队列,支持添加和移除请求。
电梯的移动逻辑根据请求队列中的请求来决定方向,并优先处理同方向的请求。通过测试用例验证类之间的协作和电梯调度的正确性。
-
源码分析
- SourceMontor报表
1. 极端深度嵌套问题
-
最大嵌套深度:9+层(Main.java第303行)
-
深度分布异常:
■ 9+层嵌套占比:46.3%(74/160) ■ 7+层嵌套占比:51.9%(83/160) ■ 平均嵌套深度:5.64(安全值≤3)
2. Main类过载(架构缺陷)
-
main方法问题:
- 代码行数:16行
- 嵌套层级:4层
- 方法调用:17次
- 承担职责:输入处理、电梯控制、输出显示
-
Elevator类问题:
-
纯数据类特征:
■ 12个getter/setter方法 ■ 0个业务逻辑方法 ■ 状态管理完全外置
-
3. 控制流结构风险
质量指标 | 当前值 | 安全阈值 | 风险等级 | 典型问题位置 |
---|---|---|---|---|
分支语句占比 | 5% | 10-15% | 🔴过低 | 缺少必要条件校验 |
方法调用/语句比 | 76% | 30-50% | 🔴过高 | Main.main() |
平均方法长度 | 4.09行 | ≤5行 | ✅正常 | - |
注释覆盖率 | 1% | ≥20% | 🔴危险 | 全文件 |
-
类图
-
踩坑心得
-
对于题目的理解一开始出现了分叉,后来才意识到对于外部请求而言目标楼层相对命令发起楼层本事就有一个相对方向,这个方向就相当于前面的方向命令。
-
发现上一次迭代时没有做好封装性原则,且没有发现Control类中有许多私有类型的方法,才意识到对于方法也是会有私有类型的。[3]
-
并且仍然因为思路问题导致尽管我经过多次重写代码,但仍然不能减少复杂度。
1. 基本概念
私有方法是仅能在声明它的类内部访问的方法,使用
private
修饰符定义。其核心特点是:- 封装性:隐藏内部实现细节,仅暴露必要的公共接口。
- 安全性:防止外部类误用或破坏内部逻辑。
- 代码复用:在类内拆分复杂逻辑,避免重复代码。
2. 典型应用场景
场景 说明 示例 辅助方法 拆分复杂公有方法的子逻辑 解析请求字符串、校验参数有效性等 状态管理 封装对象状态变更逻辑(如电梯状态转换) private void updateElevatorState()
工具方法 类内复用的通用操作(如数据格式化) private String formatFloorMessage(int floor)
限制访问 禁止外部调用的敏感操作(如资源释放) private void releaseResources()
-
-
改进建议:
1.多向老师请教,改进方法打开思路。
2.简化电梯方向处理逻辑和判断点。
3.优化指令类和其它类的关系减少其与其它类的耦合性。
总结
-
学习收获
1.明白了类间关系对于代码复用和代码维护的重要性,以及对于代码类间关系的使用有了一定的经验和意识。
2.对于正则表达式的使用有了使用经验和切实体会到正则表达式的便捷和好用。
3.对于private和public有更深刻的理解,和学会了如何正确使用。
4.对于包的理解和使用更加深刻。
5.提高了我对于代码编程,Java面向对象的编程有了更多的理解。
-
改进建议
1.可以使用AI学习新的知识与方法,以及帮助自己进行纠错(尤其是一些语法和不懂知识的犯错)。
2.可以多和老师同学进行交流,相互学习和进步。
3.多提升自己的毅力和信心别轻易放弃。