电梯调度程序设计迭代之路:从单类到职责分离的面向对象实践"
第一次迭代:单类设计(面向过程思维)
需求背景
设计一个电梯类,具体包含电梯的最大楼层数、最小楼层数(默认为1层)当前楼层、运行方向、运行状态,以及电梯内部乘客的请求队列和电梯外部楼层乘客的请求队列,其中,电梯外部请求队列需要区分上行和下行。
电梯运行规则如下:电梯默认停留在1层,状态为静止,当有乘客对电梯发起请求时(各楼层电梯外部乘客按下上行或者下行按钮或者电梯内部乘客按下想要到达的楼层数字按钮),电梯开始移动,当电梯向某个方向移动时,优先处理同方向的请求,当同方向的请求均被处理完毕然后再处理相反方向的请求。电梯运行过程中的状态包括停止、移动中、开门、关门等状态。当电梯停止时,如果有新的请求,就根据请求的方向或位置决定移动方向。电梯在运行到某一楼层时,检查当前是否有请求(访问电梯内请求队列和电梯外请求队列),然后据此决定移动方向。每次移动一个楼层,检查是否有需要停靠的请求,如果有,则开门,处理该楼层的请求,然后关门继续移动。
使用键盘模拟输入乘客的请求,此时要注意处理无效请求情况,例如无效楼层请求,比如超过大楼的最高或最低楼层。还需要考虑电梯的空闲状态,当没有请求时,电梯停留在当前楼层。
设计问题
1.单一类承担所有职责:电梯类(Elevator)不仅包含电梯状态(楼层、方向等),还负责请求队列管理(innerQueue、outQueue)、调度逻辑(decideDirections)、移动逻辑(move)和输出控制(door)。
代码臃肿:方法复杂度高,违反单一职责原则,难以维护和扩展。
1类图

2代码分析

总结:方法平均复杂度10
于是第一次的大作业,对这个类的单一职责原则还不是很清楚,所以代码中只有一个主类和电梯类,加起来214行代码,代码中的注释也很少,导致代码的可读性和理解性偏低,有四分之一的分支语句,分支结构带来很多执行路径,使程序逻辑复杂性增加,调试和测试工作量也会增大,并且较多的方法调用语句,调用关系复杂,所有的核心逻辑都在电梯类一个类中,导致工作量大,代码难理解direction中也很多if-else语句,代码总体上看起来偏向于面向对象的结构,还是有很大的改进空间的。
优点:快速实现功能,适合简单场景。
缺点:类职责混乱,违反 SRP,难以应对需求变化(如新增乘客类或修改请求格式)。
问题:这次代码的主要问题是构建方向,判断方向当时卡了好久,后面根据每一个情况单独列出来的一种方向,并接触到了arraylist,开始会没判断是否为空导致越界
第二次迭代:引入职责分离(题目集6:遵循SRP原则的改进版本
)
这第二次的电梯作业是在第一次作业的基础上进行迭代,在乘客请求不合理,具体为输入时出现连续的相同请求,程序自动忽略相同的多余输入
遵循单一职责原则,拆分原电梯类的职责。
新增乘客请求类、队列类和控制类,明确各组件职责。
职责拆分
1.电梯类(Elevator):仅负责电梯状态(楼层、方向、状态)和基础移动逻辑。
2.请求队列类(RequestQueue):管理内外请求队列,处理请求去重和有效性检查。
3.控制类(ElevatorController):负责调度逻辑(方向决策、开关门控制)。
4.乘客请求类(PassengerRequest):封装外部请求的楼层和方向
请求去重:在RequestQueue中检查重复请求(如连续的<3><3><3>过滤为<3>)。
职责分离:调度逻辑从Elevator迁移到ElevatorController,队列管理由RequestQueue负责。
类图

java
// RequestQueue处理请求去重public void addInnerQueue(int floor) {
if (elevator.checkRequest(floor) &&
(innerQueue.isEmpty() || innerQueue.get(innerQueue.size()-1) != floor)) {
innerQueue.add(floor);
}}
代码分析:

oElevator.java:57 行,35 条语句,分支占比 5.7%,2 次方法调用,1 个类,平均每个类有 11.00 个方法,每个方法平均 1.64 条语句,最大复杂度 3,最大深度 3,平均深度 1.54,平均复杂度 1.27。
oElevatorController.java:180 行,136 条语句,分支占比 41.2%,185 次方法调用,1 个类,平均每个类有 5.00 个方法,每个方法平均 25.40 条语句,最大复杂度 51,最大深度 6,平均深度 3.74,平均复杂度 19.40(复杂度较高)。
oMain.java:39 行,32 条语句,分支占比 18.8%,23 次方法调用,1 个类,1 个方法,每个方法平均 28.00 条语句,最大复杂度 7,最大深度 4,平均深度 2.47,平均复杂度 7.00。
oPassengerRequest.java:17 行,10 条语句,无分支(0.0%),0 次方法调用,1 个类,平均每个类有 3.00 个方法,每个方法平均 1.33 条语句,最大复杂度 1,最大深度 2,平均深度 1.30,平均复杂度 1.00(复杂度最低)。
oRequestQueue.java:53 行,27 条语句,分支占比 7.4%,23 次方法调用,1 个类,平均每个类有 9.00 个方法,每个方法平均 1.33 条语句,最大复杂度 10,最大深度 3,平均深度 1.48,平均复杂度 2.67。
因为有了第一次的完成所以相对的工作量不会很大,也更有思路,相较于第一次的题目集,这次的代码基本符合了类的单一职责原则,方法调用频繁,但是代码的注释依然还是没有,严重影响了代码的可读性和可维护性,然后判断方向那里还是一如既往的高;平均每个类的方法较多,方法规模小,功能单一,方法整体会有重复;整体代码复杂程度适中,但存在局部复杂点,代码存在局部复杂度高的地方,还有改进的地方。
总结
优点:代码结构清晰,各类职责明确,符合 SRP 原则,便于后续扩展。
缺点:外部请求仍以简单楼层和方向表示,未体现乘客的完整上下文(如起点和终点)。
第三次迭代:引入乘客类(题目集7:引入Passenger类)
第三次的大作业,取消乘客请求类,新添加了乘客类,输入的方式也不一样了,外部请求由之前的<请求楼层数,请求方向>修改为<请求源楼层,请求目的楼层>,当电梯处理该请求之后(该请求出队),要将<请求源楼层,请求目的楼层>中的请求目的楼层加入到请求内部队列(加到队尾),但是题目逻辑和第二次的大作业大差不差。
外部请求格式改为<请求源楼层,请求目的楼层>,引入 ** 乘客类(Passenger)** 封装完整请求。
电梯处理外部请求后,将目的楼层自动加入内部队列。
职责调整
1.乘客类(Passenger):封装源楼层和目的楼层,替代原PassengerRequest。
2.请求队列类(RequestQueue):存储外部请求的乘客对象,处理后将目的楼层加入内部队列。
3.控制类(ElevatorController):在开关门逻辑中,将外部请求的目的楼层添加到内部队列。
外部请求处理:在RequestQueue中解析源楼层和目的楼层,并生成方向(UP/DOWN)。
目的楼层入队:当外部请求处理完成(乘客进入电梯),将目的楼层加入内部队列。
类图:

java
// RequestQueue添加外部请求public void addOutQueue(int sourceFloor, int destinationFloor) {
String direction = destinationFloor > sourceFloor ? "UP" : "DOWN";
// 去重逻辑:检查源楼层、目的楼层和方向是否重复
if (elevator.checkRequest(sourceFloor) && elevator.checkRequest(destinationFloor) &&
(outQueue.isEmpty() || !重复条件)) {
outQueue.add(sourceFloor);
outDestinationQueue.add(destinationFloor);
outQueueDirections.add(direction);
}}
// ElevatorController处理开关门时,将目的楼层加入内部队列private void door() {
// 处理外部请求时
if (currentFloor == outQueue.get(0) && ...) {
int destination = outDestinationQueue.get(0);
outQueue.remove(0);
outDestinationQueue.remove(0);
innerQueue.add(destination); // 目的楼层入队
}}分析代码

于与第二次大作业相差不大,所以代码的行数和语句数还有分支语句也没有变化很大,依旧是没有注释,类和接口数多了一些,方法规模较小,平均深度还行,但是最大深度较大,局部逻辑嵌套较深,增加理解与调试难度,依然存在局部复杂度过高的问题,将整体复杂度降低了不少也是满足了类的单一职责原则。
总结
优点:通过乘客类建模真实场景,请求处理更符合业务逻辑;职责进一步细化,系统扩展性增强。
最佳实践:严格遵循 SRP 原则,每个类仅负责单一职责,通过类间协作实现复杂功能。
三次迭代总结:面向对象设计的核心原则
迭代阶段 核心问题 设计原则应用 关键改进点
第一次迭代 类职责混杂,可维护性差 无 快速实现功能
第二次迭代 单一职责原则缺失 拆分职责到独立类 引入队列类、控制类
第三次迭代 业务模型抽象不足,请求上下文缺失 领域建模(乘客类) 封装完整请求流程,优化调度逻辑
经验教训
1.decideDirections() 方法中嵌套大量 if-else 分支,重复判断 currentFloor 与队列首元素的关系,导致代码可读性差、维护困难。
2.没有判断数组越界导致运行超时陷入死循环
3.在处理数据的输入时,对正则表达式的使用不是很熟悉,开始的时候根本没想到使用正则表达式,在查阅资料后才完成了输入的处理,但是针对队列的处理时,有点懵,不知道该使用几个数组来满足数据的输入与储存,
4.通过这三次迭代,我们逐步从面向过程思维转向面向对象设计,深刻体会到单一职责原则在构建可维护系统中的重要性。
浙公网安备 33010602011771号