Java程序设计——题目集5~7总结与单部电梯调度问题分析
Java程序设计——题目集5~7总结与单部电梯调度问题分析
前言
经过了几周紧张的Java编程训练,我完成了题目集5到7的所有编程任务。这三次题目集涵盖了Java面向对象编程的核心内容,从基本的类设计到复杂的多类协作系统,不仅考察了编程基础,更锻炼了解决实际问题的能力。
题目集5
题目集5主要集中在Java继承与多态的应用,共包含8道编程题目。其中最具挑战性的是"单部电梯调度程序",这道题要求设计一个电梯类,包含电梯的最大楼层数、最小楼层数、当前楼层、运行方向、运行状态以及电梯内部和外部请求队列。电梯需要按照特定规则运行,优先处理同方向的请求,并在所有同方向请求处理完毕后处理反向请求。这个题目要求我们建立一个复杂的状态管理系统,同时处理多种请求逻辑,是对面向对象编程能力的全面检验。
题目集6
题目集6在题目集5的基础上进行了迭代升级,要求对电梯调度系统进行重构,遵循单一职责原则(SRP)。这次题目要求将原本单一的电梯类拆分为多个专业类:电梯类、乘客请求类、队列类以及控制类。此外,还增加了对错误输入的处理,如楼层数超限和重复请求的过滤。这个题目集强调了良好的软件设计原则,让我深入理解了类的职责划分和组件化设计的重要性。
题目集7
题目集7是三个题目集中设计最为完善的一部分。这次题目在题目集6的基础上再次迭代,引入了乘客类(Passenger)取代原有的乘客请求类,并改变了请求输入的格式。外部请求从之前的"请求楼层数,请求方向"修改为"请求源楼层,请求目的楼层",电梯处理外部请求后需要将请求目的楼层加入到内部请求队列。这种设计更接近实际电梯系统的工作方式,提高了模拟的真实性。
通过这三次题目集的递进式设计,我不仅学习了如何实现一个电梯调度系统,更理解了软件设计的演进过程和面向对象设计的核心原则。
设计与分析:单部电梯调度问题
系统设计演进
单部电梯调度问题经历了三次迭代,系统设计也随之演进。下面我将分析每次迭代的设计变化和改进。
第一次迭代:单一电梯类设计
在第一次迭代中,整个系统仅由一个电梯类实现,该类负责所有功能,包括状态管理、请求队列管理以及调度算法。虽然实现了基本功能,但这种设计违反了单一职责原则,导致电梯类承担了过多责任,不利于维护和扩展。
代码结构的主要问题是:
- 电梯类既要处理电梯的状态管理,又要管理请求队列
- 请求逻辑和电梯移动逻辑混合在一起
- 缺乏良好的模块化设计
第二次迭代:多类协作设计
第二次迭代引入了多类设计,将系统拆分为以下几个主要组件:
- 电梯类(Elevator):专注于电梯本身的属性和行为
- 乘客请求类(Request):表示乘客的请求信息
- 请求队列类(RequestQueue):管理电梯内部和外部的请求队列
- 控制类(Controller):负责整体调度逻辑
这种设计符合单一职责原则,每个类只负责一项职责,大大提高了代码的可维护性和可扩展性。同时,新的设计还增加了对无效请求的处理,包括楼层数超限和重复请求的过滤。
第三次迭代:引入乘客类
第三次迭代将乘客请求类(Request)替换为乘客类(Passenger),进一步完善了系统设计。乘客类不仅包含源楼层和目的楼层信息,还可以计算请求方向。这种设计更接近实际电梯系统的工作方式,在处理外部请求后,会自动将目的楼层加入到内部请求队列,模拟乘客进入电梯后按下目的楼层按钮的行为。
代码度量分析
SourceMonitor提供了对代码复杂度、代码行数、方法调用深度等多项指标的分析,帮助我们客观评估代码质量的变化。
第一次迭代代码度量
第一次迭代中,由于所有功能都集中在电梯类中,导致类的行数过多,方法复杂度高,内聚性低,耦合度高。具体表现为:
- 类的代码行数过多
- 方法的圈复杂度较高
- 继承深度低(单一类设计)
第二次迭代代码度量
第二次迭代后,拆分为多个类使得每个类的规模适中,职责明确,复杂度降低:
- 各类的代码行数均衡
- 方法的圈复杂度降低
- 类之间形成了良好的关联关系
第三次迭代代码度量
第三次迭代进一步优化了设计,特别是引入乘客类替代请求类,使系统更加符合实际场景:
- 代码总行数略有增加,但功能更完善
- 类的职责更加清晰,内聚性进一步提高
- 方法的平均行数和复杂度保持在合理水平
数据结构选择
在三次迭代中,数据结构的选择也发生了变化:
- 第一次迭代中,简单使用Queue接口实现请求队列
- 第二次迭代中,使用LinkedList实现RequestQueue类,分别存储内部请求和外部请求
- 第三次迭代中,LinkedList存储的元素类型从Integer和ExternalRequest变成了Passenger
LinkedList作为请求队列的实现有以下优势:
- 支持高效的头部和尾部操作,适合队列先进先出的特性
- 支持动态扩容,适应不确定数量的请求
- 提供丰富的集合操作API,便于管理请求
核心算法分析
三次迭代中,电梯调度算法的核心思想保持一致,但实现方式有所不同:
- 初始方向确定:根据第一个请求相对于当前位置的方向确定电梯初始移动方向
- 电梯移动:电梯按当前方向逐层移动,每到达一层都检查是否需要停靠
- 停靠判断:检查当前楼层是否有需要处理的请求
- 方向调整:处理完当前方向所有请求后,如有反向请求则转向;否则进入IDLE状态
在第三次迭代中,算法增加了处理外部请求后将目的楼层加入内部请求队列的逻辑,这使得算法更加复杂但也更符合实际电梯运行逻辑。
关键方法分析
Controller类的processRequests方法
这是整个系统的核心方法,负责协调电梯运行和请求处理。方法流程如下:
- 确定电梯初始运行方向
- 电梯逐层移动
- 每到达一层时,检查是否需要停靠
- 如需停靠,处理当前楼层的请求
- 根据剩余请求情况调整电梯运行方向
ProcessRequests方法流程图
Elevator类的move方法
负责电梯的移动操作:
- 根据当前方向调整楼层
- 更新电梯状态
- 输出当前楼层和方向信息
Controller类的shouldStop方法
决定电梯是否需要在当前楼层停靠:
- 检查内部请求是否包含当前楼层
- 检查外部请求中是否有当前楼层且方向相符的请求
- 根据检查结果决定是否停靠
ShouldStop方法流程图
RequestQueue类的方法
在第三次迭代中,RequestQueue类提供了管理请求队列的方法:
- addInternalRequest:添加内部请求
- addExternalRequest:添加外部请求
- getInternalRequests:获取内部请求队列
- getExternalRequests:获取外部请求队列
踩坑心得
在实现单部电梯调度系统的过程中,我遇到了一系列有趣的挑战和问题,这些经历为我提供了宝贵的编程经验和教训。
问题1:类设计职责不清
在第一次迭代中,我将所有功能都放在电梯类中,导致该类过于庞大,难以维护。当需要添加新功能或修复bug时,经常需要大幅修改代码,容易引入新的问题。
解决方案:按照第二次迭代的要求,将系统拆分为多个类,每个类负责单一职责。这种设计让代码结构更清晰,也更容易维护和扩展。
问题2:请求处理顺序不正确
在实现过程中,一个常见问题是电梯有时会忽略当前楼层的请求,而是先去处理其他楼层的请求,然后再回来。
解决方案:修改Controller类的shouldStop方法,优化停靠判断逻辑:
// 伪代码展示
boolean shouldStop(int floor) {
// 检查内部请求
if (queue.getInternalRequests().contains(floor)) {
return true;
}
// 检查外部请求
for (Passenger passenger : queue.getExternalRequests()) {
if (passenger.getSourceFloor() == floor) {
// 检查方向是否一致
if (passenger.getDirection() == elevator.getDirection() ||
elevator.getDirection() == Direction.IDLE) {
return true;
}
}
}
return false;
}
输入:
1
20
<3,DOWN>
<2>
<5,UP>
<1>
<4,DOWN>
<3>
<2,DOWN>
<8>
<4>
END
错误输出
Current Floor: 1 Direction: UP
Current Floor: 2 Direction: UP
Open Door # Floor 2
Close Door
Current Floor: 3 Direction: UP
Current Floor: 4 Direction: UP
Current Floor: 5 Direction: UP
Current Floor: 6 Direction: UP
Current Floor: 7 Direction: UP
Current Floor: 8 Direction: UP
Current Floor: 7 Direction: DOWN
Current Floor: 6 Direction: DOWN
Current Floor: 5 Direction: DOWN
Current Floor: 4 Direction: DOWN
Current Floor: 3 Direction: DOWN
Open Door # Floor 3
Close Door
Current Floor: 2 Direction: DOWN
Current Floor: 1 Direction: DOWN
Open Door # Floor 1
Close Door
Current Floor: 2 Direction: UP
Current Floor: 3 Direction: UP
Open Door # Floor 3
Close Door
Current Floor: 4 Direction: UP
Current Floor: 5 Direction: UP
Open Door # Floor 5
Close Door
Current Floor: 6 Direction: UP
Current Floor: 7 Direction: UP
Current Floor: 8 Direction: UP
Open Door # Floor 8
Close Door
Current Floor: 7 Direction: DOWN
Current Floor: 6 Direction: DOWN
Current Floor: 5 Direction: DOWN
Current Floor: 4 Direction: DOWN
Open Door # Floor 4
Close Door
Current Floor: 3 Direction: DOWN
Current Floor: 2 Direction: DOWN
Open Door # Floor 2
Close Door
正确输出:
Current Floor: 1 Direction: UP
Current Floor: 2 Direction: UP
Open Door # Floor 2
Close Door
Current Floor: 3 Direction: UP
Open Door # Floor 3
Close Door
Current Floor: 2 Direction: DOWN
Current Floor: 1 Direction: DOWN
Open Door # Floor 1
Close Door
Current Floor: 2 Direction: UP
Current Floor: 3 Direction: UP
Open Door # Floor 3
Close Door
Current Floor: 4 Direction: UP
Current Floor: 5 Direction: UP
Open Door # Floor 5
Close Door
Current Floor: 6 Direction: UP
Current Floor: 7 Direction: UP
Current Floor: 8 Direction: UP
Open Door # Floor 8
Close Door
Current Floor: 7 Direction: DOWN
Current Floor: 6 Direction: DOWN
Current Floor: 5 Direction: DOWN
Current Floor: 4 Direction: DOWN
Open Door # Floor 4
Close Door
Current Floor: 3 Direction: DOWN
Current Floor: 2 Direction: DOWN
Open Door # Floor 2
Close Door
问题3:同一楼层多个请求处理不完整
当一个楼层既有内部请求又有外部请求时,电梯可能只处理其中一个请求就继续移动,导致另一个请求被遗漏。
解决方案:修改请求处理逻辑,确保处理完当前楼层的所有请求后才继续移动:
// 伪代码展示
void processCurrentFloorRequests(int floor) {
boolean hasProcessed = false;
// 处理内部请求
if (removeInternalRequest(floor)) {
hasProcessed = true;
}
// 处理外部请求
Passenger passengerToRemove = null;
for (Passenger passenger : queue.getExternalRequests()) {
if (passenger.getSourceFloor() == floor &&
(passenger.getDirection() == elevator.getDirection() ||
elevator.getDirection() == Direction.IDLE)) {
passengerToRemove = passenger;
// 将目的楼层加入内部请求
queue.addInternalRequest(passenger.getDestinationFloor());
hasProcessed = true;
break;
}
}
if (passengerToRemove != null) {
queue.getExternalRequests().remove(passengerToRemove);
}
if (hasProcessed) {
elevator.openDoors();
}
}
输入:
1
10
<3>
<3,UP>
<3,DOWN>
end
错误输出
Current Floor: 1 Direction: UP
Current Floor: 2 Direction: UP
Current Floor: 3 Direction: UP
Open Door # Floor 3
Close Door
正确输出:
Current Floor: 1 Direction: UP
Current Floor: 2 Direction: UP
Current Floor: 3 Direction: UP
Open Door # Floor 3
Close Door
Open Door # Floor 3
Close Door
问题4:方向判断逻辑有缺陷
在某些情况下,电梯会过早地改变方向,导致部分请求未能按预期处理。
解决方案:改进determineDirection方法,考虑所有请求:
// 伪代码展示
Direction determineDirection() {
if (elevator.getDirection() == Direction.UP) {
// 检查上方是否还有请求
boolean hasRequestsAbove = false;
for (Integer floor : queue.getInternalRequests()) {
if (floor > elevator.getCurrentFloor()) {
hasRequestsAbove = true;
break;
}
}
for (Passenger passenger : queue.getExternalRequests()) {
if (passenger.getSourceFloor() > elevator.getCurrentFloor()) {
hasRequestsAbove = true;
break;
}
}
if (hasRequestsAbove) {
return Direction.UP;
} else {
// 检查下方是否有请求
boolean hasRequestsBelow = false;
for (Integer floor : queue.getInternalRequests()) {
if (floor < elevator.getCurrentFloor()) {
hasRequestsBelow = true;
break;
}
}
for (Passenger passenger : queue.getExternalRequests()) {
if (passenger.getSourceFloor() < elevator.getCurrentFloor()) {
hasRequestsBelow = true;
break;
}
}
if (hasRequestsBelow) {
return Direction.DOWN;
} else {
return Direction.IDLE;
}
}
} else if (elevator.getDirection() == Direction.DOWN) {
// 类似的逻辑检查下方和上方的请求
// ...
} else {
// IDLE状态,根据请求位置决定方向
// ...
}
}
经验总结
通过解决这些问题,我学到了几个重要的编程经验:
- 设计先行:良好的类设计是成功实现复杂系统的基础。每个类应该有清晰的职责,避免职责过度集中。
- 测试驱动:针对不同的输入场景进行测试,特别是边界情况和特殊情况,能够及早发现问题。
- 渐进式开发:从简单的实现开始,逐步添加功能和优化,是一种有效的开发策略。
- 重构优化:当发现设计或实现有缺陷时,不要犹豫,及时重构代码以适应新的需求和发现的问题。
改进建议
尽管三次迭代已经大大改进了电梯调度系统的设计,但仍有多个方面可以进一步优化。
1. 设计模式应用
当前实现已经体现了一些设计模式的思想,但可以更系统地应用以下设计模式:
- 状态模式:将电梯的不同状态(停止、移动、开门、关门)封装为独立的状态类,简化状态转换逻辑。
- 命令模式:将不同类型的请求封装为命令对象,统一处理方式。
// 伪代码展示:状态模式
interface ElevatorState {
void move();
void stop();
void openDoors();
void closeDoors();
}
class MovingState implements ElevatorState {
// 实现移动状态的行为
}
class StoppedState implements ElevatorState {
// 实现停止状态的行为
}
// 电梯类使用状态对象
class Elevator {
private ElevatorState state;
public void setState(ElevatorState state) {
this.state = state;
}
public void move() {
state.move();
}
// 其他方法
}
2. 多电梯支持
当前系统只支持单部电梯的调度。在实际应用中,多部电梯的协同调度更为常见,也更具挑战性。可以扩展系统以支持多电梯场景,实现更复杂的调度策略。
这需要引入一个中央调度器(Dispatcher),负责将请求分配给最合适的电梯,以最小化乘客等待时间和电梯移动成本。
// 伪代码展示
class ElevatorDispatcher {
private List<Elevator> elevators;
private List<RequestQueue> queues;
public void dispatchRequest(Passenger passenger) {
Elevator bestElevator = findBestElevator(passenger);
int index = elevators.indexOf(bestElevator);
queues.get(index).addExternalRequest(passenger);
}
private Elevator findBestElevator(Passenger passenger) {
// 根据各种因素选择最合适的电梯
// 如距离、当前方向、负载等
}
}
3. 异常处理机制
当前实现在处理异常情况(如无效楼层请求)时比较简单,只是忽略这些请求。可以实现更完善的异常处理机制:
- 记录详细日志,便于后期分析和调试
- 在适当的地方使用自定义异常类,提高代码可读性
- 实现错误恢复机制,提高系统鲁棒性
// 伪代码展示
class InvalidFloorException extends Exception {
public InvalidFloorException(String message) {
super(message);
}
}
class RequestQueue {
public void addInternalRequest(int floor) throws InvalidFloorException {
if (floor > elevator.getMaxFloor() || floor < elevator.getMinFloor()) {
throw new InvalidFloorException("Invalid floor: " + floor);
}
internalRequests.add(floor);
}
// 其他方法
}
5. 用户界面
当前系统使用命令行接口接收请求和显示电梯状态。在实际应用中,一个直观的图形用户界面(GUI)可以大大提高用户体验。
可以使用Java Swing或JavaFX开发一个简单的GUI,显示电梯的实时状态、楼层位置和请求队列,并允许用户通过图形界面发送请求。
总结
通过完成题目集5到7,特别是单部电梯调度这样的复杂问题,我在Java编程和软件设计方面有了显著的成长。
学习收获
- 面向对象设计能力:通过三次迭代,我深入理解了面向对象设计的核心原则,特别是单一职责原则和类的协作方式。
- 设计演进思想:学会了如何通过迭代设计不断改进系统,从单一类逐步演进到多类协作的完善系统。
- 代码重构技能:培养了重构代码的能力,能够识别设计缺陷并进行有效改进。
- 问题分析能力:提高了分析复杂问题的能力,能够将大问题分解为可管理的小问题。
- 测试思维:养成了全面测试的习惯,能够设计测试用例覆盖各种场景和边界条件。
需要进一步学习的方向
- 设计模式:虽然在实现中隐含使用了一些设计模式思想,但还需要系统学习各种设计模式,以及它们在实际项目中的应用。
- 并发编程:电梯调度系统在实际应用中涉及多线程和并发控制,这是一个需要深入学习的重要领域。
- 测试驱动开发:学习如何通过编写测试用例驱动开发过程,提高代码质量和开发效率。
- 性能优化:深入了解Java性能优化技术,如JVM调优、算法优化等,提高系统性能。
课程建议
基于本次题目集的学习体验,我有以下建议:
- 渐进式难度设计:题目集已经体现了很好的难度递进,未来可以考虑在每个题目集内部也实现更细致的难度递进。
- 更多实际案例:增加更多来自实际项目的案例,帮助学生理解理论知识在实践中的应用。
- 小组协作项目:引入小组协作项目,培养学生的团队协作能力和项目管理能力,例如多人合作开发一个更复杂的电梯系统。
- 代码评审环节:引入代码评审环节,让学生互相评审代码,学习他人的优秀实践和思路。
- 更多交流讨论:增加课堂讨论和在线交流的机会,让学生分享解题思路和遇到的问题。
通过这三次题目集的学习,我不仅提升了Java编程技能,更重要的是培养了解决复杂问题的思维方式和方法论。从单一类设计到多类协作,从基本功能到完善系统,这种渐进式的学习过程使我对软件设计和开发有了更深入的理解。这些能力不仅适用于Java编程,也是软件开发和工程实践的普遍原则,将对我未来的学习和工作产生深远影响。