BUAA_OO_第二单元

一、设计策略分析总结

  本单元实验要求对进阶难度的电梯进行模拟,要求模拟乘梯人员发出输入请求,且能够让电梯处理请求并模拟运行、开关门、将乘客送到目的地,在电梯做出相应行为时输出一定的信息。

  在本次任务中,我的设计原则是“一个类只做一类事”,同时几乎是按照现实生活中的电梯进行调度的设计。

(一)模块设计

  第一次作业中,经过分析并考虑可扩展性,将程序总体分为三个模块:输入请求处理,请求列表,电梯与电梯方向,调度器。其中请求列表为所有模块的共享变量,用于保存请求并供调度器获取;电梯与电梯方向模块中,前者只执行开门关门、上行下行以及进出人的操作,后者只是一个枚举类型,供电梯保存运行状态;输入请求处理使用了一个线程,将读到的请求插入请求列表,在读到NULL时将请求列表记录为结束并结束线程;调度器使用了一个线程,在这个模块中查找合适的请求,并控制电梯的运行。

  第二次作业中,在电梯与电梯方向模块里增加了楼层类,主要作用是将可能存在空缺的可用楼层映射到1-n的连续数字,并且记录每个楼层是否有乘客的目的地。

  第三次作业中,增加了换乘人员及其列表模块,记录需要换乘的人和换乘路线。

  三次作业均只有主线程、输入请求线程及调度器线程三种。

(二)调度算法设计

单电梯调度算法:

初始化:当电梯状态为停止且请求列表中存在该电梯能够运送的请求时,获取第一个可用请求,并将方向设为前往该请求请求楼层的方向,目标楼层设为请求楼层。

目标楼层:当前电梯方向中,据当前楼层最近的楼层。

  每到达一层楼,判断该楼层是否有出电梯的请求和同方向进电梯的请求,若有则开门出人,并在能够进入的情况下(目前仅有容量限制导致不能进入)进入所有该层请求的人(包括同方向和异方向),重新计算目标楼层。继续向目标楼层前进。

  电梯到达目标楼层时,转换方向,若存在该方向的目标楼层继续前进,否则查看请求列表确定目标楼层。如果列表里也没有合适的,那么电梯状态设为停止,调度器进入wait状态。

多电梯调度算法:

  三个电梯调度器分别对满足自己运送条件的请求进行竞争,设置三种换乘:到达15层以上的在15层换乘、上行且需要到3层的在5层换乘,其余需要换乘的在1层换乘。

二、基于度量分析程序结构

指标说明:

  • LOC: Line of Code

  • NCLOC:Non-Commented Line Of Code

  • ev(G) 基本复杂度(Essential Complexity):衡量程序非结构化程度。

  • iv(G) 模块设计复杂度(Module Design Complexity):衡量模块判定结构,即模块和其他模块的调用关系。

  • v(G) 圈复杂度(Cyclomatic Complexity):衡量一个模块判定结构的复杂程度,数量上表现为独立路径的条数

(一)第一次作业

1.代码规模

类名LOCNCLOC
Main 13 13
Dispatcher 64 64
Elevator 77 77
ElevatorDirection 13 13
RequestInput 30 29
RequestList 35 35
总计 232 231

2.类的属性方法

调度器:

        

         在线程中不断调用lookforReq()方法,若无请求则进入wait状态或者结束线程。

 电梯方向:  

        

  对电梯的运行方向的枚举,上行下行或者处于暂停状态。reverse函数用于转换方向。

输入请求:

        

请求列表:

        

       涉及的所有操作均需要锁住,当读取到NULL时设为over。

 电梯:

         

     将电梯的一系列操作细分为一个个小方法,如开门关门,进人出人,并能记录电梯内部人员的请求。

3.度量

  

  可看出复杂程度较高的,主要为调度器中的查找请求的方法。这是将对电梯的所有操作都用一个方法来表示造成的。

4.类图

        

  使用单例模式,主线程创建请求输入线程和调度器线程,一个调度器控制一个电梯实例。

(二)第二次作业

1.代码规模

类名LOCNCLOC
Main 12 12
Dispatcher 138 135
Elevator 96 96
ElevatorDirection 13 13
FloorList 51 51
RequestInput 30 29
RequestList 35 35
总计 380 376

  与第一次作业相比,更改只出现在调度器、电梯及楼层类中。

2.类的属性方法

调度器: 

  

  调度器中,为了满足更优的调度算法,增加了若干关于请求的计算,其中findUppFloor()和findDownFloor()函数是为了寻找当前方向上的目标楼层。

楼层:

  

  doors数组类似于生活中电梯内部的表盘,记录哪一层有人要下电梯。

  getIndex() 用于转换不规则楼层信息与规则的序列。

3.度量

  同样也是一些起到控制作用的函数具有较高的复杂度。

4.类图

  

  依然是单例模式,主线程创建输入请求线程和调度器线程,一个调度器对应一个电梯实例及相应的楼层实例。

(三)第三次作业

1.代码规模

类名LOCNCLOC
Main 17 17
Dispatcher 194 189
Elevator 137 137
EleDirect 13 13
FloorList 66 66
RequestInput 44 43
RequestList 33 33
Person 75 75
PersonList 32 32
总计 611 605

  由于出现换乘的可能性,故增加人员列表导致了大部分类都发生一部分改变,在调度方面区别不大。

2.类的属性方法

 人员:

  

  对每一个人的请求创建每一个人的类,当需要换乘时才会对这个人进行下一步操作,不然就不用管了按前两次作业的处理来。

  divideReq() 为将该人的请求拆分成两个可达的请求,当第一个请求完成时,向请求列表中插入第二条请求。

换乘人员列表:

  

  如果没有换乘人员,那么一直空着。对该表的操作也是都应该加锁的。

3.度量

  感觉复杂度高的都是逻辑比较复杂的,其中牵扯到的操作比较多,看了看也不太好修改。

4.类图

  

   要比前两次的结构复杂,但是其实把RequestList换成PersonList,一直用后者操作也可以,但是那样需要改的比较多,怕改着改着出现问题(懒)。于是只简单增加了换乘人员类,各个类中增加部分语句即可。

(四)UML协作图

  本单元程序均只有三个线程,故三次作业UML协作图可统一展示。

 

(五)分析改进

1.优缺点分析

  优点:结构比较明确,电梯运行与控制分离;模块化,增加功能时只需要改变一部分,省时省力。

  缺点:存在冗余的情况,如Person类可继承PersonRequest类,但只重新建了个并重复的部分功能。

2.SOLID设计原则改进

  • Single Responsibility Principle(单一职责原则):每个方法基本上都只负责一项功能的执行,避免一个类做太多事情,符合。

  • Open Close Principle(开放封闭原则):除了调度算法整个更新以外,其他都是简单增加一部分方法代码,很少重构现象的发生,符合。

  • Liscov Substitution Principle(里氏替换原则):不存在子类的设计,符合。

  • Interface Segregation Principle(接口分离原则):不存在接口设计,符合。

  • Dependency Inversion Principle(依赖倒置原则):若增加新的电梯,只需要在FloorList中增加对应电梯的ID的信息,抽象程度较好。

三、错误分析

(一)存在错误

  在前两次作业中,处理好输入的结束并且把对共享变量的操作都加上锁之后,应该就没有线程上的bug了。

  第三次作业中,出现了CPU超时的情况,原因是判断wait的条件为“请求列表为空且电梯内为空”,但存在“请求列表不为空,但是请求该电梯不能满足”的情况,导致了这个调度器一直在轮询,从而超时。

  还有无法中断的情况,在前两次作业中,输入NULL时请求列表会设为状态结束,此时会唤醒一次所有线程,如果电梯wait了,会被唤醒并判断结束,终止线程。但第三次作业中,请求输入结束了,但是如果有换乘的,换乘完会插入请求列表,调度器判断进入wait状态,由于不再有变化,导致线程无法再次被唤醒并且一直wait下去。这需要考虑终止的各种情况。

 

(二)测试的方法

 1. 手工测试

  如对NULL的插入位置依次尝试。

 2. 自动测试

  编写java程序,自动生成测试样例,读取测试并按时间延时输出模仿手工输入,并检查输出结果,记录下错误的测试数据及结果供日后debug。

四、心得体会

  做电梯最重要的是理解多线程的思想,理解了之后程序便比较容易编写了,同时也能够知道莫名的bug出现位置的可能性。

  另一方面,计组中学到的工程化思想得到了应用,第一次作业时参考去年的电梯作业要求用了一天的时间进行设计,在后两周效果还不错,虽然性能分不算高,但是付出与收获的性价比很高…

posted @ 2019-04-22 22:57  -q  阅读(151)  评论(0编辑  收藏  举报