OO第二单元总结

OO第二单元总结

        这是oo课的第二个单元,也就是久仰大名的电梯单元。在这一个单元中,我感到和上一次很不同的事情在于:对象设计和构造的难度减小了,但是多线程的并发问题变得非常复杂。在这一个部分,我感觉一开始入门的时候,有非常多新的知识需要理解,但是有了上一次的经验,我在迭代开发方面做的更好,没有出现大面积重构的问题,总体来说得到了还不错的结果。

第五次作业

    设计思路:

        第五次作业的主要工作,我认为在于搭建一个比较完整的电梯模型框架,形成一个基本的生产者-消费者结构。而为了实现这一个结构,我定义了:主线程(MainClass.java)、输入线程(InputThread.java)、电梯线程(ElevatorThread.java)以及一个缓冲区(InfoBuffer.java)。而在调度问题上,我在电梯处定义了一个请求队列(requests),在每一层停靠都与缓冲区的新请求进行交互(看看可不可以添加新请求)。事实上,这样一个经典的生产者-消费者结构我在这三次作业中始终没有改变,实现了比较好的扩展。

        这次设计中,也存在不少的难点:

        1、如何停止?我采用的方法是:在每一次电梯执行完一个请求的时候,判断条件①输入线程终止②缓冲区为空③电梯内部队列为空,是否同时满足。如果满足则结束线程,否则继续。

        2、如何调度?我在参考了很多的调度算法之后,采用了look和ALS的混合算法。也就是说在不断往返最高楼层和最低楼层的过程中,在每一层都考虑可不可以添加捎带的请求(detect)。这种方法兼具了look和ALS的优点,实测性能不错。

    UML类图:

        OO第二单元总结_第1张图片

    度量分析:

 OO第二单元总结_第2张图片

        这个复杂度总的来说还是挺高的,有不少飘红的。仔细分析其中的原因,主要问题出现在了以下两个部分:

        1、在缓冲区中加入了调度算法,比较复杂。现在想来,应当建立一个专门的调度器类,以此来进行管理。

        2、在电梯中加入了一个比较复杂的队列request。现在想来,应当把队列封装成一个类,进行一些打包。

    BUG分析:

        由于这次作业复杂度比较低,所以我在强测和互测阶段均没有被找到问题。至于同屋的其他人,我也并没有测试出错误用例。

 

第六次作业:

    设计思路:

        第二次作业的主要要求与第一次的主要区别在于加入了多部电梯,并且更改了楼层的限制范围。事实上,后者的修改是很方便的,唯一需要注意的问题就是电梯并不存在0楼,这里从-1到1发生了一个跳跃。

        这次作业同样存在一些难点:

        1、怎么停止?由于在这次作业中我没有考虑换乘,所以就不存在放回请求的可能性。因此一旦输入线程关闭并且缓冲区为空,它就不可能再一次变得非空(这一点在下一次作业就不同了)。因此判断条件仍然无需改变,即:①输入线程终止②缓冲区为空③电梯内部队列为空。

        2、多部电梯怎么调度?事实上,我采用的是电梯抢人的调度方法,而不是人选择电梯。这样一来也就是没有了全知全能的调度器,这无疑在大大简化了代码的复杂程度,但性能上是一种隐性的伤害,可事实证明这种伤害并不大。为什么呢?这里给出一些理由:所有的电梯花在到达主请求初始楼层上花费的时间,一定远远短于请求执行的时间。而ALS的捎带,一定是最优的(因为这段路不加乘客也一定会走),所以我就直接采用了第一次的调度策略,并且是电梯抢人的模式,我也在性能分部分得到了比较高的分数。

    UML类图:

OO第二单元总结_第3张图片

    度量分析:

        利用idea自带的分析,得到了如下的分析:

OO第二单元总结_第4张图片

        这次代码的改动较小,事实上和上次没有太多差距,因此就不赘述。

    Bug分析:

        这次的设计中,我仍然没有出现BUG。但是通过自己编写的测评机,我找到了别人的一个BUG。这个BUG似乎是由于楼层非法导致的,不知道那位同学是出现了什么样的问题,才导致了这样的BUG。

 

第七次作业:

    设计思路:

        这次作业主要的不同在于丰富了请求的种类、添加了电梯不可到达的楼层并且区分了电梯的速度。总体来说,这次作业,我的修改量还是比较多的,在自己一开始写代码的时候,也遇到了许多BUG,但是还是一一改正了过来。

        这次作业的主要难点我觉得可能有如下几点:

        1、怎么换乘?通过分析,许多楼层之间是不能直达的,那么这个时候就必须采用换乘的方式。而我在实现换乘的时候,就是把一个请求拆分成了若干请求序列。那么为了得到请求的构造方法,我定义的MyRequest类,这事实上就是在PersonRequest上面添加了public的构造方法。然后为了保证拆分出来的请求序列的顺序执行,我严格要求必须序列前面的请求执行完毕,后面的请求才会由电梯放入缓冲区,这样就保证了顺序问题。

        2、怎么停止?由于在这次作业中,我的电梯线程也有可能放入新的请求,所以之前的结束条件就不适用了。新的条件应当是:①输入线程终止②缓冲区为空③电梯内部队列为空④所有电梯处于等待状态或者未被激活

    UML类图:

       OO第二单元总结_第5张图片

 

    度量分析:

OO第二单元总结_第6张图片

        这次作业还是基于原来的结构。总的来看,这三次作业的过程就是一个不太合理的结构逐渐变得臃肿的过程;总体而言,这次我的代码设计还是有不少改进空间的,但是在构造的时候,为了避免大面积的重构,我也只能继续下去。所幸,复杂度好像也不是太高。

    Bug分析:

        这次作业中,我既没有找到别人的BUG,自己也没有被找到BUG。但是总的来说,性能分并不高。这主要是因为我比较追求正确性,并且尽可能不愿意大范围改动之前的调度策略。所以我在考虑换乘的时候,默认地采用了直线换乘的方法(即不考虑走回头路的情况,当然2-3,3-2,4-3,3-4除外)。可是这种调度策略确实在性能上存在一定的不合理性,比较低效。

思考

    通过SOLID原则对代码进行分析:

  • SRP 单一职责原则:三次作业的迭代中体现的较好。但是在缓冲区的设计中,混杂了相当一部分的调度策略,这其实是不好的。在阅读了同组同学的代码后,发现他们很多都是采用了专门的调度器类,比较好地实现了这个问题。
  • OCP 开闭原则:这一点实现的较好。每次迭代开发时,上一次的设计大部分均能沿用,只需针对新增需求做出相应添加修改。
  • LSP 里氏替换原则:本单元我在实现过程中没有用到继承,所以并未体现这一个原则。
  • ISP 接口隔离原则:本单元我在实现过程中没有用到接口,所以并未体现这一个原则。
  • DIP 依赖倒置原则:本单元作业均是依赖的实例,没有体现该原则。

    发现Bug的思路

        在寻找Bug方面,我采用了评测机随机生成数据和自己手动测试边界数据、异常数据的方法。在编写测评机上,我主要包括了几个部分:1、生成数据;2、定时输入测试样例;3、检查输出。这三个部分,我都是用Python完成的,最后再写一个cmd执行程序,反复地运行,并且把错误的样例放到一个文本文件里面。这样就完成了自动化的测试过程。

        然后在测试别人的程序时,为了提高测试的效率,我设置了一个参数文件(config.py)在这里可以修改生成样例的特点。比如:密集请求、稀疏请求、指定楼层范围等等。在第六次作业中,我就是利用了这样的方法,找到了别人的BUG。

对比与心得

  • 多线程程序设计与单线程程序设计的思维有和很大的不同,尤其要注意线程安全问题。我一开始出现了死锁、数据不安全等问题,后来都是在不断地修改中,逐渐对此有了更深刻的理解,意识到了这是一个非常精巧的结构。
  • 生产者-消费者的模型非常有普遍意义,也给我在多线程设计上有了非常多的启发。
  • 前面作业中留下的可扩展性,让我在后面迭代开发的过程中享受到了益处,尤其我在第六次作业上,几乎没有花什么时间,就得到了不错的结果。这更让我认识到代码扩展性的重要。

你可能感兴趣的:(OO第二单元总结)