第一次FAFS电梯
设计策略
采用两个线程,一个输入输出线程,一个电梯线程。采用生产者消费者模式,中间的托盘为调度器,调度器中部分方法声明为synchronized。采用wait、notifyAll模式,如果请求队列为空则wait,如果输入请求或者输入null则notifyAll。输入线程结束的条件为输入null,电梯线程结束的条件为输入null且请求队列为空且电梯没有正在运行。由于是FAFS调度策略,于是请求全部保存在一个先入先出的队列中。
基于度量的程序结构分析
SOLID原则分析
SRP原则:实现的较好,inputhandler负责管理输入请求,dispatcher负责存放请求,elevator负责运送乘客。
OCP原则:(说实话我对这个原则不是很理解)实现的较好,第二次作业仍然沿用了第一次的类,只进行了小部分的修改。
LSP原则:没有使用类的继承。
ISP原则:没有实现接口
DIP原则:实现的较好,没有出现依赖关系倒置的情况。
第二次ALS电梯
设计策略
设计大体与第一次类似,加入了捎带。我的捎带策略是:
①从队列中取出一个最早来的请求作为主请求
②如果currentFloor和mainRequestFrom不是同一层,则电梯要从currentFloor到mainRequestFrom,在这个过程中捎带同方向的from和to在currentFloor和mainRequestFrom之间的请求。
③在mainRequestFrom到mainRequestTo的过程中捎带所有同方向的from在mainRequestFrom和mainRequestTo之间的请求。这种策略效果不是太好,性能分会比较低。
基于度量的程序结构分析
SOLID原则分析
SRP原则:实现的较好,inputhandler负责管理输入请求,dispatcher负责存放请求,elevator负责运送乘客。
OCP原则:实现的不好,第三次作业进行了架构方面的大改。
LSP原则:没有使用类的继承。
ISP原则:没有实现接口
DIP原则:实现的较好,没有出现依赖关系倒置的情况。
第三次SS-LOOK电梯
设计策略
因为第二次作业的性能分极低,于是我第三次没有沿用第二次的设计策略,而是采用了LOOK算法。首先电梯由一个变成了三个,我采用抽象工厂模式(其实可以使用简单工厂模式)生成了三个电梯类。其中抽象工厂为:
1 public interface ElevatorFactory { 2 3 static final int OPEN_TIME = 200; 4 static final int CLOSE_TIME = 200; 5 6 public abstract void runElevator(); 7 8 public abstract void walkIn(Request request); 9 10 public abstract void walkOut(Request request); 11 12 public abstract void openDoor(); 13 14 public abstract void closeDoor(); 15 16 public abstract void moveOneFloor(int sign); 17 18 public abstract int getSign(); 19 20 public abstract void changeDir(); 21 22 public abstract boolean canPark(); 23 }
我的做法是创建了三个不同的电梯类,分别实现这个接口,这样会造成类的数目过多且三个类之间的相似度特别高。其实可以创建一个电梯类并实现这个接口,然后把三个电梯之间不同的部分通过参数传进来,实现一个类的三个不同对象即可,这一点是需要改进的地方。
我采用的是四个线程(一个输入输出,三个电梯),因为少了一个调度器线程(思考过把调度器单独做成一个线程,但是觉得实现起来比较复杂而且收益不高),所以整个程序的设计模式就是一个生产者,三个消费者,三个消费者在托盘里抢请求。采用固定的换乘分配策略,具体分配策略如下表:
|
如果是需要换乘的请求,不能直接将它拆分成两个请求放在请求队列里,这样可能会有后半段先跑的逻辑错误。我的做法是对需要换乘的请求做出标记,在第一段请求执行完毕(输出”OUT-ID-FLOOR-NO.”之后),立即将取消标记并将后半段回写到请求列表中)。三个电梯起始是都规定一个行驶方向,电梯启动的条件是列表中存在它可以获取的请求,电梯转向的条件是当前行驶方向之后的楼层没有请求或者到达了顶层或者底层。
基于度量的程序结构分析
SOLID原则分析
SRP原则:实现的较好,inputhandler负责管理输入请求,dispatcher负责存放请求,elevatorA、elevatorB、elevatorC负责运送乘客。
OCP原则:自我感觉实现的教好,对电梯个数的扩展具有良好的封装性,但是由于没有第四次作业,具体效果不得而知。
LSP原则:没有使用类的继承。
ISP原则:没有实现接口
DIP原则:实现的较好,没有出现依赖关系倒置的情况。
Bug分析
三次作业自己强侧和互测均没有出现Bug,在找别人bug的过程中,使用了同学自己写的测评机,随机生成了六类数据
①boundary,只生成与边界有关的数据,即-3、-1、1、20层有关的数据
②concurrent,楼层和时间同时高并发,对超载和实时处理进行压力测试
③piggyBack,指令带有捎带的
④longInterval,指令之间的间隔较长,考察对null的处理。
⑤pureRandom,生成纯随机的数据
⑥up_down,生成上下来回翻转的数据
用这几类数据每类生成近千种,来扫每个人的程序,扫出了很多人存在概率性死锁的问题,但是因为不是百分之百的死锁,于是交上去很难命中。
这一单元和上一单元在测试方面的最大差别就是上一个单元可以直接在编译器中测试自己的程序,而且逻辑上的错误少,更多的是输入输出格式的问题和化简中存在的不仔细的方法。
而这一单元必须使用测评机对输入的时间戳进行处理才能进行评测,评测的难度增加导致互测变得更困难,没有测评机将无从下手。而且多线程存在很多不可复现的错误,需要用大量数据进行测试才有可能发现和找到错误。
心得体会
虽然这一单元的三次互测都没有出现问题,但是每次都在强测全爆的边缘试探。因为每次的中测都过于简单,于是在过了中测就没管了,同学在周二晚上写好评测机发给我,我测了几组数据就会发现有很严重的bug。第二次在强测结束前20分钟提交最后一个版本,第三次在强测结束前5分钟提交最后一个版本。这告诉我们程序不只是写完就完了,还要进行程序测试,而且程序测试更重要。