面向对象设计与构造第三单元总结

一、实现规格所采取的设计策略

  • 经常阅读规格,结合方法名其实基本上已经能猜出来规格想要做什么了。对于部分\forallexists,其约束条件稍微看下即可,不同寻常的约束条件可以一眼看出来,其余时刻抓住规格中的内涵,可以很快地完成理解。
  • 对于整体的实现,笔者代码按照自底向上来完成。
    • 先通过复制粘贴解决重复劳作的异常类
    • 分析其他主要类的结构,理清相互间的调用关系,首先选择被调用的类下手,完成实现后可以帮助理解那些调用的类的需求。
    • 整个过程方便理解,同时IDEA所弹出的红色警告也要少很多。
  • 对于主要类的实现,主要先整体把握需求,再自上而下依次完成。
    • 阅读类的JML需求,基本把握这个类整体上在干什么,关注JML中那些带有循环的方法,这对类属性容器的选择有着重要影响。
    • 根据需求,为类属性选择合适的容器。然后从上到下依次实现类的每一个方法,对于复杂度较高的方法采用考虑特定的算法来降低下复杂度,如并查集、堆优化等。
  • HashMap完成了太多的事情,不少方法没啥可以设计的。

二、基于JML设计测试的方法和策略

  • 对于的测试,笔者还是主要通过对拍来完成,辅以JUnit测试。记住JML规格的大致内容,和他人比较结果的差异性,容易找出基本的正确性问题。但JUnit的测试依旧很有必要,其主要用于保证方法实现的完备性。

Junit测试

  • 主要测试对象:JML内容特别多的方法、进行性能优化后的方法、复杂度较高的方法
  • JUnit主要通过assert断言来完成测试,比较输出与期望输出是否一致。
  • 样例生成:
    • 结果容易计算的方法:直接测试方法内计算得到
    • 需要辅助计算的方法(如最短路等),可以使用python等进行计算,采取现有的库(如NetworkX等),利用从文件中读取输入输出的方式来进行assert比对。
  • 时间计算:
    • 在测试方法内容的前后通过System.currentTimeMillis()获取时间,可以大致判断方法的性能,决定是否做进一步的优化。

三、容器的选择与使用

  • 容器的选择根据需求来定。经常需要查找的适合用HashMap,经常需要从头尾插入的用LinkedList,需要排序的用PriorityQueue等,需要不重复计数时用HashMapHashset,其他不知道用什么的时候用ArrayList

  • 纵观三次作业的所有类属性,除了PersonmessagesLinkedList以外,其他清一色的HashMap。从id得到对象,典型的HashMap需求。规格中频繁要求获取某个对象、判断是否存在,使用HashMapcontainsKeygetputmerge等可以很好地解决问题。尽管偶尔需要遍历,但无伤大雅。

四、性能问题

  • 为了避免CTLE,需要对复杂度进行适当的降低,需要尽量避免出现O(n^2)及以上的复杂度。在降低复杂度后,设计基本上可以避免超时的问题。

第9次作业

  • 性能问题主要来自queryBlockSum,按照传统的方式将会是O(n^2)的复杂度。可以采用并查集来维护,并进行路径压缩,就算每次调用该方法都计算一次,复杂度也只是O(n)

第10次作业

  • 性能问题主要来自ageVarvalueSum。尽量不要采用查询一次计算几次的方式,毕竟变动是少量的,可以在发生变动时进行维护即可。发生变动仅存在于addPersondelPersonaddRelation等,维护较为简单。估算发现所有数据均不会爆int,除了不要除0,应该就没有什么需要特别注意的了。

  • 平均数向下取整后的方差公式:

    (年龄平方和 - 2 * 年龄和 * 平均值 + 总人数 * 平均值 * 平均值) / 总人数
    

第11次作业

  • 性能问题主要来自sendIndirectMessage,本质上是计算发送者和接收者之间的最短路径。采用Dijkstra算法,同时用PriorityQueue来进行堆优化,将复杂度降到O(nlogn)即可。

五、架构设计

  • 架构没啥可设计的,本单元,由于JML的需要,基本上已经把大部分内容限制死了,在需求的前提下,大部分人所采用的设计基本上大同小异,趋于一个近似的较优解。

  • 由于异常类差别并不大,可以独立出一个ExceptionCount,作为异常类的static final属性,方便对调用该异常的id进行管理,同时也方便实现输出。

  • 根据实际的需求,稍做继承,如下图所示:

    让其余三个特殊Message类继承自MyMessage类,可以更好地完成实现,避免代码上的重复。

  • 图模型的构建与维护:

    • 在单独将图独立为一个类时,Person当作结点,Edge为边和权重,可以方便对图进行整体上的管理。
    • 在其不独立时,其从NetworkPerson的类属性容器中获取所需要的信息。Person当作结点,从其所持有的acquaintance中获取边和权重。图不需要刻意去建立,在需要时自然而然就出现了。
    • 图的维护:由于Dijkstra是调用时才计算的,图的维护基本上只有在Group中addPersondelPerson,在Network中addRelation时一并更新所有Group了。

六、感想与体会

  • JML是形式化描述语言,其描述的内容基本不具有二义性,在某种程度上能够更好地理解用户需求。
  • JML其目的可能是为了测试与实现的分离,二者交由不同的人来完成,可以同步进行工作,其对于开发来说还是具有一定意义的。
  • 面向接口编程是一种思维方式,和继承相互辅助,可以更加灵活一点。
  • lambda表达式建议使用,其带来了代码的简洁性,在大部分情况下可读性更高,同时性能上并没有下降很多,评测机上的CPU耗时有一定的波动,笔者并没有看出其性能上有特别的劣势。
posted @ 2021-05-27 17:33  NoNameExists  阅读(68)  评论(0)    收藏  举报