面向对象设计与构造第三单元总结
一、实现规格所采取的设计策略
- 经常阅读规格,结合方法名其实基本上已经能猜出来规格想要做什么了。对于部分
\forall和exists,其约束条件稍微看下即可,不同寻常的约束条件可以一眼看出来,其余时刻抓住规格中的内涵,可以很快地完成理解。 - 对于整体的实现,笔者代码按照自底向上来完成。
- 先通过复制粘贴解决重复劳作的异常类
- 分析其他主要类的结构,理清相互间的调用关系,首先选择被调用的类下手,完成实现后可以帮助理解那些调用的类的需求。
- 整个过程方便理解,同时IDEA所弹出的红色警告也要少很多。
- 对于主要类的实现,主要先整体把握需求,再自上而下依次完成。
- 阅读类的JML需求,基本把握这个类整体上在干什么,关注JML中那些带有循环的方法,这对类属性容器的选择有着重要影响。
- 根据需求,为类属性选择合适的容器。然后从上到下依次实现类的每一个方法,对于复杂度较高的方法采用考虑特定的算法来降低下复杂度,如并查集、堆优化等。
HashMap完成了太多的事情,不少方法没啥可以设计的。
二、基于JML设计测试的方法和策略
- 对于的测试,笔者还是主要通过对拍来完成,辅以JUnit测试。记住JML规格的大致内容,和他人比较结果的差异性,容易找出基本的正确性问题。但JUnit的测试依旧很有必要,其主要用于保证方法实现的完备性。
Junit测试
- 主要测试对象:JML内容特别多的方法、进行性能优化后的方法、复杂度较高的方法
- JUnit主要通过assert断言来完成测试,比较输出与期望输出是否一致。
- 样例生成:
- 结果容易计算的方法:直接测试方法内计算得到
- 需要辅助计算的方法(如最短路等),可以使用python等进行计算,采取现有的库(如
NetworkX等),利用从文件中读取输入输出的方式来进行assert比对。
- 时间计算:
- 在测试方法内容的前后通过
System.currentTimeMillis()获取时间,可以大致判断方法的性能,决定是否做进一步的优化。
- 在测试方法内容的前后通过
三、容器的选择与使用
-
容器的选择根据需求来定。经常需要查找的适合用
HashMap,经常需要从头尾插入的用LinkedList,需要排序的用PriorityQueue等,需要不重复计数时用HashMap和Hashset,其他不知道用什么的时候用ArrayList。 -
纵观三次作业的所有类属性,除了
Person的messages是LinkedList以外,其他清一色的HashMap。从id得到对象,典型的HashMap需求。规格中频繁要求获取某个对象、判断是否存在,使用HashMap的containsKey、get、put、merge等可以很好地解决问题。尽管偶尔需要遍历,但无伤大雅。
四、性能问题
- 为了避免CTLE,需要对复杂度进行适当的降低,需要尽量避免出现
O(n^2)及以上的复杂度。在降低复杂度后,设计基本上可以避免超时的问题。
第9次作业
- 性能问题主要来自
queryBlockSum,按照传统的方式将会是O(n^2)的复杂度。可以采用并查集来维护,并进行路径压缩,就算每次调用该方法都计算一次,复杂度也只是O(n)。
第10次作业
-
性能问题主要来自
ageVar和valueSum。尽量不要采用查询一次计算几次的方式,毕竟变动是少量的,可以在发生变动时进行维护即可。发生变动仅存在于addPerson、delPerson、addRelation等,维护较为简单。估算发现所有数据均不会爆int,除了不要除0,应该就没有什么需要特别注意的了。 -
平均数向下取整后的方差公式:
(年龄平方和 - 2 * 年龄和 * 平均值 + 总人数 * 平均值 * 平均值) / 总人数
第11次作业
- 性能问题主要来自
sendIndirectMessage,本质上是计算发送者和接收者之间的最短路径。采用Dijkstra算法,同时用PriorityQueue来进行堆优化,将复杂度降到O(nlogn)即可。
五、架构设计
-
架构没啥可设计的,本单元,由于JML的需要,基本上已经把大部分内容限制死了,在需求的前提下,大部分人所采用的设计基本上大同小异,趋于一个近似的较优解。 -
由于异常类差别并不大,可以独立出一个
ExceptionCount,作为异常类的static final属性,方便对调用该异常的id进行管理,同时也方便实现输出。 -
根据实际的需求,稍做继承,如下图所示:
![]()
让其余三个特殊
Message类继承自MyMessage类,可以更好地完成实现,避免代码上的重复。 -
图模型的构建与维护:
- 在单独将图独立为一个类时,
Person当作结点,Edge为边和权重,可以方便对图进行整体上的管理。 - 在其不独立时,其从
Network和Person的类属性容器中获取所需要的信息。Person当作结点,从其所持有的acquaintance中获取边和权重。图不需要刻意去建立,在需要时自然而然就出现了。 - 图的维护:由于
Dijkstra是调用时才计算的,图的维护基本上只有在Group中addPerson,delPerson,在Network中addRelation时一并更新所有Group了。
- 在单独将图独立为一个类时,
六、感想与体会
- JML是形式化描述语言,其描述的内容基本不具有二义性,在某种程度上能够更好地理解用户需求。
- JML其目的可能是为了测试与实现的分离,二者交由不同的人来完成,可以同步进行工作,其对于开发来说还是具有一定意义的。
- 面向接口编程是一种思维方式,和继承相互辅助,可以更加灵活一点。
lambda表达式建议使用,其带来了代码的简洁性,在大部分情况下可读性更高,同时性能上并没有下降很多,评测机上的CPU耗时有一定的波动,笔者并没有看出其性能上有特别的劣势。


浙公网安备 33010602011771号