电梯系列——OO Unit2分析和总结

一、摘要

 本文是BUAA OO课程Unit2在课程讲授、三次作业完成、自测和互测时发现的问题,以及倾听别人的思路分享所引起个人的一些思考的总结性博客。主要包含设计策略、代码度量、BUG测试和心得体会等内容。

二、设计策略分析

2.1 第一次作业

 第一次作业较为简单,主要由Elevator线程和InputHandler线程互相合作完成,是消费者、生产者模式,InputHandler生产AllRequest类中的Request对象存在ArrayList中,电梯从中取出Request对象并完成送乘服务。该ArrayList即是共享资源,需要实现互斥。Elevator在内部有人的情况下先完成内部任务,当内部人员为空时去接Request中的乘客,若Request也为空,且输入未完成,则sleep。InputHandler每次增加Request对象都会唤醒电梯。若输入完成,则电梯下班。

2.2 第二次作业

 本次作业与上次作业的区别并不大,可捎带的实现在电梯每次到达某一楼层时进行一次是否捎带的判断。如果符合捎带条件,开门后的处理流程和第一次基本是一致的,需要注意的线程安全问题基本没有改变。

2.3 第三次作业

 本次作业中有多部电梯,并且电梯有各自运行的楼层,于是某些情况下乘客需要转乘来到达目标楼层。本人在设计中是通过对乘客类的修改实现,记录乘客的目标楼层,并先把目标楼层替换为中转楼层,在乘客出门后将增加一个请求。在电梯是否捎带的策略设计中,每部电梯只捎带从自己运行楼层出发,目的地也为自己运行楼层的请求,或者从自己运行的非换乘楼层出发的请求。设计中需要保证所有可能的请求都能顺利完成,不会出现无法到达的情况或者没有电梯响应请求的情况。也增加了新的线程安全问题,需要重新考虑电梯下班的条件,以及增加请求时也需要唤醒sleep的电梯线程。

2.4 对调度策略和实用性的思考

 本次作业中,有很多指导书中的要求只是对实际情况的一种抽象,存在某些细节与实际不符合,也有考虑不周的情况。

例如对负重,现实中一定不会按人数,而会按照具体的重量来决定,那么在乘客输入请求时,电梯是无法预判是否会超载的。因此,基于超载预判的调度策略优化在现实中并不合理。

其次,作业中从未思考乘客的自主选择,除了反悔等,乘客可能提前下电梯、采用自己的换乘方案等。

最后,某些整体时间缩短的策略可能导致个别乘客等待时间的过长,可能会引起使用者的不满。

2.5 对优化的思考

 本部分仅仅考虑在指导书规定中的优化,将有与上一节讨论(即与实际应用)矛盾或不合理的地方,仅可能带来性能上的提升。

首先,电梯在不载人的时候可以运行到某个特定楼层,例如中间楼层,或者换乘楼层,或者基于乘坐数据集的最可能接客地点。

其次,预判电梯将超载时,如果请求可以由其他电梯完成,同时调度其他电梯到该楼层。

还有基于当前所有请求的最优调度模拟。如果请求可以由不同电梯完成,或者楼层的访问顺序可以改变,可以预先计算所有调度方案所用时间,再选择最快的方法。但此方法对性能有较大影响。

三、程序规模统计

3.1 第一次作业

类图

代码度量

主线程UML时序图

Elevator UML时序图

 

3.2 第二次作业

 类图

 代码度量

 主线程时序图

 Elevator UML时序图

 

3.3 第三次作业

 类图

 代码度量

 主线程时序图

 Elevator UML时序图

3.4 分析

 优点:设计中可扩展性强,后两次作业没有对之前的作业进行大规模的重构,类图具有一定相似性,设计中基本符合SOLID原则中的里氏替换原则、接口隔离原则、依赖倒置原则。

 缺点:单一责任原则不是非常符合,Elevator的run、canTake、RunningCheck等方法复杂度过高,对Elevator类还可以进行拆分,可以将优化、调度策略等单独用一个类实现;第三次作业中不同的电梯设计不符合开放封闭原则,三部特殊电梯可以使用继承的方式实现。

四、测试中的bug分析

4.1 自我程序bug分析

本单元三次作业中,本人自己的bug仅仅只有第三次作业有1个bug,具体如下。

 1         } else if (name == 'C' && !el.isInC(toFloor)) {
 2             this.toNewFloor = toFloor;
 3             if (el.isInA(toFloor)) {
 4                 this.toFloor = 1;
 5             } else {
 6                 if (floor <= toFloor) {
 7                     this.toFloor -= 1;
 8                 } else {
 9                     this.toFloor += 1;
10                 }
11             }
12             this.special = true;
13         }

含BUG的代码

 1         } else if (name == 'C' && !el.isInC(toFloor)) {
 2             this.toNewFloor = toFloor;
 3             if (el.isInA(toFloor)) {
 4                 this.toFloor = 1;
 5             } else {
 6                 if ((floor <= toFloor && floor != 3) || (toFloor == 2)) {
 7                     this.toFloor -= 1;
 8                 } else {
 9                     this.toFloor += 1;
10                 }
11             }
12             this.special = true;
13         }

修复后代码

此为设计上的考虑不周。BUG位于People类的构造器中,即在分析特殊乘客的转乘路线时,设计出现了错误,导致乘客不能正确地转乘,从而无法到达目标楼层。

在本单元我的程序中,并没有出现有关线程安全问题的bug。

4.2 互测其他人bug分析

本单元中互测时遇到的bug也比较少,例如请求完成电梯线程sleep后唤醒时出现的重复到达楼层的问题,和第三次作业中特殊楼层请求不能被正确完成的bug。基本都为设计上的小问题,没有遇到线程安全的问题。

五、Hack策略分析

5.1 Hack策略

Hack分别基于代码和统一共性问题进行测试。统一的问题如

线程安全问题的测试主要通过阅读代码来进行判断,这次互测时也没有发现线程安全方面的问题,因此没有特意设计基于某个线程安全bug的针对性数据输入。

5.2 与第一单元的差异

第一单元中,学生主要出现bug的地方都是关于输入输出格式方面的问题,Hack主要基于指导书要求中容易被忽视或者设计上易出现错误的数据,而程序功能性问题比较少。本单元中使用统一接口后,bug只剩下功能性问题和线程安全问题。

六、心得体会

6.1 线程安全

本单元中开始了多线程的设计,在以前简单地学习JAVA中我对多线程有一点点了解,但之前从未考虑过安全问题。通过理论课程的学习和三次作业的练习,大概了解了保证线程安全的重要性和方法。本单元的练习中,主要是输入结束和电梯暂时无任务、电梯下班等需要仔细考虑,输入完成后不意味着电梯就已经完成任务,电梯内仍然可能有人,而第三次作业中,输入完成、电梯内无人后仍然可能有人还没到达目标楼层,这些都是要考虑的地方。而且有关线程安全的测试也更加困难,不一定会引起错误而且具有不可再现性,需要在设计中考虑周全并进行多次测试。

6.2 设计原则

本单元的设计方面现实性比较强,易从实际角度分析电梯的调度要求,可能出现的各种情况等,功能的设计方面思考起来比较容易。在设计原则方面,由于功能的复杂总体较低,有些原则没有顾全,例如单一责任原则,本身电梯需要完成的调度还算比较简单,所以没有分离设计,对更复杂功能的程序实现是存在问题的。而基本上没有使用继承、抽象等,无法体现里氏替换原则。本单元只是对多线程设计的简单初探,之后的更多要求更加复杂的设计中需要更加考虑到SOLID原则才能使程序具有更好的健壮性和扩展性,符合工业化的要求。

原文地址:https://www.cnblogs.com/kortez/p/10762253.html