电梯系列——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 第一次作业

类图

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

代码度量

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

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

主线程UML时序图

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

Elevator UML时序图

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

3.2 第二次作业

类图

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

代码度量

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

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

主线程时序图

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

Elevator UML时序图

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

3.3 第三次作业

类图

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

代码度量

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

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

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

主线程时序图

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

Elevator UML时序图

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

3.4 分析

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

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

四、测试中的bug分析

4.1 自我程序bug分析

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

         } else if (name == 'C' && !el.isInC(toFloor)) {
this.toNewFloor = toFloor;
if (el.isInA(toFloor)) {
this.toFloor = 1;
} else {
if (floor <= toFloor) {
this.toFloor -= 1;
} else {
this.toFloor += 1;
}
}
this.special = true;
}

含BUG的代码

         } else if (name == 'C' && !el.isInC(toFloor)) {
this.toNewFloor = toFloor;
if (el.isInA(toFloor)) {
this.toFloor = 1;
} else {
if ((floor <= toFloor && floor != 3) || (toFloor == 2)) {
this.toFloor -= 1;
} else {
this.toFloor += 1;
}
}
this.special = true;
}

修复后代码

此为设计上的考虑不周。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原则才能使程序具有更好的健壮性和扩展性,符合工业化的要求。

上一篇:Week3(9月23日):例子更Powerful更完整了,哇咔咔


下一篇:Nginx 高级配置-实现多域名HTTPS