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

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

第三单元的主要内容是对 JML 规格的理解和书写,从代码实现难度上并不大,但学习这方面的知识对于完善我们设计构造以及编程的能力还是很有意义的。三次作业完全是迭代开发,并考察了一些数据结构尤其是图方面的知识,值得一提的是,除契合规格以外,代码的架构设计并不能简单拘泥于规格,否则可能会面临TLE的问题,现对三次作业进行总结。

一、设计策略

  • 首先本单元作业的主体框架已给出,我们需要实现的是具体的接口,对于实现规格,先整体阅读规格大致理解含义,明确前置条件 requires 和后置条件ensures,关注不变式,这样可以先明白规格主要在哪一个层次上做改动,以及代码实现改变的对象。
  • 明确异常类型处理,了解不满足前置条件时如何处理,以及有关异常的抛出。
  • 再细致耐心地阅读 JML 规格,理解含义,值得一提的是还需要根据自己的设计来coding,而不是一句一句的按照规格编程。
  • 我采取的策略是先写底层的代码,再写最上层的 Network。可以在Person 或者 Group 中存储一些数据,这样可以减轻 Network 方法的复杂度,也便于管理维护。
  • MyPersonMyGroupMyMessage 分别实现各自的接口,在内部多用 Hashmap 和 ArrayList 来存储数据,还增加了一些常量来动态记录一些数据,以便于查询操作时做出优化。
  • MyNetwork:实现Network 接口,是本单元作业社交网络的实现部分,也是整个程序实现功能的核心。主要采用 Hashmap 来存储数据,内部有5个Hashmap,分别记录了person,group,message,emoji 以及 维护并查集所需要的的记录表。对于 isCircle 和 qbs 还增加了常量用以维护。
  • 异常类共有10个,都是继承自下发的抽象异常类,内部用 Hashmap 来记录每个id出现的异常及其次数,用一个static 变量来记录发生该类异常的总次数。

二、测试策略

  • 我在本单元采取的测试策略主要是黑箱测试,即自己构造样例来测试,这样做有两个不好处,一是测试数据如何全面的覆盖到细节,避免遗漏,二是对于时间复杂度的控制单从正确上无从检验,还需要其他的工具。

  • 在学习了Junit 工具的使用后,我发现使用 Junit 工具可以更加方便从方法角度进行测试,这样更有利于找到一些比较细节的bug, 但是自我感觉使用体验中不好的一点是还需要自己编写比较复杂的测试代码,这里不禁幻想:未来能否发明出基于规格的更加智能的自动生成测试集的工具...

  • 第三点,对拍是一个在黑箱测试中比较方便的办法,即在互测中可以根据几位同学的代码运行结果比对来判断是否出错,不过这样缺点也很明显,如果是应用于自己debug,发现不一样的结果,往往需要追根溯源来看看具体是哪一点出了错误,比如假设 iscircle 判断失误,有可能是dfs 或并查集某处代码有误,也有可能是 addrelation 时某处操作错误,因此单用对拍的方法,自己查错纠错时会有点困难。

三、容器的选用经验

  • 容器是怎么选的,说白了就是怎么存储管理数据的,本单元的学习让我更深层次的了解到:使用数据是否方便很大程度上决定于你是怎么存的!不要摊大饼式的存储数据,查询时暴力的遍历,遍历大概率会让时间性能出锅~。
  • 首先是Hashmap 是本次作业中应用最多的容器,查找,增减元素都非常方便。结合这次编程要求中的特点:id是独一无二的,且需要频繁根据 id 获取元素,自然而然地 Hashmap 成为使用最多的容器。我还学到一点,就是Hashmap 的 key 尽量选用简单的办法,比如我原来的 person 中记录亲疏关系 value 用的 person-value,后来经吴际老师指导改为 id-value。
  • Arraylist 在需要遍历循环时比较方便,插入、删除元素很方便,而且ArrayList 讲究一个次序问题,add 时也可以插入在对应的下标处,因此在Person 中使用 ArrayList 来存储收到的 Message。

四、性能问题

本单元性能问题是一个值得关注的问题,如果只一板一眼的按照规格来书写,性能上会很不好。(其中部分是血的教训)主要有以下几个值得注意的点:

  • isCirclequeryblocksum:刚开始我采用的最易实现的dfs,写起来轻松,但每次查询都需要重复递归,时间复杂度过高。后来改为并查集动态调整,这样 isCircle 查询的复杂度降为O(1),不必每次都递归调用。
  • 关于 qbs 初始我是按规格一板一眼地翻译的,这样慢的离谱。后来经过优化:
    • 存储一个常量 qbssum,以及一个Boolean类型的 flag 用于标记是否有人员及关系增加。
    • 每当新增人员以及关系时置 flag 为 false,调用qbs时先检验 flag ,如果为 true 则直接返回qbssum,如果为false 再进行O(n) 的遍历,同时更新 qbssum,再将 flag 置true。
    • 这样优化的好处在于,再没有改动关系网络时,重复查询 qbs 不用每次都遍历,节省大量时间。
  • group 中的查询操作:例如 getAgeVargetAgeMean ,每次遍历计数复杂度过高,良好地做法是动态更新存储数值,比如在group 中存储 valuesum,增减人员以及关系时动态调整数值,这样 getAgeMeangetAgeVar 查询采取在线查询会轻便不少。
  • sendindirectmessage:直接采用 dijsktra 还是不够好,在第三次作业中出现了 TLE 的情况。优化的方法是利用小顶堆来查询最小距离的点,可以优化到O(nlogn)。

五、架构设计

  • 图模型的构建:因为 person 中本身含有 id-value 的 Hashmap,因此整个图采用的是类似于邻接表的方式存储。
  • 对于每个人 Person 而言,他只了解和自己熟知的人的情况;对于 group 而言,只关心在本组中人员的管理;除此以外,Network 中存放了整个网络中的人员、群组、消息,也就是说,并不是笼统地将所有信息全部堆在 Network 中,而是分层次地表现社交网络。
  • 维护策略:吸收了出现 TLE 的bug的经验,对于一些查询的操作,尤其是逻辑上需要遍历的查询,每次都大量地、重复地循环遍历显然不是一个好办法。比较合理的方法是:采用动态调整数值,在增加人员或关系时维护数值,查询时直接O(1)得出结果,在性能上会有很大优化。
  • 善用 Hashmap存储数据,在一些查找操作中通过 key 获取元素方便快捷。

六、心得体会

本单元 JML 在难度上较前两个单元有所下降,完成作业比之前轻松,通过学习 JML 的阅读与书写,对于java 规范编程以及架构设计有了更神的理解,必须要指出的是根据 JML 编程并非是一板一眼的翻译!,如果直接翻译而在数据层次和设计上有所思考,性能上会表现的不好,需要结合结构采取更优良的设计来优化性能。

这是OO旅程的第三个单元,学到的东西很多,希望自己可以在最后一个单元持之以恒,学有所乐,学有所得。

posted @ 2021-05-30 15:29  Wuhaotian  阅读(47)  评论(0编辑  收藏  举报