BUAA_OO第三单元反思与总结
OO第三单元反思与总结
18375182 范竞元
第九次作业
第九次作业我主要按要求实现了\(MyPerson\)和\(MyNetwork\)和四个异常类,初步建立起来了人际关系网络。
具体实现分析
这次作业的主要精力耗费在了容器的选择和求连通块的算法选择中,在容器的选择中,因为规格中并没有要求某些东西按顺序排布,所以我采用了读取的时间复杂度很小的\(HashMap\)容器,\(Key\)中存放实例的独一无二的\(ID\),\(Value\)中存放实例,即\(HashMap<Integer, Object>\),这样在规格中需要遍历去寻找的就可以直接使用\(Get\)方法即可。在对于\(queryBlockSum\)方法的实现中,我是完全依照规格的实现方法去完成的,即执行\(n^2\)次\(checkCircle\)方法,所以造成了\(qbs\)指令过于高的时间复杂度。其中的\(checkCircle\)方法也用的是传统的\(DFS\)算法,可以说是中规中矩。在\(DFS\)中,需要对访问过的\(Person\)打上标记位,我采用了
new ArrayList<>(this.acquaintance.keySet());
这行代码,可以有效地将\(HashMap\)转换为\(Arraylist\),从而实现了标记位的处理。
\(Bug\)分析
- 在实现\(queryBlockSum\)方法的时候,我将\(sum\)最开始就初始化为\(1\),从而导致了没有任何\(ap\)操作时,即没有任何人时,执行\(qbs\)指令时的返回值为\(1\),这显然是不对的,因为\(queryBlockSum\)方法的内涵就是查询关系网络总共分为了多少块,所以当人数为\(0\)的时候,查询现有的连通块的结果显然为\(0\)。我认为,这个\(Bug\)的出现是因为我没有完全理解规格的内涵,也没有严格照着规格去实现方法。
- 另外一个\(Bug\)就是有关于复杂度的,在实现\(queryBlockSum\)方法的时候造成的时间复杂度过高。因此,在人数很多的时候,执行多次\(queryBlockSum\)方法的时候会造成\(CPU\)时间超时。我的改进方法是将\(queryBlockSum\)方法的时间复杂度分布到\(addPerson\)和\(addRelation\)这两个方法中,首先初始化一个用于计算连通块数量的全局变量\(blockSum\)为\(0\),然后每次执行\(addPerson\)时加\(1\),每次执行\(addRelation\)时,如果两个人不在一个联通块中的话就减\(1\)。这样在执行\(queryBlockSum\)方法的时候就可以直接返回全局变量\(blockSum\)的值,成功地将\(qbs\)指令的时间复杂度减少。
第十次作业
第十次作业在上一次作业的基础上,将\(Person\)构建成若干个\(Group\),并且支持各种组内查询,同时对Network的功能进行拓展,也实现了\(Message\)类,可以实现发送消息和删除消息等操作。并增加了\(4\)个异常类。
具体实现分析
此次作业的容器选择我也是和上一次一样都选择了\(HashMap\)类型的容器,只是有一点例外,在\(MyNetork\)类中有一个\(queryReceivedMessages\)方法,这个方法需要返回最近四个发送的\(Message\),用\(HashMap\)显然做不到,用\(ArrayList\)的话,时间复杂度又太高,所以我创建了一个容量为\(4\)的\(ArrayList\)专门用来存放最近四个发送的\(Message\),在\(addMessage\)时维护此\(ArrayList\)。另外在\(MyGroup\)中的\(getValueSum\)方法中,如果完全按照规格来写,那么复杂度一定是过高的,所以我在\(addPerson\)和\(delPerson\)这两个方法中对\(valueSum\)进行实时的更新,从而避免了\(n^2\)的时间复杂度。
\(Bug\)分析
- 在\(MyGroup\)中的\(getAgeVar\)方法中因为没有考虑到人数为零的特例,所以造成了除以零会报错的低级\(Bug\)。
- 这个\(Bug\)和上次的\(Bug\)有关,准确的说,是因为我完全将\(queryBlockSum\)方法的时间复杂度下放到了\(addPerson\)方法中,每执行一次\(addPerson\)方法就要执行一次\(checkCircle\)方法,导致如果大量执行\(addPerson\)方法的话,\(CPU\)时间也会过长,所以这次我又将放到\(addPerson\)方法中的时间复杂度又收回到\(queryBlockSum\)方法中,并优化了查询连通块数量的算法,加入了标志位,当一个人被访问过时,就不必再次访问了,这极大地减小了\(qbs\)指令的时间复杂度。
- 最后一个\(Bug\)出在\(MyGroup\)中的\(getValueSum\)方法中,我用的方法与规格的描述不同,这就导致了有些方面并不是严格符合规格的描述。在架构分析里说到\(blockSum\)是通过\(addPerson\)和\(delPerson\)实时更新的,然而我忽略了当输入\(ar\)指令进行\(addRelation\)操作时的对于\(blockSum\)的改变,因此导致了\(Bug\)的产生。在\(addRelation\)方法中加入对\(blockSum\)的操作即可消除这个\(Bug\)。
第十一次作业
第十次作业在第十一次作业的基础上,又细分了\(Message\)类,增加了红包消息类,表情消息类等等,并且在方法中细分了对这些类的特殊操作。
具体实现分析
这次的作业和上两次一样,容器也都用的是\(HashMap\)。在规格中约定了我要实现一个\(sendIndirectMessage\)类,我采用了最基础的迪杰斯特拉算法,我实例化了一个\(HashMap\)名为\(dist\)存放了每个点最终到指定点的最短路径,又实例化了一个\(PriorityQueue\)存放了每个点暂时的距离。因为优先队列里的元素要继承\(Comparable\)接口,并且要实现\(compareTo\)接口,所以我新建了一个类\(MyMinDist\)存放了一个键值对,\(key\)的值代表了\(Person\)的\(ID\),\(value\)的值代表了暂时的距离,两个\(MyMinDist\)实例中,\(value\)值小的\(compareTo\)就会返回\(0\),因此,就每次循环起始就直接从优先队列中取出最顶端的元素即可。
\(Bug\)分析
- 第一次作业将\(HashMap\)转换成\(ArrayList\),再进行传参,我为了减少复杂度,所以将这个方法放进了\(MyPerson\)中,原来在\(MyNetwork\)中时我调用的是\(MyPerson\)中的\(idLinked\)方法,到了\(MyPerson\)类中时,我是调用\(acquaintance\)的\(contains\)方法判断的,所以当\(ID\)和自身相等时,会出现返回值为\(false\)的\(Bug\)。在方法中加一个特判即可。
整体分析
\(UML\)类图如下:

因为另外几个\(Message\)类和\(MyMessage\)类较相似的缘故,所以在此图中省略不表。
在整体架构上我并没有过多的思考,而是仅仅完成了题目要求的继承接口类的设计,在架构上就显得非常的平庸。此外我发现,如果完全按照\(JML\)语言写出来的代码的性能是非常差的,必须要在具体实现和容器选择上下功夫。而且细节也要做到位,像除以\(0\)这样的小失误是不应该出现的。在互测时阅读其他人的代码时,我也发现自己在时间复杂度的平衡上有着明显的不足。
对于规格的阅读,我首先是结合代码主要理解方法的内涵,也就是方法的作用,然后通过理解的作用再细读代码。
对于基于\(JML\)规格来设计测试的方法和策略这方面,我是自己手动构造的基本数据,并没有进行全面地测试,也没有用\(JUNIT\)基本功能测试。
第三单元总结
通过\(JML\)语言的学习,我深刻了解到了规格抽象对于软件开发的作用,也能够通过仔细阅读规格,了解类,方法,属性的作用和内涵,之后再有效地进行代码的编写。也通过惨痛的经历了解到,就算有\(JML\)语言的引导,实现代码也需要自己使用更加高效的容器与算法,在架构的层面上也不能直接照搬规格,需要有自己更好更清晰的架构才行。

浙公网安备 33010602011771号