OO第二单元总结

OO第二单元总结

1.作业设计策略

  • (1)多线程协同结构

    • 在本单元的三次作业中,由于采用了捎带电梯的分配策略,本人均采用了如下的多线程协同结构:
      • 线程类:
        • ReadQuestThread:读入线程
        • DispatcherThread:设定电梯主需求线程
        • ElevatorThread:电梯模拟运行线程
      • 共享数据类:
        • QuestConvey:用于存储还未被电梯搭载的需求队列
        • Elevator:用于存储已被电梯搭载的需求队列
    • 在总结后面的3.基于度量的程序结构分析部分,本人根据展示的UML类图更加详细的讲解了具体的协同结构工作原理。
  • (2)线程同步控制

    • 本单元作业需要实现同步控制的范围主要涉及使需求队列产生变化的方法,大致包括以下几个方面:
      • 读入线程ReadQuestThread向未被电梯搭乘的需求队列QuestConvey中插入新需求。
      • 分配主需求线程DispatcherThread从未被电梯搭乘的需求队列QuestConvey取出一个满足分配策略的需求,并设定为电梯Elevator的主需求。
      • 电梯线程ElevatorThread从未被电梯搭乘的需求队列QuestConvey定期取出满足捎带策略的需求,并将其装入已被电梯搭载的需求队列Elevator。
      • 电梯线程ElevatorThread将已装入被电梯搭载的需求队列Elevator中满足离开电梯条件的需求取出,模拟人从电梯里面离开的过程。
    • 通过对实现以上操作的共享数据类中的方法设置synchronized,从而实现线程对共享数据的访问同步。

2.架构设计原则检查及可扩展性

  • (1)SOLID原则检查设计情况

    • SRP

      • 含义:每个类或方法都只有一个明确的职责。
      • 第三次作业中,本人通过ReadQuestThread,DispathcerThread,ElevatorThread三个线程分割开了整个作业达成目标的读入,设定,运行三个步骤,这一方面相对满足要求。
    • OCP

      • 含义:无需修改已有实现,而是通过扩展来增加新功能。
      • 第三次作业中,相较于前两次作业,本人并没有过多修改Elevator类的内容,而是采用继承的方式扩展了电梯类的种类。但是如果更改调度策略,DispatcherThread这个类可能就难以达到OCP的要求。
    • LSP

      • 含义:任何父类出现的地方都可以用子类来代替,并不会导致使用相应类的程序出现错误。
      • 第三次作业中,本人主要采用的继承类是Elevator电梯类,根据电梯的种类,分出了新的子类,这些子类满足了LSP的要求。
    • ISP

      • 含义:通过接口来建立行为抽象层次具有更好的灵活性。
      • 第三次作业中,本人并没有采用接口的实现方式,这个方面可能做得稍差。
    • DIP

      • 含义:依赖倒置原则。
      • 同样,由于在第三次作业中本人并未采用较多的抽象层次,所以这个方面也可能稍差。
  • (2)功能设计与性能设计平衡

    • 为保证电梯的功能正确和性能较高之间的平衡,本人采用了电梯抢请求的分配策略。如此,既可以保证分配过程和电梯运行过程的绝对正确,同时由于“抢”这一操作具有随机性,能保证性能不会过差。

3.基于度量的程序结构分析

  • 第一次作业

    • (1)作业类图及设计思路分析

      • 本次作业实现目标为简单的捎带电梯,作业类图如下:
        • UML类图
      • 根据以上类图,分析本次作业设计思路如下:
        • 通过ReadQuestThread线程类,实现对请求的读入,并将新的请求加入QuestConvey中未被电梯搭乘的请求队列内。
        • 通过DispatcherThread线程类,实现从QuestConvey中未被电梯搭乘的请求中选择适合的请求作为唯一的电梯Elevator的主请求。
        • 通过ElevatorThread线程类,实现电梯捎带QuestConvey中未被搭乘的请求,并实现电梯内请求的上下电梯操作。
        • QuestConvey类,为共享数据类,由于本次作业只有一台电梯,所以将未被电梯搭乘的请求队列和已经被电梯搭乘的请求队列都放置在此类中。
        • MainClass类,实现共享数据对象的申请,同时启动上述线程对象。
    • (2)根据数据度量分析程序结构

      • 类的度量分析
        • 类复杂度统计
        • 相关参数含义:
          • DIT为继承树深度,如词面描述,该数值用于统计该类继承深度。
          • LOC为类行数,统计该类有效行数。
          • NOAC为该类新添加的方法数,用于统计该类除重写,继承外相对增加的新的方法数量。
          • NOOC为该类重写的方法数,作用如字面意。
          • NAAC为该类新添加的属性数,用于统计该类除继承外相对增加的新的属性数量。
        • 度量分析如下:
          • 本次作业由于结构设计基本简单,因而除线程类外,类之间的继承关系并不多。
          • 可以看到,本次作业QuestConvey类的LOC相比其他类而言相当大,这主要是因为本次设计并未考虑多部电梯的存在,而将已经被电梯搭载的请求队列放在了QuestConvey内,造成了该类功能相对冗余。
    • (3)本次作业优缺点总结

      • 优点:
        • 通过较为简洁的几种线程类,分割了电梯和请求队列的工作内容,结构相对清楚简明。
      • 缺点:
        • 本次作业并没有考虑到多部电梯的拓展内容,造成了部分类的功能过于冗余,没有很好的满足SRP原则的要求。
  • 第二次作业

    • (1)作业类图及设计思路分析

      • 本次作业相对增加多部电梯,作业类图如下:
        • UML类图
      • 根据以上类图,分析本次作业设计思路如下:
        • 通过ReadQuestThread线程类,实现对请求的读入,并将新的请求加入QuestConvey中未被电梯搭乘的请求队列内。
        • 通过DispatcherThread线程类,从QuestConvey中未被电梯搭乘的请求中选择适合的请求,并为每一部空闲的电梯分配合适的请求作为主请求。
        • 通过ElevatorThread线程类,实现电梯捎带QuestConvey中未被搭乘的请求,并实现电梯内请求的上下电梯操作,多部电梯捎带的过程是随机抢夺请求。
        • QuestConvey类,为共享数据类,由于本次作业有多部电梯,所以只将未被电梯搭乘的请求队列放置在此类中。
        • Elevator类,为共享数据类,本次作业将电梯已搭乘的请求队列放置在此类中,便于ElevatorThread线程类进行维护。
        • MainClass类,实现共享数据对象的申请,同时启动上述线程对象。
    • (2)根据数据度量分析程序结构

      • 类的度量分析
        • 类复杂度统计
        • 相关参数含义同上次作业。
        • 度量分析如下:
          • 可以看到,本次作业相较于上次作业,QuestConvey类的LOC和Elevator类接近,这是因为本次作业需要独立电梯内请求队列,并将一定的方法放置在电梯Elevator类中,为QuestConvey类减负。
          • 同时本次由于考虑到下次作业可能需要对电梯进行扩展,所以本次作业的Elevator类还聚合了一些共通的属性,可以看到属性有10个,增加数量相对较多。
    • (3)本次作业优缺点总结

      • 优点:
        • 本次作业为QuestConvey类减负,将电梯内请求队列内置于Elevator类中,Elevator和QuestConvey两个类的功能职责更加明确,满足了SRP原则的要求。
      • 缺点:
        • 部分电梯内请求队列的更新操作并未很好的独立出来,而是选择了放置在ElevatorThread线程类的run方法中,容易造成更新操作混乱。
  • 第三次作业

    • (1)作业类图及设计思路分析

      • 本次作业增加了对电梯到达楼层以及上下时间不同的要求,UML类图如下:
        • UML类图
      • 根据以上类图,分析本次作业设计思路如下:
        • TransferHandler类,此为预处理类,通过进行相关的预处理操作,得到不能直达的请求的换乘策略,并将之储存在TransferFloor类。
        • 通过ReadQuestThread线程类,实现对请求的读入,并将新的请求加入QuestConvey中未被电梯搭乘的请求队列内。此外,通过预处理得出的换乘策略,将必须要换乘的请求拆分为多个请求,再把新的多个请求加入QuestConvey的多级请求队列内。
        • 通过DispatcherThread线程类,从QuestConvey未被电梯搭乘的分级请求队列中选择适合的请求,并为每一部空闲的电梯分配合适的请求作为主请求。
        • 通过ElevatorThread线程类,实现电梯捎带QuestConvey中未被搭乘的请求,并实现电梯内请求的上下电梯操作,多部电梯捎带的过程是随机抢夺请求。此外,当前置请求完成时,更新后置请求的状态。
        • QuestConvey类,为共享数据类,由于本次作业有多部电梯,所以只将未被电梯搭乘的请求队列放置在此类中。此外,对请求队列进行分级,实现前置请求和后置请求的分离。
        • Elevator类,为共享数据类,由于本次作业对电梯的到达楼层有了关键的要求,所以从此类继承了多个种类的电梯类,通过重写构造函数,实现到达楼层队列在不同种类电梯的不同初始化过程。
        • MainClass类,实现共享数据对象的申请,同时启动上述线程对象。
    • (2)根据数据度量分析程序结构

      • 类的度量分析
        • 类复杂度分析
        • 相关参数含义同上次作业。
        • 度量分析如下:
          • 由于考虑到电梯的可拓展性,上次作业中就已经对电梯的属性和方法有所聚合,所以本次电梯相对上次作业并未有太多修改,可以看到,电梯类Elevator行数和方法个数并未有太大改变。
          • 由于本次作业涉及到对不能直达的请求的拆分,拆分过程被放置在了QuestConvey类的方法里。此外,QuestConvey中还增加了不同层级的请求队列,所以本次QuestConvey类的内容相对较多。
    • (3)本次作业优缺点总结

      • 优点:
        • 本次作业考虑到了电梯的可拓展性,将属性和方法高度聚合,降低了电梯类Elevator和其他类之间的耦合度,更加有利于新的需求的扩展。
      • 缺点:
        • QuestConvey类中的内容有所冗余。应当通过继承等更加清晰的方式来扩展QuestConvey类,而不是应该直接修改QuestConvey类中的内容,最终导致冗余。
        • 拆分请求的策略不够优秀。本人的拆分策略会将过多的请求在同一层积压,造成程序效率下降。

4.个人bug分析及探测bug策略

  • 个人bug分析

    • 在本单元的线程调试过程中,我个人的bug主要包括以下两个方面:
      • 死锁:
        • 产生原因:在调用不同对象的同步方法的过程中没有保持单向性。当两个线程分别以不同的顺序调用两个对象的同步方法,造成循环依赖,自然会产生死锁现象。
        • 解决方法:注意调用同步方法时,保持单向调用。
      • CTLE:
        • 产生原因:某一线程进行wait的条件判断错误,导致其发生暴力轮询。
        • 解决方法:对于wait的条件判断要条理清晰,可尝试通过画图来理清其逻辑结构。
  • 探测bug策略

    • 本单元的测试中,由于测试数据点大多需要定时投放,所以本人的探测bug策略主要在于使用简单的自动测评脚本进行测试。
      • 使用python的subprocess.Popen()可以写出一个简单的自动评测脚本。

5.本单元心得体会

  • 设计原则

    • 本单元学习到SOLID的经典五个设计原则。懂得了在设计自己程序结构的过程中,要时刻比照这五个原则,及时找出程序结构设计上的问题,进而优化设计。
  • 线程安全

    • 本单元最大的收获之一就是熟悉了java线程的编写。线程安全则作为线程代码编写的重要的过程之一,也带给我本人很多收获。
      • 首先,我了解到了多线程程序的特殊性,认识到在线程并发的情况下,若不对共享数据进行保护,会造成线程不安全。
      • 其次,我学习到了多线程中锁的相关机制,掌握了通过锁的同步控制来保证线程安全的一些方法。
      • 最后,我的收获还包括以下确保线程安全的设计的三个要素:控制对象发布和共享、共享对象设计为线程安全类、保持简洁的线程类。
  • 以上就是本单元个人的简要心得体会。

posted @ 2020-04-18 09:57  OmedetoHe  阅读(147)  评论(0编辑  收藏  举报