OOP 第二章作业总结
实现策略
-
这里结合一下我画的第三次作业的时序图(可能有画的不好的地方)来叙述一下我的实现逻辑。最开始主线程负责创建必要的线程(输入、调度线程)与请求队列类实例;输入线程负责与人进行交互,将获取到的请求放入请求队列;调度线程则每次从请求队列中取出一个请求,将其分发给三个电梯,若需要拆分,则新建一个次级调度线程,将第二部分的请求的分发任务交给它来实现;电梯线程只需要负责模拟自己电梯的运行即可。
-
本次作业共享数据的同步互斥主要采用的是 synchronized 方法,再配以 wait notifyAll 等通信方法,完成了线程之间的协作。
程序结构分析
类图分析
-
第一次作业类图分析
-
主函数类负责创建电梯线程和输入线程,Request 类负责实现请求队列的功能,Input 类负责将输入的请求放到请求队列里面,电梯线程负责调用 ELevator 类实现的方法来模拟电梯运行。感觉这样设计还挺合理的。
-
-
第二次作业类图分析
-
在上一次设计之上,将 ElevatorRun 类的功能(模拟电梯运行线程)放到 Elevator 类里,新增 Schedule 类充当调度器,即将请求队列中的请求取出并分发给电梯线程,并且由它创建电梯线程,主类只负责创建 Schedule 和 Input 两个线程。其余地方设计与上一次作业一样。
-
-
第三次作业类图分析
-
在上一次设计之上,将原有的 Request 类重命名为 ReqQueue 更符合该类所实现的功能。
-
新增 Request 类,这个类是为了复制 PersonRequest 类并向其中增添了 Finish 位,来标志当前请求是否被解决了,这一点在拆分请求中对于第二条请求的阻塞条件判定会用到。(这里用代理的话,就能更简单的实现)
-
新增 SubSchedule 类用于实现拆分请求的二级调度。
-
在本次作业中,对于请求拆分的处理,我是想实现一个二级调度,当需要拆分的时候,新建一个子调度线程(让他负责拆分后请求的第二部分的分发)。按道理来说,子调度类应该是主调度类的子类,但是我当时认为没有必要再为子调度类复制主调度类的那些成员域,感觉只需要调用主调度类的分发请求的方法就可以了,所以就让主调度类成为了子调度类的一个成员变量,但这样一来又有违于主子这个概念,感觉还是继承要好一点。
-
代码静态分析
- 由于是 eclipse 用户,所以代码静态分析使用的是 Designite jar 包。
- 三次作业的类复杂度分析
- 三次作业的 LCOM、FANIN、FANOUT 指标都非常相近,LCOM 指标小这点是好的,但是后面两个指标都为 0,表明了我的结构并没有按照分层设计这三次作业,更多的是平级的互相合作,这一点在后续的作业中有待改进。(但总觉得也不至于为 0 吧,有点质疑这个分析包)
- 三次作业的方法复杂度来说,CC 指标较上一次有显著的下降了,第一次平均值是1.3,最大值是4;第二次平均值是2.5,最大值是8;第三次作业平均值是2.7,最大值12。
- 第二次作业 CC 值最大的方法是 Elevator 类的 deal 方法,该方法模拟电梯接送乘客,所以每到一层需要判断是否开门,以及最后一个电梯里的乘客送达之后,判断等待队列里面是否有乘客需要接送,有则将其视为主请求,并接着运行。仔细想了一下,里面确实有一些判断语句能够用一个方法封装起来,这样既提高代码复用性,又能够降低复杂度。
- 第三次作业 CC 值最大的方法便是 Schedule 分发请求的方法,因为这个方法需要判断一个请求,是能给哪一个电梯,或者需要拆分,而这个做出判断的过程,我是使用了一个大的 switch 判断语句,故 CC 复杂度过大,但是感觉这个是不得已的事情。
- 三次作业的 code smell 分析
- Magic Number:三次作业中随着作业的难度的递增,魔数出现的数量逐渐增加,到了第三次已经有了 33 次了,检查了一下代码,主要出现在 Elevator 与 Schedule 类,确实有很多地方可以改为宏定义的方式,下一次需要注意,有助于提高代码可读性。
- Complex Conditional:这个主要出现在第二次和第三次作业中的电梯请求分发的判断地方与电梯开关门逻辑之中。对于后者有改进之处,因为我是把判断有没有人要上来与出去的语句分别放在了开关门方法里面,这一处应该拆分一下职能。
自我程序 bug 分析
- 后两次作业分别被找出一处 bug
- 第二次作业:bug 是在电梯运行模拟处,完全是逻辑错误,在两个地方改变了电梯的 aimFloor 但是 direction 并没有改变,导致电梯上天了。
- 第三次作业:bug 是我的线程在评测机上出现了线程不稳定的情况,反正本地是跑出来没问题,原因是我 的次级调度中心,在获悉前一部分请求结束之后,先告诉主调度器自己结束了再分发的请求,这导致了主调度器在确认所有次级调度器结束之后就直接让电梯下班了,这时若分发工作还未完成则会有提早下班的情况。
发现他人 bug 策略
- 其实互测都是挺佛了,主要就靠随机生成数据,然后靠网上的评测机来验证输出的正确性(由于自己感觉写评测机太麻烦就咕了,逃)
心得体会
- 在设计框架上,现在至少想到有两处可以改进的地方:
- 用工厂方法,实现生成不同种类的电梯:这个主要是由于第二次作业就没有想到以后电梯会不同的情况,所以就把电梯写死了,然后第三次并不想重构电梯。应该可以在电梯类里面定义实现好业务逻辑,然后通过继承的方式,来实现不同类别的电梯,这样才符合开闭原则。
- 在第三次作业的时候,由于我想扩展 PersonRequest 类的功能,实现一个结束标识符,我重写了一个 Request 类,这非常不优雅,其实可以用动态代理实现的,这样就不用傻傻的重抄一遍了(虽然就工作量而言,可能都是差不多的)。关于代理模式,我总结在了另一篇博文中 Java 设计模式 -- 代理模式