面向对象第三单元总结

面向对象第三单元总结

(1) 总结分析所采取的设计策略

第一次作业

  • 对于异常类,需要自己继承官方包提供的抽象异常类,并正确实现指定参数的实现方法以及无参的print方法。我在异常类中构造了静态属性times以记录发生异常的次数,并构造了一个计数器类idCount,其实例作为每个异常类的 static 属性,管理该类型异常的计数。

  • 第一次作业需要实现的比较简单,可根据所给规格简单实现。

第二次作业

  • 异常类的增加比较简单,基本可以复制前一次作业的异常类。
  • 在其他类中,从比较小的、数据和方法较小的类如PersonMessage类开始实现。之后再考虑用哪种较好的容器或算法实现较为复杂的类如GroupNetwork。还需考虑完全按照JML的算法可能超时的问题。

第三次作业

  • 第三次作业相对于第二次作业的增量在于几个异常类和三种不同类型的消息,以及相应增加的类属性。增加了可以通过间接路径发送消息的方法。
  • 在实现上需要仔细阅读第二次和第三次作业相关方法的JML的变化。

(2) 基于JML规格设计测试

  • 使用JMLUnitNG

    根据规格自动生成测试用例,可以针对边界数据进行测试。虽然不能高度覆盖各种情况,但是对于极端数据的测试还是有效的。

  • 使用JUnit

    JUnit的配置和使用比较方便,是单元测试框架。需要通过自己编写测试代码。但是可能强度较弱,可以针对不确定的方法块进行功能性测试。

  • 使用OpenJML

    通过对JML语言的检查(变量检查、读写检查)来验证代码的正确性。但是环境配置麻烦。

(3)总结分析容器选择和使用的经验

  • 考虑到用Hash类型的容器查找效率较快,故在很多容器的选择上都使用了HashMap

  • Person类中熟人属性private HashMap<Integer,Integer> acquaintance = new HashMap<>();键是熟人的id,值是两人的value。在第一次作业时将键类型设为了Person,但是没有将hashCode重写,导致不同的Person可能hashCode相等,在互测中被hack。

  • 由于Person类中的getReceivedMessages方法需要返回List类型的变量,故将此人收到的Message保存在ArrayList容器内。

  • Mygroup类也使用了private HashMap<Integer,Person> people;来存储群内的人。

  • Mynetwork类的所有容器均为HashMap类型

    private HashMap<Integer,Person> people;
    private Map<Integer,HashMap<Integer,Person>> blocks;
    private Map<Integer,Group> groups;
    private Map<Integer,Message> messages;
    private Map<Integer,Integer> emojis;
    

    在此类中,我使用了二次HashMap嵌套来存储整个图的连通分量blocks。Key是每个block唯一的ID,来自某个加入block的Person。Value是用来存储Person的Map。在执行addPerson方法时,需要增加一个block,id为此人的id,HashMap里存的是此人。addRelation时需要将含有两人的blocks合并,使用putAll方法,并删除另一个。

(4)容易出现的性能问题

  • isCircle方法(即判断两个人是否在同一个连通块中)中,如果每次查询都使用深度优先或广度优先算法,时间复杂度是n2,容易超时。在使用`qbs`方法来求连通分量的个数时,如果使用`JML`中的算法,每次查询的时间复杂度也是n2。我避免超时的方法就是使用HashMap二次嵌套来存储整个图。如前所述,private Map<Integer,HashMap<Integer,Person>> blocks;这样判断两个人是否在同一连通块时,只需遍历这个容器,看两个人是否含于同一个block中,复杂度降为O(n)。求连通分量的个数时只需要求整个Map的大小就行,时间复杂度为O(1)。
  • 在group中求valueSum、年龄平均数和年龄方差时,如果每次都遍历整个容器作加法的话复杂度分别为O(n^2)、O(n)、O(n)。特别是第一个valueSum,容易被卡超时。解决方法是对每一个group增加属性valueSumtotalAgetotalAgeSquare来保存当前的总和信息。当addToGroupdelFromGroupaddRelation时实时修改上述三个值。这样在查询的时候复杂度降为O(1)。
  • 第三次作业的sendIndirectMessage方法需要我们求两节点之间的最短路径。考虑使用迪杰斯特拉算法,但是其复杂度为O(n^2),容易超时。故使用优化算法,借助优先队列PriorityQueue进行堆优化,新建一个节点类Node,包含当前person和当前距离distance。时间复杂度为O(nlogn)
  • 其余的主要是使用HashMap作为容器,提升查找效率。

(5)体会与感想

  • 本次作业的整体架构都由已给出的代码框架给定了,无需自己在框架层面创新,但是必须要熟悉给的框架,理解代码功能以及仔细阅读JML以免造成错误理解。
  • 通过本次作业我对JML有了初步的了解,能阅读理解比较复杂的jml代码,并在实现时对算法进行优化,在允许的范围内增加自己的方法来优化。也能够对照代码自己编写一些JML注释。
  • 本单元涉及图的知识比较多,需要花时间分析来减少图相关算法的复杂度。不能完全按照JML描述的方法来翻译代码,因为这样的时间复杂度可能很高。
posted @ 2021-05-28 14:43  Kyrie_2  阅读(64)  评论(1)    收藏  举报