第一次Blog作业
一、前言
题目集5~7围绕电梯调度程序的迭代设计展开,逐步要求提升代码的可维护性与扩展性。
三次作业的知识点包括:需求分析,编译算法,合理规划类的设计,分析规划电梯的请求。
题量与难度:题目集5为基础单类设计,更多侧重需求的分析和算法的设计,题目集6为多类的拆分,题目集7引入新的类乘客类。
核心难点在于调度算法与类间协作,尤其是如何通过多类协作实现状态驱动的电梯行为。
二、设计与分析
- 题目集5:单类设计的困境与反思
核心问题:
高耦合性:电梯类Elevator承担了状态管理、队列处理、调度算法等多重职责,导致代码臃肿。例如,moveStep()方法需处理楼层移动、方向判断、请求检查,圈复杂度高达22(SourceMontor检测结果),远超合理阈值(建议≤10)。
可扩展性差:新增功能(如请求过滤)需修改主类逻辑,极易引入错误。
设计亮点与缺陷:
亮点:通过externalUpQueue和externalDownQueue分离外部请求方向,简化同方向优先逻辑。
缺陷:未处理无效请求(如超过最大楼层),导致测试用例覆盖率不足60%。
优化启示:
拆分职责:将队列管理、调度算法剥离为独立类。
引入状态模式:将电梯状态(移动、停止)封装为独立对象,减少条件分支。 - 题目集6:多类协作的实践与挑战
(1)类图与职责划分:
Elevator类:仅负责状态管理(当前楼层、方向)和物理移动。
RequestQueue类:封装请求的添加、过滤(去重、有效性校验)、排序(同方向优先)。
Controller类:解析输入、调用调度逻辑,充当系统“中枢”。
(2)调度算法实现:
方向优先级策略:电梯移动时优先处理同方向请求。例如,电梯上行时仅响应externalUpQueue中高于当前楼层的请求。
空闲状态处理:无请求时电梯停留在当前楼层,而非默认返回1层(与初始题目要求不同,需特别注意)。
(3)复杂度对比:
代码质量提升:Elevator类的圈复杂度从22降至8,方法平均行数从50行降至20行。
维护性增强:新增请求类型时,仅需修改RequestQueue而非主类逻辑。
(4)未解决问题:
乘客实体缺失:外部请求仍以<楼层,方向>形式存在,未体现“乘客”概念,限制后续扩展。 - 题目集7:乘客类引入与系统重构
(1)架构升级:
乘客类(Passenger):封装乘客的源楼层和目标楼层,解耦请求的发起与执行。
请求转换机制:处理外部请求后,自动将目标楼层加入内部队列。例如,外部请求<3,5>(从3层到5层)被处理时,电梯需在3层开门,并将5层加入内部队列。
(2)关键逻辑变更:
外部请求处理:
java
public void processExternalRequest(Passenger p) {
if (elevator.getCurrentFloor() == p.getSource()) {
elevator.openDoor();
elevator.addInternalRequest(p.getTarget());
requestQueue.remove(p);
}
}
调度策略调整:电梯需同时考虑内部队列和转换后的外部请求,优先级计算更复杂。
(3)性能与扩展性:
性能影响:因乘客对象创建和销毁,处理100个请求时耗时增加15%。
扩展性提升:支持多乘客请求的场景(尽管题目仍为串行),为后续并发处理奠定基础。
三、采坑心得 - 输入过滤的隐蔽缺陷
问题场景:题目集6中,输入<5,UP><5,UP>时未过滤重复请求。
根因分析:
RequestQueue使用List.contains()去重,但未重写Request.equals()方法,默认比较对象地址而非属性值。
解决方案:
java
@Override
public boolean equals(Object obj) {
if (obj instanceof Request) {
Request other = (Request) obj;
return this.floor == other.floor && this.direction == other.direction;
}
return false;
}
测试验证:通过JUnit测试用例验证重复请求过滤功能,覆盖率提升至85%。 - 方向优先级逻辑的误区
问题复现:电梯在4层向上移动时,收到外部请求<6,DOWN>后错误掉头。
(1)调试数据:
队列状态:内部队列包含5层,外部下行队列包含6层。
预期行为:继续上行至5层,清空内部队列后再处理6层。
实际行为:直接转向下行,导致5层请求被遗漏。
修复方案:在Controller中增加方向一致性检查:
java
List
.filter(r -> r.getDirection() == elevator.getDirection())
.collect(Collectors.toList());
if (!sameDirRequests.isEmpty()) processRequests(sameDirRequests);
3. 状态机未同步的严重后果
问题现象:电梯开门后未更新状态,导致连续移动时跳过楼层。
关键日志:
text
[ERROR] Floor 3: Door opened but state remains 'MOVING'
根因分析:openDoor()方法未将状态设为STOPPED,电梯仍按原方向移动。
修复代码:
java
public void openDoor() {
this.state = State.STOPPED; // 强制状态更新
System.out.println("Open Door");
}
四、改进建议
- 设计模式的应用优化
策略模式:将调度算法(如SCAN、LOOK)抽象为接口,支持动态切换。
java
interface SchedulingStrategy {
Request nextRequest(List
}
class SCANStrategy implements SchedulingStrategy { ... }
观察者模式:通过事件通知机制解耦电梯状态与界面更新,便于后续添加GUI。
-
数据结构优化
队列性能:使用TreeSet替代ArrayList实现请求队列,将查找复杂度从O(n)降至O(log n)。
缓存机制:为频繁访问的队列(如当前方向请求)添加缓存,减少实时计算开销。 -
测试体系的完善
边界测试:覆盖电梯位于最高层、最低层时的极端场景。
压力测试:模拟1000个请求验证系统稳定性,检测内存泄漏。
自动化测试:集成Jenkins实现持续集成,确保每次提交后的回归测试。
五、总结
-
核心收获
设计原则:通过三次迭代深刻理解SRP、DRY原则,类平均代码行数减少。
调试能力:掌握日志插桩、断点调试与覆盖率分析,定位效率提升50%。
架构思维:学会通过UML类图与序列图预判系统瓶颈,避免盲目编码。 -
待深化领域
设计模式:工厂模式、装饰器模式在复杂系统中的应用。
并发处理:尽管题目限制为串行,但现实场景需处理多电梯协同调度。 -
课程建议
阶梯式案例库:提供从简到繁的参考项目,展示如何从200行代码演进到千行级系统。
工具集成教学:引入JaCoCo(覆盖率)、SpotBugs(代码缺陷)等工具,提升工程化能力。
设计评审机制:组织小组互评类图与架构设计文档,促进最佳实践传播。
反思与展望:电梯调度问题本质是状态机与算法设计的结合体,三次作业从不同维度锤炼了系统设计能力。未来需进一步学习分布式系统设计,以应对更复杂的工业级场景。
浙公网安备 33010602011771号