1.设计策略
第一次作业:
第一次作业刚刚了解多线程,不是很熟悉,一股脑地用了许多本能结合起来的线程。但经过复杂地线程安全处理,测试还是通过了。
由controler启动unloader,loadercontroler,mover,loader线程。分别卸人,装人,移动电梯,从输入获取请求。
第二次作业:
第二次作业相比于第一次,多了一个调度器,控制将乘客分发到哪一个电梯。但我对于架构没有改动。
第三次作业:
第三次作业深感前两次代码耦合度过高,且缺乏面向对象地特色。重构后耦合度大大降低。
通过一个template作为中介,运用了生产者-消费者模式,相比于前两次,将耦合度大大降低。但作为消费者的controler在换乘乘客时,又可作为生产者,将换乘人员加入template。
二、第三次作业可扩展性
功能设计
第三次作业各部件间耦合度较低,由controler负责运行逻辑,再调用elevator的load、unload、move等方法,符合SRP--单一职责原则。如果引入更多复杂的调度方法,仅需修改controler的运行逻辑,继续调用elevator的方法;若需要增加电梯的功能,则增加elevator相关的属性和方法,由controler调用即可。
template作为中间容器,仅仅具有中转作用,与其它类之间的依赖低,扩展性高。
terminal作为最高层线程,接受输入。对request进行处理后,调用template方法即可。扩展性好。
第三次作业较为符合SRP原则,故功能设计方面扩展性好。
性能设计
本次作业采用的是改良版的ALS算法,对于换乘楼层动态更新。但据说LOOK算法是否吃香。仅需在controler类中修改调度算法,再调用elevator的load方法即可。
三、基于度量分析程序结构
第一次作业
第二单元作业难点在于线程安全,而线程安全与架构紧密相关。前两次作业架构较为简单,即将“生产”职能与“消费”职能放到同一个Controler线程中,然后将Passenger交给Elevator线程自由消化。
但在Elevator的移动,上下人,操作中,多此一举,将每个功能交给一个线程执行。第三次作业觉得过于复杂,变放到一个controler中统一执行。
前两次通过复杂的线程安全操作,在前两次作业中,线程控制线程并未出现错误。
代码行数统计
代码类复杂度统计
UML图
第二次作业
第二次作业与第一次作业相比,仅仅增加了一个调度器,控制将乘客分派给哪个电梯。由于第一次作业中,我的电梯线程仅需乘客便可自动运行,所以第二周较为迭代较为顺利,一晚上就写完了,过了中测。但没注意到讨论区与官方接口的改变,导致强测直接翻车。。。实在太长记性了!!!!!!debug过程也就改了个输入方式,就顺利过了。。。
行数统计
类复杂度统计
第三次作业
由复杂度统计可见,前两次作业elevator复杂度较高,因为我将大部分功能集合到elevator类中。第三次作业,我痛心疾首,决定重构。
增加template中间层,让terminal负责初始输入passenger,controler取出passenger,再控制elevator运行,此时elevator只有基础的功能,比如上下人,移动,一切操作逻辑交给controler完成,降低了耦合度。同时controler也能作为消费者,向template中输入换乘的passenger。
行数统计
复杂度统计
UML图
由此可见,第三次作业解耦效果比较显著,所有类的复杂度普遍降低,没有出现红色的部分。同时精炼的架构更加适合迭代(尽管没有机会再坐电梯了)
四、分析bug
第一次作业
第一次作业由于线程间的互相调用问题,导致死锁,最终修改了实现方式通过。
第二次作业
没注意到官方接口的改变,使用了两个system.in。导致强测爆炸,其余无bug。
第三次作业
线程不稳定的缘故,在极端情况下,线程过早终止。加上sleep(100)后消除波动。
五、Hack策略
第一次作业
第一次数据缺乏特殊性,故随机生成起始于结束楼层,到来时间可相同,也可不同。
第二次作业
与第一次数据相差不大。
第三次作业
第三次作业数据较为复杂,可从时间,是否需要换乘两方面来考虑。到来时间可分外同时与不同时,与是否需要换乘进行随机组合,产生测试数据。hack5次。
六、心得体会
- 一定要仔细阅读讨论区与官方接口!!!!!!!!!!!!!!!!!!!!
- 磨刀不误砍柴工,一个好的架构抵得过任何花里胡哨的操作。(面向对象设计无原则)
- 自动化测试十分重要。掌握自动化测试,不仅可以方便测试环节,在设计正确性检查的时候,更有助于理解题意。
- 善于利用工具测试。比如各种多线程测试工具,相比printf方法有着不一样的效果