OO第二单元总结

OO第二单元总结

程序结构分析

  • 第一次作业

    Type Name MethodName LOC CC PC
    Elevator Elevator 7 1 1
    Elevator move 10 1 0
    Elevator on_off 21 7 0
    Elevator hasOff 8 3 0
    Elevator open 9 1 0
    Elevator close 9 1 0
    Elevator getPrior 9 3 0
    Elevator init 11 3 0
    Elevator run 12 4 0
    Input Input 3 1 1
    Input run 18 3 0
    MainClass main 8 1 1
    Queue Queue 4 1 0
    Queue isEmpty 3 1 0
    Queue isStop 3 1 0
    Queue setStop 3 1 1
    Queue add 4 1 1
    Queue remove 3 1 1
    Queue getFirst 11 2 0
    Queue hasRequest 8 3 2
    Queue needGo 17 6 2
    Queue get 20 4 2
    Request Request 11 2 3
    Request getFrom 3 1 0
    Request getTo 3 1 0
    Request getId 3 1 0
    Request getDirection 3 1 0
    Type Name NOF NOPF NOM NOPM LOC WMC NC DIT LCOM FANIN FANOUT
    Elevator 5 0 9 9 103 24 0 0 0 0 0
    Input 1 0 2 2 24 4 0 0 0 0 0
    MainClass 0 0 1 1 10 1 0 0 -1 0 0
    Queue 2 0 10 10 80 21 0 0 0 0 0
    Request 4 0 5 5 29 6 0 0 0 0 0

    从设计架构和度量分析中可以看出,Elevator类的on_off方法复杂度较高,Queue类的needGoget方法复杂度较高,可以看出,对于第一次作业来说,最重要的是处理好电梯的运行功能正确,尤其是电梯上下人处,容易出现问题;而needGo方法则与我使用的电梯运行策略有关,在处理好电梯运行功能正确后,提高电梯性能。

    从时序图可以看出,第一次作业就是一个简单的生产者-消费者模型,Input类充当生产者,像共享对象Queue中投放乘客请求,Elevator为消费者,不断拿走请求将乘客运送到指定楼层。
    整体来说第一次作业比较容易,处理好共享对象Queue的安全访问,选择实现一个性能不错的运行策略就可以了。

  • 第二次作业

    Type Name MethodName LOC CC PC
    Elevator Elevator 10 1 2
    Elevator getPlace 3 1 0
    Elevator getDir 3 1 0
    Elevator getDes 3 1 0
    Elevator move 10 1 0
    Elevator on_off 24 7 0
    Elevator hasOff 8 3 0
    Elevator open 9 1 0
    Elevator close 9 1 0
    Elevator getPrior 9 3 0
    Elevator init 16 4 0
    Elevator run 14 5 0
    MainClass main 7 1 1
    Queue Queue 4 1 0
    Queue getSize 3 1 0
    Queue isEmpty 3 1 0
    Queue isStop 3 1 0
    Queue setStop 4 1 1
    Queue add 4 1 1
    Queue getFirst 14 3 0
    Queue hasRequest 8 3 2
    Queue needGo 17 6 2
    Queue get 25 5 4
    Request Request 11 2 3
    Request getFrom 3 1 0
    Request getTo 3 1 0
    Request getId 3 1 0
    Request getDirection 3 1 0
    Schedule Schedule 10 2 2
    Schedule calculate 44 10 4
    Schedule run 38 9 0
    Type Name NOF NOPF NOM NOPM LOC WMC NC DIT LCOM FANIN FANOUT
    Elevator 8 0 12 12 128 29 0 0 0 0 0
    MainClass 0 0 1 1 9 1 0 0 -1 0 0
    Queue 2 0 10 10 89 23 0 0 0 0 0
    Request 4 0 5 5 29 6 0 0 0 0 0
    Schedule 3 0 3 3 97 21 0 0 0.666667 0 0

    第二次作业在第一次的基础上增加了多部电梯,因此设计一个调度器是十分必要的。此时调度器的调度和电梯的运行策略同等重要。可以看出,除了电梯运行相关的方法复杂度较高外,Schedule类的calculaterun的复杂度更高,这个类的代码量比较高。这次作业中,我才用了优先选择距离较近的电梯优先分配请求,相同距离则比较电梯分配的请求数,请求数少的优先。对应的是Schedulecalculate方法。在强测中,这一调度方法配合单部电梯的LOOK算法获得了较好的性能。

    从时序图中可以看出,第二次作业的设计模型与第一次略有不同。对每部电梯来说依然是生产者-消费者模型,但整体上来说是Worker-Thread模型。Schedule获得输入的请求,处理之后发送给对应Elevator_iQueue_i中,Elevator_i再从中执行请求。在第一次作业的基础上,第二次作业采用这样的设计模式非常容易,只需要增加电梯和对应的队列即可,不用对第一次作业已采用的模式进行大的调整,整个模式非常清楚,不易出错。

  • 第三次作业

    Type Name MethodName LOC CC PC
    EleA EleA 3 1 3
    EleB EleB 3 1 3
    EleC EleC 3 1 3
    Elevator Elevator 13 1 6
    Elevator getPlace 3 1 0
    Elevator getDir 3 1 0
    Elevator getDes 3 1 0
    Elevator getRuntime 3 1 0
    Elevator getname 3 1 0
    Elevator inRange 8 3 1
    Elevator move 12 2 0
    Elevator on_off 34 10 0
    Elevator hasOff 8 3 0
    Elevator open 9 1 0
    Elevator close 9 1 0
    Elevator getPrior 9 3 0
    Elevator init 16 4 0
    Elevator run 16 6 0
    MainClass main 7 1 1
    Prequest Prequest 12 2 3
    Prequest getStatus 3 1 0
    Prequest setStatus 3 1 1
    Prequest getFrom 3 1 0
    Prequest getTo 3 1 0
    Prequest getId 3 1 0
    Prequest getDirection 3 1 0
    Queue Queue 4 1 0
    Queue getSize 3 1 0
    Queue isEmpty 3 1 0
    Queue isStop 3 1 0
    Queue setStop 4 1 1
    Queue add 4 1 1
    Queue getFirst 14 3 0
    Queue hasRequest 8 3 2
    Queue needGo 17 6 2
    Queue get 25 5 4
    Schedule Schedule 10 2 2
    Schedule construct 12 4 4
    Schedule contain 8 3 2
    Schedule inRange 12 4 2
    Schedule findMid 28 9 4
    Schedule makePrequest 30 7 1
    Schedule produce 10 3 1
    Schedule distribute 19 5 2
    Schedule calculate 44 10 4
    Schedule finish 12 4 0
    Schedule rest 14 5 0
    Schedule manage 15 5 0
    Schedule run 53 11 0
    Type Name NOF NOPF NOM NOPM LOC WMC NC DIT LCOM FANIN FANOUT
    EleA 1 0 1 1 6 1 0 0 0 0 0
    EleB 1 0 1 1 6 1 0 0 0 0 0
    EleC 1 0 1 1 6 1 0 0 0 0 0
    Elevator 12 0 15 15 163 39 0 0 0 0 0
    MainClass 0 0 1 1 9 1 0 0 -1 0 0
    Prequest 5 0 7 7 37 8 0 0 0 0 0
    Queue 2 0 10 10 89 23 0 0 0 0 0
    Schedule 11 0 13 13 280 72 0 0 0.461538 0 0

    第三次作业相比第二次增加了不同电梯类型和换乘。相应的设计也更加复杂,尤其是SchedulerElevator类,增加了很多成员变量和方法。以至于Scheduler类的设计十分臃肿,方法复杂度也很高,可维护性并不很好。因此在完成这次作业的过程中,我认真地考虑过tasks这一容器是否需要单独设计一个类来进行管理,这主要是受课程实验的影响,即设计一个类似于票池的容器,这样能减轻Scheduler的负担。但最后我并没有采用这样的设计,一方面是基于现有架构进行补充,最方便的还是放在Schedule类中管理;另一方面,考虑到除了乘客请求,还有新增电梯的请求,而tasks是只存放乘客请求的容器,分离出去不便管理,可能会有线程安全的隐患。
    此外,分别设计EleAEleBEleC三个电梯子类实际上没有这个必要,只需要在电梯类中增加type变量和相应的判断即可。

    但事实上,最好的设计应当是设计一个子调度器,用来接受请求并指挥电梯运行,电梯只管理上下运行,并不直接和主调度器进行通讯。这样就可以分担电梯和主调度器的工作负担,设计也不会那么臃肿,会更有层次感,可维护性也更好。我这样的设计主要还是受第一次作业的影响,在第一次作业中我并没有设计调度器管理电梯的运行,而是将电梯运行具体实现直接集成在了Elevator中。说明第一次的设计结构还是十分关键的。

    为了应对换乘情况,我的思路是对于需要换乘的请求进行拆分,按执行顺序加入到一个task的容器里,再将task加入到tasks容器里,无需拆分的请求则直接加入tasks容器中,并将请求状态设为0,即未分配状态;接着扫描tasks,找到未分配且可以分配的请求,逐一按照第二次作业的思路进行分配,然后将请求状态设为1,即已分配;电梯执行完请求后,将对应请求的状态设为2,即已完成。当所有任务的状态均为已完成且输入结束后,通知电梯结束。

    相比于第二次作业,第三次就是增加了一个主请求队列tasks来管理请求,整体结构没有太大的改动,模式设计还算是差强人意。只是如前文所述,能增加子调度器,使得设计更有层次感,任务分工更加明确具体就更好了。

  • 总结

    相比于第一单元惨不忍睹的架构,这一单元的设计明显容易了不少。得益于第一次第二次作业的良好的设计,没有出现第一单元次次重构的惨状。总体来说写得也比第一单元更轻松。除了了解了很多优秀的设计模式外,这也让我深刻认识了架构的重要性。下一单元继续努力。

bug分析

  • 第一次作业

    第一次作业在完成过程中主要遇到的是电梯结束的问题,电梯在执行完请求后处在等待状态而没有被唤醒,在停止信号设置后增加一个notifyall就可以解决问题。

    在互测过程中我并没有测出其他人的bug,第一次作业整体设计也还算是不错的。

  • 第二次作业

    第二次作业在第一次的基础上没有做太多改动,无论是线程安全还是电梯功能都没有出问题。

    互测中也没有发现其他人的bug,整体都是很好的。

  • 第三次作业

    第三次作业出现了ctle的情况,在Scheduler分配请求的过程中出现了轮询,在没有可分配的请求时没有让出cpu进入等待状态,增加了rest函数判断后解决了这一问题。值得一提的是,在解决这一问题的过程中,经dalao推荐,我使用了开源工具visualVM来监视cpu的运行状况,成功解决了轮询的问题,在这里推荐这一工具。

    在互测过程中,我发现了一位同学的代码仅能支持新增电梯id为X1X2X3的bug。后来发现此类设计并不仅是个案,可能是对指导书的理解有误差。此外并无其他问题。

心得体会

整体来说三次作业完成状况都是不错的,强测全通过互测也没有被hack,这得益于简单有效的设计模式,当然电梯作业的整体难度也并没有第一单元高。采取一个合适的模式,对于保证线程安全和电梯功能正确性能良好都有着重要作用。

在这一单元,我了解了java的多线程设计和线程安全问题,还认识了生产者-消费者、Worker-Thread、订阅-发布等设计模式,让我明白了优秀的设计架构带来的高效的编码等诸多好处。下一单元继续努力,学习体会更多有用的设计。

posted @ 2020-04-15 14:38  p_wk  阅读(134)  评论(0编辑  收藏  举报