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

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

1.JML语言及应用工具链

  JML(Java Modeling Language)是用于对Java程序进行规格化设计的一种表示语言。JML是一种行为接口规格语言,基于Larch方法构建。它以标准化的语言描述了类与方法的属性和规格,便于代码工作人员理解和实现。

  JML以javadoc注释的方式来表示规格,每行都以@起头。有两种注释方式,行注释和块注释。其中行注释的表示方式 为 //@annotation ,块注释的方式为 /* @ annotation @*/ 。

  表达式包括原子表达式,例如\old(exp);\result;\not_assigned(x,y,...)。

  量化表达式,例如 (\forall int i,j; 0 <= i && i < j && j < 10; a[i] < a[j]); (\exists int i; 0 <= i && i < 10; a[i] < 0)。

  集合表达式,new JMLObjectSet {Integer i | s.contains(i) && 0 < i.intValue() }。

  操作符,包括java中定义的算数操作符、逻辑运算操作符等。

  在描述方法规格时,分为正常行为规格(normal_behavior)和异常行为规格 (expcetional_behavior)两种,包括前置条件(requires)、后置条件(ensures)和副作用范围限定(assignable)。

  相关工具包括Openjml、JUnit等。Openjml可用于检查JML语法,它还可使用SMT Solver来对检查程序实现是否满足所设计的规格。JUnit通过生成一个类似的java文件,来实现自动检测。

 

2. 部署SMT Solver

  我直接下载了openjml.jar工具包,这里只选取了MyPath.java中的两个函数size()和getNode()来测试。第一次运行时报错“需要数组,但找到ArrayList”,这是由于规格里描述的是数组,但是我出于算法和其他方面设定的是动态数组。所以为了匹配上规格,我修改了数据结构。

  这样还是出现了错误,它认为private属性不应该出现在public属性的ensures语句中。

 

3.JMLUnit及自动生成测试用例

  由于MyPath.java太复杂,我就新建了一个Demo.java用于测试。

  

  运行下面这条指令后,JMLUnitNG会自动生成一系列测试文件。

  

 

  之后我尝试在IDEA里直接运行主类Demo_JML_Test.java,但是它import的很多类系统会报错,找不到。IDEA内部也搜不到合适的插件。后来咨询了同学,发现还需要openjml和javac作为辅助工具。

   

   从运行结果来看,对于所有参数为int类型,它都是采取±231、±231 - 1 和0来测试的。这样的测试难免不够完备。

 

4.架构设计

  第一阶段比较简单,只需实现Path和PathContainer两个类。在所有指令中DISTINCT_NODE这一指令是比较费时的,如果每次收到指令时,都重新查找一遍,势必会浪费很多时间。因此我选择了每次加路径时都判断是否有新的点加进来,使用HashMap来存储这些点。这样就把点的计算分解到了很多条指令,而且没有改动的点也不需要重复计算。而对于DISTINCT_NODE指令,只需要每次返回HashMap.size()即可。

  第二阶段体现了图的结构,增加了两点之间是否邻接、是否可达以及最短路径等指令。由于图修改指令限制在20条以内,而查询指令却多达几千条,因此我沿用了第一次的思路,建立了一个记录最短路径数值的二维数组,每次图结构发生改变时就刷新,收到查询指令时直接返回数值。一开始我是用了Dijkstra来计算最短路径,因为它的时间复杂度O(n2logn)是要优于Floyd的时间复杂度O(n3)的。但是在真正运行时,我发现Dijkstra在收到add和remove指令时有明显的停顿,也就是说计算最短路径花费了比较长的时间,而换成Floyd却很流畅。我认为这是由于在实现Dijkstra算法时,我使用了HashSet这类容器来存储中间数据,还需要读取HashMap中的数据等,而且代码量较大。Floyd虽然理论上慢一些,但是它代码简单,而且直接访问内存中的二维数组,反而在实际运行时更快。

  第三阶段加入了实际问题,模拟地铁运行系统。其中最小票价与最低不满意度可以看作同一类问题,都是对边和换乘加权,因此我采用的是同一种算法。类似于第二阶段的最短路径,我也为这两个问题各设置了一个二维数组,来存储任意两点之间的数据。而刷新它们需要两次Floyd。第一遍刷新只刷新每条路径内部,不在同一条路径内的点设置数据为无穷,计算完毕后,给所有数据加上一个换乘权值(2或者32)。第二遍刷新,则是对所有点进行的常规Floyd,以第一遍刷新的数据为基础,计算最短路径。最后输出答案时,对于非零数据要再减去一个换乘权值。这个算法的重点是不关注具体在哪里换乘,而是关心一共换乘了几次。每经过一条路径,都加了一次权值,经过n条路径代表换乘了n-1次,因此最后还要减去一次。

  对于连通块和最少换乘这两个问题,我建立了一个关于路径的二维数组。之前建立的数组都是描述点对点的,而这里是把一条路径看作一个整体,描述路径对路径的。在这个二维数组中,描述路径与路径之间的连接关系,相当于路径间“最短路径”。这样对于点与点之间的最少换乘问题,就转换为了路径与路径之间的最短距离(相交的两条路径距离设为1)。连通块的计算也是也该二维数组为基础。

  总的来说,这一单元的架构的中心思想是“以空间换时间”。我设置了较多的数组来存储数据,每次修改图结构时刷新,而查询时直接输出。

 

5.bug修复

   这一单元在测试中没有出现bug。

 

6.心得体会

  经过这一单元的练习,我对代码规格有了更深刻的理解。第一次作业时我还尽量完全按着规格的思路来写代码,但是后来意识到规格只是描述了期望的结果,代码的实现过程并不需要和规格的设计思路相同。此外还复习了一些常用的图算法和图相关数据结构,收获很大。希望在最后一个单元可以画上圆满的句号。

 

posted @ 2019-05-22 11:17  cathywang03  阅读(159)  评论(0编辑  收藏  举报