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

设计策略

​ 本单元的输入输出代码和主体控制代码都已经给出,我们需要实现的只有具体的各种方法,而这些方法的接口规范也已经给出,只需要保证功能完全符合接口的要求即可。

​ 在实现的过程中,我认为首先需要通过阅读接口的规范,大致上了解所有的功能,这样能对自己实现的所有功能有整体上的把控。接下来需要针对各种操作思考接口功能实现的方法,如是否需要增加辅助数据,是否需要对部分运行结果和运行中间量进行缓存。在整体实现方式决定之后,再根据前面设计的内容进行数据结构与容器的选择,保证功能实现的正确性和性能。最后,根据前面已经分析的各种方法和接口的设计,加入各接口的具体细节,如抛出异常等,完成最终功能的实现。

测试方法和策略

​ 本单元使用的测试方法主要是三部分:Junit测试、黑箱生成大量随机数据对拍和特殊数据构造测试。

Junit测试

​ 本单元使用IDEA内内置的Juint的测试方式进行了测试,根据对应方法的接口要求构造了方法对应的覆盖性数据,并由IDEA自动完成测试。

黑箱测试

​ 根据数据要求生成了大量随机数据,并通过对拍程序进行了一定的测试。

特殊数据构造

​ 最后,根据边界条件等构造了特殊数据,如可能导致TLE的数据等,进行了针对性的测试。

黑箱生成大量随机数据

特殊数据构造

容器的选择和使用

HashMap

​ 本单元中绝大部分数据都是通过HashMap存放的,包括MyGroup中的people,MyNetwork中的person,groups,messages等。HashMap的key为相应的people的id,group的id,message的id等专有id,HashMap中的value为id对应的person,group,message等。

​ 选择这样设计HashMap的第一个原因是id是唯一化的,可以通过id来确定某一个具体的对象或者比较两个对象是否为同一个,如判断某个集合中是否含有某个对象元素,只需要通过containsKey(id)操作即可完成。

​ 其次,HashMap具有速度上的优势,其查找为O(1)复杂度,而插入和删除等操作在O(1)-O(n)之间不定,当元素分布较为稀疏时为O(1)。这种时间复杂度比直接使用ArrayList等容器小的多,可以明显提高速度。

LinkedList

​ 在MyPerson类中也使用了LinkedList存储messages。

​ 选择LinkedList的原因是在MyPerson类中的messages需要记录插入的顺序,而且插入和读出主要集中在头部和尾部,显然使用LinkedList实现较为方便且速度较快。(这里也可以使用ArrayList实现,但需要将头部和尾部调换。即规格中要求插入头部则插入ArrayList的尾部)。

PriorityQueue

​ 在实现第三单元的sendIndirectMessage时,使用了堆优化的Dijkstra算法。在实现小顶堆时使用了PriorityQueue来实现。

容器选择经验

​ 选择容器首先需要了解容器使用的环境。因此,在使用某一个容器之前,需要了解该存储内容集合的增删查改要求及顺序问题,也就是各种操作方法的要求。随后,对增删改查的频率和性能要求进行分析,即各种方法的调用频率及其对整体性能的影响大小。最后,统筹对各种容器的特点进行分析,包括可行的操作,增删改查时间复杂度和需求的匹配程度。如果两种容器效果接近,则优先选择使用较为简单的容器。

性能问题的分析

第一次作业

​ 在第一次作业中,比较有可能出现性能问题的地方是MyNetwork中的isCircle方法。

​ 这里通过并查集的方式实现,就可以保证速度足够。

int root1 = find(id1);
int root2 = find(id2);
return root1 == root2;

第二次作业

​ 在第二次作业中,我的MyGroup中的getValueSum方法出现了性能问题。

​ 在修改前的设计中,我通过\(O(n^2)\)嵌套遍历了整个Group中的所有人,并查看他们是否认识,并计算ValueSum。这种设计导致了MyGroup中的Perosn较多时,遍历的时间过长。

​ 修改bug的方式为,第一层遍历为Group中的所有人,嵌套的遍历是第一个人所认识的所有人,如果某个人的认识的人在MyGroup中,则增加valueSum的值。

for (Person varPersonI: people.values()) {
    for (Person varPersonJ: ((MyPerson) varPersonI).getAcquaintance()) {
        if (hasPerson(varPersonJ)) {
            valueSum += varPersonI.queryValue(varPersonJ);
        }
    }
}

​ 改进分析:虽然时间复杂度仍然保持在\(O(n^2)\),但是第二层遍历遍历的内容有了大幅度的降低。原因是将人之间认识的关系建立需要的指令数远大于直接加入到Group中的指令数,在指令总数有限制的情况下,这种遍历方式需要遍历的节点数大幅度降低。

​ 在修改之后,底线运行时间有了大幅度的改善。

第三次作业

​ 在第三次作业中,最容易出现问题的位置是MyNetwork中的sendIndirectMessage方法中的最短路问题。在这里我选择使用了堆优化的Dijkstra算法。

​ 堆优化的Dijkstra算法的时间复杂度为\(O(nlgn)\),相较于其他的算法有较快的速度。

​ 堆优化的Dijkstra需要实现小顶堆的容器,在这里选择了使用使用优先队列PriorityQueue来模拟小顶堆的实现,从而完成了整个Dijkstra算法的需求。

其他性能问题

​ 其他的需要性能问题集中在容器的选择上,我大量使用了HashMap,从而能保证查询等操作的速度。具体情况见上一节。

架构设计

​ 本单元的整体设计为将Network作为整个社交网络的全部载体。每一个Person都在Network中登记与活动。此外,可以将Person聚集在一起建立Group(加群)。Person和Person之间还可以建立直接联系(加好友)。对于上述架构,Person之间可以发消息,建立关系等互动行为。

​ 在本单元的大部分设计上,在网络节点构成的图中,通常是将Perosn作为节点,Person和Person之间的关系或者value作为边的连接边。例如,在并查集的设计方式里,就是将Person和Person之间通过相识关系相连接,并指向一个”大队长“,通过类似于一个个”大队长“带领的群体的方式,实现并查集的各种行为。又例如在sendidirectMessage中,使用Dijkstra算法寻找最短路径时,就是将Person作为了节点,将Person和Person之间的Value作为了边的权重,通过使用Dijkstra算法在这样的一个图中,寻找到了最短路径。

总结

​ 相较于前两次作业,我认为在本单元的学习中,我收获最大的地方在于对规格和顶层设计的认识。

​ 通过学习了JML,大体上了解了工程项目中对于底层的规范与设计。也在学习与实现的过程中了解到设计与实现相分离的理念。实现的过程不能完全按照设计中给出的语言来实现,要重新思考并重新构建。最终实现的结果为在较优的性能情况下满足接口的具体规格要求,这才是本单元要考虑和付诸行动的地方。

​ 在本单元中,我除了完成作业以外,还在研讨课上总结了《面向接口编程》。结合在研讨课中学习的内容,和在本单元中的代码实现经历,更深入的理解了何为规格,又何为实现。

posted @ 2021-05-29 22:30  RE_REG  阅读(61)  评论(0)    收藏  举报