【作业】BUAA OO 第二次博客作业
第二单元电梯调度策略作业总结
一、三次作业分析
(一)第一次作业
-
设计模式:第一次作业采用的是傻瓜调度来设计单部目的选层电梯。总体来说是十分简单。我将此次作业分成了两个线程,一是电梯线程,二是读入线程。此外除了
Main
类外,还加入了等待队列作为命令的中转站。- 读入线程结构较为简单,只有
getRequest
方法,用于与输入接口对接,然后将读取到的PersonRequest
存入等待队列waitingQueue
。 -
waitingQueue
采用的是线性表类型,添加了取指令的pull
方法,每次remove第一个元素,然后返回该Request,使电梯取得指令。 -
Elevator
的设计也很简单,除去基本的开关门、进出乘客、取指令和行进动作外,我增加了去往目的楼层的方法。这个方法是睡眠从当前楼层到目标楼层的时间,仍然无法摆脱面向过程编程的阴影。由于无法输出每层的状态,并且不支持捎带,所以在之后的作业中都无法使用了。电梯每次执行是针对一个指令,即每次只取一个指令,之后对这个指令进行“行进到目标楼层-开门-上人-关门-走-开门-下人-关门”的操作。
- 读入线程结构较为简单,只有
-
类图分析:从图中可以看出,方法复杂度不高,除了输入较复杂,其他都还好。而类的复杂度主要集中在
Elevator
。本次的代码并不复杂,结构很清晰,所以看起来很清爽。
-
问题筛查:本次作业虽然简单,但是仍有许多地方需要改进。
- 首先,拓展性很差是个致命的问题。这台电梯是从当前楼层直接走到目标楼层,中间不停靠,无法进行捎带。电梯运行方法封装级别较高,没有把操作更加原子化,导致下面的两次作业进行了近乎完全的重构。
- 其次,等待队列的数据结构过于简单,只能适应单个指令单次执行的需求。
- 最后,也是最重要的,我好像写了个单线程!!!(雾)只写了run方法,然后直接在
Main
里面调用了,隔壁大佬看完都绝望了。
bug分析:由于傻瓜调度单电梯过于简单,所以没有发现错误。
hack策略:大家写得都很好,只能空刀。
(二)第二次作业
-
设计模式:
- 第二次作业采用的是ALS捎带策略的单部目的选层电梯。相比于第一次作业,主要的改变为:每一层的信息需要输出,并且需要判断这个人是否可以捎带。所以设计模式的主要改动体现在
Elevator
类和WaitingQueue
类。 -
Elevaotr
添加了许多参数来表示电梯当前的状态——currentFloor
表示当前楼层,forwardStatus
表示前进方向,doorStatus
表示开关门状态,exeStatus
表示电梯启动状态,targetFloor
表示目标楼层。有了这些参数,电梯的运行可以抽象为状态之间的转换。电梯类新增的方法有showCurrentFloor
显示当前楼层,converter
楼层到线性表索引的转换器,getTargetFloor
寻找目标楼层,turnforward
表示当目标楼层与当前运行方向冲突时的转向函数,getTheFirst
表示当电梯服务队列空时接入的第一位客人信息,这里不是将指令加入服务队列,而是通过改变目标楼层实现电梯的运行。 -
WaitingQueue
等待队列类使用了新的数据结构。区别于第一次作业的一维线性表结构,本次采用了“分楼层——分方向”的嵌套型队列。每一层有向上和向下两个方向的队列,对应电梯按楼层和方向接人的服务方式。由于每一层每一个方向的需求都可以满足,所以接取后,可以全部清空,因此增加了clearRequests
方法。同时还增加了getFloorRequest
方法,用于在当前电梯目标楼层和等待队列最大/最小楼层之间作比较,从而选择新的目标楼层。 - 调整了整体结构之后,本次实验终于成为了正常的多线程电梯。分为了电梯线程和读入线程,这两个线程独立工作,共享等待队列资源。
- 第二次作业采用的是ALS捎带策略的单部目的选层电梯。相比于第一次作业,主要的改变为:每一层的信息需要输出,并且需要判断这个人是否可以捎带。所以设计模式的主要改动体现在
-
类图分析:从图中可以看出,本次代码复杂度主要集中在
Elevator
类,而在该类中,复杂度主要集中在Elevator.run()
、Elevator.perExe()
和Elevator.turnForward
三个方法中。-
Elevator.turnForward()
方法用于调整电梯行进方向,需要比较当前楼层与目标楼层的大小关系,然后依据当前的朝向选择是否改变方向。由于该方法需要访问WaitingQueue
类,需要判断服务队列serviceList
是否为空,并且需要找到当前的目标楼层,并且所调用的方法复杂度也不低,所以该方法复杂度较高。 -
Elevator.perExe()
方法是每经过一个楼层调用一次,完成开关门、接送乘客等操作。由于调用了许多复杂度很高的方法,这个方法也有较高复杂度。 -
Elevator.run()
方法集成了空电梯接客与有客电梯送客的情况,并且从等待队列类提取请求,所以复杂度很高。
-
-
问题筛查:
- 在debug过程中,出现了电梯送客成为空电梯之后,再次接客转向的问题。所以在
Elevator
类中增加了turnForward()
类。 - 耦合性高仍然是本次实验的致命的问题。在
perExe()
方法中,集成了电梯判断每种动作的方法。每一个方法都有时间顺序的联合,所以再加入其他方法就很不容易。 - 参数
exeStatus
是表征电梯运行状态的,它本来应当被放在相对较高的优先级的,但是由于思维混乱,我没能成功的给它控制权。 - 开关门操作融合了开关门时间,所以为第三次作业的加锁操作带来了很大的困难。
- 在debug过程中,出现了电梯送客成为空电梯之后,再次接客转向的问题。所以在
bug分析:在这次作业中,出现了一个很大的漏洞。当第一个请求是向下行的,但是在高于当前楼层时,电梯不会接受这个指令。由于我判断接受请求的方式是相同朝向,所以会导致无法接受这个请求。在修改时,每次电梯空载时,需要手动更改当前楼层与目标楼层,这样就可以使电梯先运动,到达服务楼层再判断能否接客。
hack策略:发现自己的bug,然后交上去(雾)。实际上,某些边界情况需要考虑,并且有高强度的大数量指令,可以搞掉傻瓜策略。
(三)第三次作业
- 设计模式:
- 和第二次作业不同的是增加了多电梯的协作,所以会出现一次无法送达的情况,需要拆分指令。特殊的是寻找中转站的策略。每一台电梯都有不同的中转站选择策略,详细如下:
-
A电梯
public int getShift(PersonRequest p) { int shift = 0; if (p.getToFloor() > 1 && p.getToFloor() < 15) { if (p.getFromFloor() < 1) { shift = 1; } else if (p.getFromFloor() > 15) { shift = 15; } } else { shift = 0; } return shift; }
-
B电梯
public int getShift(PersonRequest p) { int shift = 0; if (stops[converter(p.getToFloor())] == 1) { shift = 0; } else if (p.getToFloor() == 3) { if (p.getFromFloor() > 5) { shift = 5; } else if (p.getFromFloor() == 2) { shift = 1; } else if (p.getFromFloor() == 4) { shift = 5; } } else if (p.getToFloor() == -3) { shift = -2; } else if (p.getToFloor() > 15) { shift = 15; } return shift; }
-
C电梯
public int getShift(PersonRequest p) { int shift = 0; if (stops[converter(p.getToFloor())] == 1) { shift = 0; } else if (p.getFromFloor() == 3 && p.getToFloor() == 2) { shift = 1; } else if (p.getFromFloor() == 3 && p.getToFloor() == 4) { shift = 5; } else if (p.getToFloor() < 1) { shift = 1; } else if (p.getToFloor() > 15) { shift = 15; } else if (p.getToFloor() > 1 && p.getToFloor() < 15) { if (p.getToFloor() > 8) { if (stops[converter(p.getToFloor())] != 1) { shift = 15; } else { shift = 0; } } else { if (stops[converter(p.getToFloor())] != 1) { shift = 1; } else { shift = 0; } } } return shift; }
-
- 类图分析:由于增加了两部电梯,并且如果放在同一个电梯类里面,代码需要大面积重构,所以使用了三个类表示三部电梯。并且增加了Lock类,用作将需要上锁的对象上锁,因此复杂度较高。由于不方便将整个方法复杂度表格列出,下面只对复杂方法分析。
- 与上次的代码相同,复杂度多集中在
Elevator.run()
、Elevator.perExe()
和Elevator.turnForward
三个方法。此外本次接乘客的行为更加复杂,选用中转站需要判断,所以很复杂。 -
Lock
类调用了多个类,所以很复杂。
问题筛查:本次作业出现的问题是中转站切分指令和向等待队列投放运行后后半部分的指令问题。在运行完前一半指令后,后一半指令没有电梯接收,所以出现较大问题。解决方法是在循环跳出条件中加入其他电梯队列信息,以免某些电梯提前退出协作。
hack策略:发现自己的bug,然后交上去(雾)。
二、心得体会
第二单元给我的感觉是比第一单元舒服。相比第一单元算法更多的习题,第二单元侧重的是策略与写法。更加贴近实际的问题可能更适合我。
三、写在后面
不知不觉,学期已经过半,OO课程也已经完成了两个模块的学习。从面向对象的基础到多线程设计基础,我们离真正的面向对象编程越来越近了。回想两个月来熬过的夜、重构过的版本、删过的代码,还有与我并肩作战、给予我很多帮助的前辈与同学,心中不胜感激。感谢经历过的苦痛与挣扎,使我看到了困苦中仍存在的希望。“你必忘记你的苦楚,就是想起也如流过去的水一样”,愿剩余的半个学期过后,我们收获的喜乐远多于经历过的苦痛。