2019-OO-第二单元总结

第一部分:三次作业设计策略分析

第五次作业

电梯建了一个类,电梯管理着楼层信息,包括哪一层有哪些人要接,哪些人要送

调度器建了一个类,管理电梯的调度,维护一个Target队列,每次新来一个人,调度器会更新Target,电梯每进来一个人,也会更新Target;

程序开始,电梯从调度器得到一个target,并运行过去,如果尚未达到Target,且中途遇到某一层外面有人,还是会选择开门,将那个人拉进来。每次开门,都会启动remove和pick两个线程,remove负责送人,pick负责接人,同时修改调度器的Target

第六次作业

结构同上,只不过优化了电梯从调度器哪里getTarget的方法,用来提升了性能

第七次作业

这次作业三个电梯,且都有人数上限,还有换乘,于是设计了一个等级调度。每个电梯配备一个子调度器,电梯从自己的自调度器得到下一个目标;此外还有一个高级调度器,负责读取请求,并分配给相应的子调度器。

乘客分两种情况,一种是可以通过某一个电梯直达,一种是需要换乘。这次作业中可以发现任何一种请求最多换乘一次就可以完成,因此设计起来可以稍微overfit一下这个性质。

每一个请求到来,先去检查ABC谁可以单独完成这个任务,如果没有,就去看AB,AC,BC组合谁可以完成任务。确定好哪两个电梯之后,实际上,因为任何两个电梯重合的可停楼层是可以看出来的,可以用判断函数零点是否在某个区间内的算法选定一个在ToFloor和FromFloor之间可以换成的楼层(如果都不是零点就随便选一个,效率先靠后放)。此外为了让PersonRequest记录上换乘楼层的信息,写了一个Wrapper。举例来说,比如分配到的两部电梯是AB,首先高级调度器把这个任务wrap之后分给A,A完成之后检查当前层不等于真正的ToFloor,于是返回给高级调度器,高级调度器查一下分配记录,然后转给B,B执行完成后看到当前层等于真正的ToFloor,于是任务完成。

此外,为了防止总是一个电梯去执行任务,我设计了轮换机制。假设这次执行任务的是A,下次从B开始匹配,如果这次是B,下次从C开始,以此类推。

调度策略(不一定最优)

维护两个队列,Upper和Lower,分别代表所有在当前层上面的目标楼层和当前层下面的目标楼层;

求出四个值:

UpperMin:完成Upper中所有目标(包括接送),需要访问到的楼层最小值;

UpperMax:完成Upper中所有目标(包括接送),需要访问到的楼层最大值;

LowerMin:完成Lower中所有目标(包括接送),需要访问到的楼层最小值;

LowerMin:完成Lower中所有目标(包括接送),需要访问到的楼层最大值;

这四个值每次都现算复杂度很高,所以就每次来一个新目标就更新维护一下;

记当前层为cur,不管先向上还是先向下,如果没有新的指令进来,一路到底再换方向比中间多次换方向时间要短

如果先向上,耗时为:

U = UpperMax - cur + UpperMax - Min(LowerMin, UpperMin) + LowerMax - Min(LowerMin, UpperMin)

如果先向下,耗时为:

L = cur - LowerMin + Max(UpperMax, LowerMax) - LowerMin + Max(UpperMax, LowerMax) - UpperMin

如果U小先向上走,否则向下

第二部分:基于度量来分析自己的程序结构

程序分析使用IDEA JAVA的插件Calculate Metrics对每次作业的类和方法进行基于度量的复杂度分析,参数意义解释如下:

1)ev(G) :Calculates the essential complexity of each non-abstract method. Essential complexity is a graph-theoretic measure of just how ill-structured a method's control flow is. Essential complexity ranges from 1 to v(G), the cyclomatic complexity of the method. 
(即表示一个方法的结构化程度,值越大则程序结构越“病态”)

2) iv(G):Calculates the design complexity of a method. The design complexity is related to how interlinked a methods control flow is with calls to other methods. Design complexity ranges from 1 to v(G), the cyclomatic complexity of the method. Design complexity also represents the minimal number of tests necessary to exercise the integration of the method with the methods it calls. 
(即表示一个方法与其调用的其他方法的紧密程度,值越大则越紧密)

3)v(G):Calculates the cyclomatic complexity of each non-abstract method. Cyclomatic complexity is a measure of the number of distinct execution paths through each method. This can also be considered as the minimal number of tests necessary to completely exercise a method's control flow. In practice, this is 1 + the number of if's, while's, for's, do's, switch cases, catches, conditional expressions, &&'s and ||'s in the method. 
(即表示一个方法的穷尽每一条路径所需要的次数,也就是循环复杂度)

4)OCavg :Calculates the average cyclomatic complexity of the non-abstract methods in each class. Inherited methods are not counted for purposes of this metric.

5)WMC :Calculates the total cyclomatic complexity of the methods in each class.

第五次作业

类图

UML图如下,除去主线程这次一共用了4个线程,电梯调度器各一个,为了增加并行效率,remove和pick乘客的操作是新开一个线程进行的,电梯门打开之后直接run两个线程,判断都结束之后再关门;

度量

可以看到电梯部分复杂度较高,因为各种运行开关门处理包括调度算法都在这个程序里了

Sequence Diagram

线程协作关系图如下

第六次作业

类图

大致思路和上次作业一样,remove和pick线程合成了一个,主线程负责起接收request的工作,Target队列的维护工作从Elevator中分离出来,单独建立了Scheduler类

度量

相比上次,各个类分工更加明确,Scheduler由于调度算法比较复杂,所以整体复杂度还是偏高


Sequence Diagram

线程协作关系图如下

第七次作业

类图

主线程负责起接收request的工作,调度器分两级,Scheduler给SubScheduler分配任务,SubScheduler给电梯线程分配任务,remove和pick分开,因为remove需要单独进行一些与Scheduler交互的操作

度量

两级调度器的复杂度较高,因为涉及调度算法和任务分配



Sequence Diagram

三次作业SOLID原则分析

SRP(单一责任原则):第一次作业中电梯类兼顾了调度算法,违背了单一功能原则(S),在后两次作业中进行了改进,调度是一个类,电梯是一个类;

OCP(开放封闭原则):第二次作业中调度器和电梯分离,放到第三次作业中可以分别对应到子调度器和电梯,结构上不怎么需要修改,说明程序具有一定的扩展性能。

LSP(里氏替换原则):作业中没有使用到继承,以后会多多尝试

ISP(接口分离原则):作业中没有使用到接口,以后会多多尝试

DIP(依赖倒置原则):本次作业层次比较简单,所以没有涉及这个问题,后面会注意

第三部分:程序bug分析

前两次强测中没有发现bug

第三次强测的bug比较可惜,原因在于旧代码一句话没有删干净。第三次作业是在第二次的基础上写的,前两次作业由于只有一个电梯,且不限载重人数,为了提升性能,我设计的算法为:电梯在get到下一个目标之后,运行途中如果检测到当前层有人,便会停下开门,即便当前层不是目标层,之后继续执行未完成的目标。第三次作业中对这个算法做了简化,计划写成未达到目标层中间都不会停(并不是最优调度,但可以降低算法复杂度),结果没有删掉前两次作业执行中间停的语句。导致的结果是,如果中间停下,原先没有完成的target直接被丢失,造成了任务的失败。且中测由于指令密度比较低,没有发现这个bug。

吸取的教训就是,如果要在原有代码的基础上更改,最好不要直接复制粘贴旧代码,就算是基本没有改动,还是照着手动敲一遍好,否则后面很难检查出来。

第四部分:心得体会

第一次写多线程任务,学到了很多新知识,比如多线程用法,避免死锁方法,一些wait notifyAll的组合技巧;

任务复杂的话很容易写出bug,这个时候,让每个对象做自己的事情就很重要;

在原有代码基础上构建新任务的代码,最好照着改不要复制粘贴,一样的部分对着敲一遍,不一样的部分重写,否则bug很难发现;

代码风格检查对代码风格帮助很大;

posted @ 2019-04-21 23:14  jwhhhhhh  阅读(205)  评论(0编辑  收藏  举报