一、问题描述
本单元作业主要为多线程入门。具体作业为电梯调度——目的选层式电梯,即乘客的请求同时包含了位置楼层和目标楼层。三次作业总体的需求差异不大,在第三次增加了多电梯和关于停靠楼层和电梯人数的限制,主要导致了换乘需求的出现。
事实上,第二次作业完全复制了第一次作业,第三次增加了一点微小的改动(复制了三次)
二、程序设计
整体构架
几乎完全模拟现实生活中的电梯。
典型的生产者-消费者模型。输入为生产者,电梯为消费者,大楼为托盘。在多电梯时增加一个(分解者)调度器用来抛请求。
整体的思路是使用队列来管理每一个楼层的请求。楼与电梯分别拥有一个嵌套_ArrayList_来管理相应楼层的请求队列。楼层号用数组存,有效下标从1开始(方便继承第一次作业的写法)。注意楼的下标为出发楼层,而电梯的下标为目标楼层。
ArrayList<ArrayList<Request>> waitingLine = new ArrayList<>(24);
ArrayList<ArrayList<Request>> passenger = new ArrayList<>(24);
(对于使用的数据结构,_ArrayList_其实并不是一个好的选择——线程不安全且为数组,不利于增删操作。)
多线程:sleep+轮询:简单粗暴不会出错。
乘客:Request
方法:出入电梯;
换乘:众所周知,我们写的电梯里的都是假人 然而出于对真实电梯情况的模拟,我还是将换乘的策略放到了Request里而非交给调度器。因为当乘客处于电梯里时,无法获取其他电梯的运行状态。
一个朴素的换乘策略如下表(前提:有换乘必要):
起使楼层 | 目标楼层 | 途经楼层 |
---|---|---|
-3~-1, 3 | ANY | 1 |
2~15(不含3) | ANY | 最近不为3的奇数层 |
2~15(不含3) | -3 | 1 |
2~15(不含3) | 16~20 | 15 |
16~20 | ANY | 15 |
如何管理换乘请求:
保证顺序:先执行第一步,再执行第二步。
管理方案:初始请求和换乘请求作为两个无差别请求,在第一个请求完成之后,改变from和to的值,作为船新的请求被抛回请求队列,保证了时间的先后顺序。
好处:换乘与否与其他类无关,可顺畅复用之前代码。
电梯:Elevator
与楼的交互:
- 当电梯内没人时,需要设置到楼的任务结束标志(第三次的三电梯需要)。
- 从电梯拿到Request。
运行算法:LOOK
与实际的最常见的运行逻辑一致:在需要运行到的最高层和最底层来回走动,每次开关门时更新最高最低楼层。
接人策略:前两次与第三次略有不同。开门的条件都是一致的,即有人要下或有同方向的人要上。不同之处在于,前两次一旦开门就要把该楼层所有的人接进来。而第三次由于有容量限制,即使开门也只能接与运行方向一致的乘客,避免以后“挤不上”。且需要先下后上。
楼:Building
主要是管理需求队列和统一控制终止信号。
调度器(伪):Dispatcher
(没有写优化的调度器只是没有感情的 丢 人 机器)
获取一个请求,根据预先初始化的数组判断有哪些电梯可乘坐(请求已被拆分,所以至少有一个电梯可乘坐)。在其中选择一个人数少的,丢进去。
三、BUG分析
来回跑不接人问题
现象:当高层的人要往上走,低层的人要往下走时,电梯会开始自闭在两端来回跑而不开门接人。
原因:到极高和极低层的时候还没来得及改变方向,拒绝接受方向不一致的人。
解决:在最高和最低点多判断一次。
异常终止问题
现象:提前终止或者不终止。
原因:终止位设置不全面。
解决:将输入、楼层人数、电梯人数的结束信号都放到楼里,各设备需要时自取。三楼停靠问题
现象:不该在三楼停的电梯在三楼停下。
原因:拆分请求到最近奇数楼的时候忘了3楼为最近奇数楼的情况(好傻)。
解决:特判一下。
四、代码分析
第一次作业
代码规模
复杂度
类图
第二次作业
代码规模
复杂度
类图
第三次作业
代码规模
复杂度
类图
SOLID原则
- Single Responsibility Principle(单一职责原则):每个类只完成自己对应的功能,每个方法只含有单一功能。
- Open Close Principle(开放封闭原则):三次作业没有重构,几乎直接复用模块,但免不了为了适应不同情况对代码以及方法进行一定的修改。(比第一单元次次重构要好不少)
- Liscov Substitution Principle(里氏替换原则):(貌似还未涉及到)。
- Interface Segregation Principle(接口分离原则):(未涉及)。
- Dependency Inversion Principle(依赖倒置原则):对于电梯类来说,做得还不够好。第三次由于电梯类的设计不够抽象,为了一些微小但繁琐的修改重写出了三个电梯类……。
五、总结
最初的难点在于开始多线程编程。理解之后程序就变得容易编写起来。
这次做得最好的是没有照搬指导书上的算法,而是按照真实电梯的思路去设计。在第一次电梯用的时间长一点,但之后几乎没有花费什么功夫,并且性能还不错(可惜第三次手滑出了bug)。对于面向对象的理解,也比第一次更加清晰了。