OO第二单元作业——魔鬼电梯

简介

本单元作业分为三次

第一次作业:第一次作业要实现单部简单电梯,停靠所有楼层,无载客容量,性能分考量电梯运行时间。

第二次作业: 第二次作业实现多部电梯,电梯数量由初始化设定,每部电梯都停靠所有楼层,有相同载客容量上限,性能分考量电梯运行时间。

第三次作业:第三次作业实现多部电梯,初始三部,可通过指令动态增加。共分为三类电梯,三类电梯停靠楼层、载客上限、运行时间(上下及开关门)均有所不同。性能分考量电梯运行时间与乘客等待时间。

 

第一次作业

实现方式

 利用LOOK算法,电梯上下摆渡来接送乘客,每当所在的楼层有需要进电梯的乘客时便开门让该乘客进来,所在楼层有需要出去的乘客便开门让该乘客出去,电梯改变运行方向的条件是电梯运行方向上已经没有任何的请求(不需要上人或者下人)。

类图

 

 

代码规模

 

复杂度分析

 

 

分析:Scheduler知晓所有的请求,并且管理着请求队列(类似生产者消费者里的托盘),所有与请求队列有关的操作均在Scheduler里执行,因而不符合高内聚低耦合原则。

 

第二次作业

 

实现方式

第二次作业在第一次作业的基础上进行迭代,仍然采用的是LOOK算法。本来试图尝试为每一个请求去分配一个电梯的,但是由于对多个电梯同时运行时的唤醒机制掌握不熟、当电梯满员时的已经分配的任务是否需要返回到总请求队列等细节没有思考清楚,因此即使按照这个思路写了一版代码但最后并没有开始Debug,也没有采用这种方式,而是沿用第一次作业的代码写了一个多台电梯无脑抢指令、谁先到位则谁先接送人的智障LOOK调度。

 

类图

代码规模

复杂度分析

分析:为了线程安全,每个电梯并没有自己的队列,就连在电梯里的人也没有由电梯来管理,这就导致电梯需要频繁的和调度器进行交互,而调度器是无脑synchronized的,这就导致运行效率低下。同时,由于电梯里几乎所有操作都要经过调度器,于是耦合度较高的Scheduler成功把Elevator也给拉下了水。总而言之,这样的设计在总体的执行顺序上是近乎单线程的顺序执行(因为一次只能有一个电梯访问Scheduler),同时耦合度较高,但是优点在于Scheduler对外的接口明确,修改调度算法只需要在Scheduler内部进行修改即可。

第三次作业

实现方式

这次终于用上了本来打算在第二次电梯就使用的任务分配队列,具体的实现是这样的:每个电梯维护一个response队列(里面的所有人一定在同一层)和一个inElev队列,每当电梯到达一层时便更新一次response队列,判断电梯里的人和response队列里的人要去哪一层以及Requests队列里有没有更近可以去接的人,若有则更新response队列;然后取最近的目的地并往那个方向走一层。这种调度方法比起上一次的优点在于:1.避免了电梯一窝蜂去抢一条指令,但最终只会有一个电梯抢到,这种做法在荷载大的时候效率比较低。2.可以确保A电梯在忙完了15~20楼的所有任务后才下-3~1楼,如果采用LOOK算法的话必定会出现A电梯来回奔波的惨象,导致有些测试点性能分低。

关于换乘,我采用的是静态换乘(类似打表的方式),换乘表格如下:

 

类图

代码规模

复杂度分析

分析:本次设计在电梯类中维护了inElev队列和response队列,调度器只维护总队列和负责分配,这样子提高了运行效率,使得并发执行的程度更高,但是由于每个楼层更新的算法复杂度较高不可避免导致数据飘红,但是总体设计上比上次有较大改善。

 

Bug分析

自己的BUG:三次作业在中测、强测中均没有发现Bug,但是第三次作业由于我误以为新增电梯的名称只能是X1 X2 X3中的一个导致在互测中被人疯狂HACK这个问题。

但是我于第一次作业Hack他人1次,第三次作业Hack他人2次。发现他人Bug的主要途径有三类:

1.在自己做作业的时候曾经犯下的错误、自查到Bug的测试数据记录下来用于Hack他人。

2.利用数据生成器生成一些暴力数据,对于功能进行全面检测,尝试找到他人程序中一些功能性漏洞。

3.自行编写特殊数据(例如多个相同起止楼层的人测超载,最后一条指令是加入电梯的指令等等)。

扩展性分析

1.SRP(Single Responsibility Principle)——单一职责原则

​在前两次作业中,由于电梯没有自己所维护的队列,所以把所有队列都放在Scheduler,导致Scheduler比较复杂,但是第三次作业中Scheduler只负责分配任务和其他线程安全的控制,维护队列、上下楼层均交给电梯来完成,ElevReader类只负责获取请求,这样的做法职责分明,对外接口清晰,符合单一职责原则。

2.OCP(Open Close Principle)——开闭原则

​从第一次作业到第二次作业只添加了部分内容,第二次作业到第三次作业由于调度算法不同以及队列位置不同修改了一些接口,但这都是主动的代码架构调整而非写不下去时被迫的重构,是可以接受的。对于第三次作业的代码,如果新增需求,比如要关闭电梯的话只需要在调度器中退出条件多加一条即可,再比如如果要按照乘客总重量来限制载重量只需要修改调度器中的分配策略即可,所以在OCP方面我的设计是比较好的。

3.LSP(Liskov Substitution Principle)——里氏替换原则

​本次作业没有采用继承的方式,无需进行LSP分析。

4.ISP( Interface Segregation Principle)——接口隔离原则

​本次作业没有采用接口的方式,但是所有的类中的public方法均提供相对独立的操作,符合LSP原则。

5.DIP(Dependency Inversion Principle)——依赖倒置原则

​Scheduler类中有调度,Elevator类负责执行,而由于信息的传递是单方向的(由Scheduler类发出指令指挥Elevator)所以对于电梯参数的修改不会影响到Scheduler,符合DIP原则。

 

心得体会

通过这一单元的学习,我有了很大的收获。在三次作业中不断尝试新的调度策略,最终在最后一次作业中取得了接近满分的性能分。此外,我还学会了自行编写对拍器。对拍器的数据生成器主要负责生产对应的数据,可供选择的生成数据由边界数据、多换乘数据、大荷载量数据、密集数据、全覆盖数据、纯随机数据,对拍器还有一个OnTimeInput的java文件负责定时向程序中投放输入数据,还有一个checker的jar包负责检查运行结果是否正确。经过一系列覆盖测试我确保了我在线程安全上以及换乘上没有遗漏,做完这些以后,强测便顺理成章的全部通过。关于线程安全的问题,由于我在设计的时候第一轮设计大框架,第二轮补充具体细节,因此在第二轮设计的时候我便已经把线程安全以及唤醒机制纳入到了考虑范围,针对唤醒以及各个线程之间的联系做了细致而全面的规划,从设计上杜绝了Bug的产生,这样的设计方法使我几乎没有出过线程安全的Bug(经历了强测、互测以及我自己评测机多个晚上跑都没有出Bug因此可以认为没有Bug)。综上,总体而言对于这一单元的作业我总体是比较满意的。

猜你喜欢

转载自www.cnblogs.com/wuchengfeng/p/12709148.html