3.1.1 Get the code and prepare Git repository
3.1.2 Problem 1: Test Graph <String>
3.1.3 Problem 2: Implement Graph <String>
3.1.3.1 Implement ConcreteEdgesGraph
3.1.3.2 Implement ConcreteVerticesGraph
3.1.4 Problem 3: Implement generic Graph<L>
3.1.4.1 Make the implementations generic
3.1.4.2 Implement Graph.empty()
3.1.5.3 Graph poetry slam(输入和数据库均采用的是题目说明中提供的样例)
3.2 Re-implement the Social Network in Lab1
3.2.2 Person类(实际上经过本实验的修改后,只调用了构造方法,所以就不列出各个方法了)
- 实验目标概述
本次实验训练抽象数据类型(ADT)的设计、规约、测试,并使用面向对象
编程(OOP)技术实现 ADT。具体来说:
⚫
针对给定的应用问题,从问题描述中识别所需的 ADT;
⚫
设计 ADT 规约(pre-condition、post-condition)并评估规约的质量;
⚫
根据 ADT 的规约设计测试用例;
⚫
ADT 的泛型化;
⚫
根据规约设计 ADT 的多种不同的实现;针对每种实现,设计其表示
(representation)、表示不变性(rep invariant)、抽象过程(abstraction
function)
⚫
使用 OOP 实现 ADT,并判定表示不变性是否违反、各实现是否存在表
示泄露(rep exposure);
⚫
测试 ADT 的实现并评估测试的覆盖度;
⚫
使用 ADT 及其实现,为应用问题开发程序;
⚫
在测试代码中,能够写出 testing strategy
- 实验环境配置
本次实验需要安装EclEmma,直接在Eclipse Marketplace中搜索安装
遵循引导即可完成。
在这里给出你的GitHub Lab2仓库的URL地址(Lab2-学号)。
https://github.com/ComputerScienceHIT/HIT-Lab2-1190201003
- 实验过程
请仔细对照实验手册,针对两个问题中的每一项任务,在下面各节中记录你的实验过程、阐述你的设计思路和问题求解思路,可辅之以示意图或关键源代码加以说明(但千万不要把你的源代码全部粘贴过来!)。
-
- Poetic Walks
这一部分给出了一个Graph的接口。要求编写ConcreteEdgesGraph和ConcreteVerticesGraph两个Graph的实现类,并且编写相应的测试类。在实现过程中,要自行编写规约。
-
-
- Get the code and prepare Git repository
-
按照实验手册提供的url,在本地打开git bash,使用git clone命令将远端仓库克隆到本地,以此获取代码。
-
-
- Problem 1: Test Graph <String>
-
以下各部分,请按照MIT页面上相应部分的要求,逐项列出你的设计和实现思路/过程/结果。
3.1.2.1 实现GraphInstanceTest类
1. testAdd()
思路:按add的规约,按点的等价类划分即可
2. testSet()
思路:按照set的规约,需要对于权值的大小进行等价类划分,并且对于边是否存在也要进行测试
3. testRemove()
思路:按remove的规约,按点的等价类划分即可
4. testVertices()
思路:按Vertices的规约,按点的等价类划分即可
5. testSources()
6. testTargets()
结果:只做到这一步其实还不能运行该测试类,因为emptyInstance()还没有实现。完成了后续的工作后,可以得到以下结果
3.1.3Problem 2: Implement Graph <String>
以下各部分,请按照MIT页面上相应部分的要求,逐项列出你的设计和实现思路/过程/结果。
3.1.3.1Implement ConcreteEdgesGraph
3.1.3.1.1 实现Edge类
为了实现ConcreteEdgesGraph类,首先得实现作为边的Edge类
field:
要构造一个边,需要有起点,终点和权值,因此field由source,target和weight构成:
AF,RI等:
// Abstraction function:
// AF(source)为边的起点
// AF(target)为边的终点
// AF(weight)为边的权值
// Representation invariant:
// weight为正整数
// source和target不为空
// Safety from rep exposure:
// source,target和weight设置为了private
// source,target和weight在返回时采用了防御式拷贝
构造方法:
构造方法根据起点,终点和权值来初始化一条边即可;
checkRep:
作用:检查RI;
思路:根据所编写的RI依次进行检查即可;
getSource:
返回边的起点;
getTarget:
返回边的终点;
getWeight:
返回边的权值;
toString:
功能:返回边的字符串;
思路:为了方便阅读,采取了“起点--权值->终点”的形式;
3.1.3.1.2 完成ConcreteEdgesGraph类(add,set等方法的规约和Graph接口中完全相同,在报告中不再重复)
根据要求,写出AF,RI等:
// Abstraction function:
// AF(vertices)是图中的所有点
// AF(edges)是图中的所有边
// Representation invariant:
// 所有点都存在于vertices中
// 所有边都存在于edges中
// 权值必须为正整数
// Safety from rep exposure:
// vertices和edges都设置为private类型
// vertices在返回时使用了防御式拷贝
filed:
要构造一个图,需要一个边集合,一个点集合:
构造方法:
直接采取缺省的构造方法
checkRep:
作用:检查RI;
思路:根据所写的RI,逐项进行检查即可;
add:
作用:将一个点加入图中;
思路:检查该点是否在图中。已有的点返回false,新的点加入vertices;
set:
作用:将一条新的边加入图中,或者更新一条已有的边的权值,或者移除一条边;
思路:检查weight的值。如果小于0,返回-1;如果等于0,移除该边;如果大于0,将此边加入图中,或者更新该边的权值(取决于这是否是新的边);
实现:以下为weight大于0的情况:采用迭代器来检查是否是新的边,对于需要更新的情况,先移除原边,再加入新的边即可;
remove:
作用:移除一个点,以及和它有关的边;
思路:检查图中是否存在这个点;如果存在,则移除;如果不存在,返回false;
实现:需要注意的是,以被移除点作为起点和作为终点的边均要移除;
vertices:
作用:返回点集合;
实现:采取防御式拷贝
sources:
作用:返回<起点,权值>构成的map;
思路:构造一个空的map,遍历边集合,将和target匹配的<起点,权值>加入map中即可;
target:
作用:返回<终点,权值>构成的map;
思路:同8;
toString:
作用:返回图的字符串;
思路:依次返回每条边的字符串;
3.1.3.1.3 实现ConcreteEdgeGraphTest类
本测试类只需要测试Edge中的方法,其他的方法都在GraphInstanceTest中测试了。
1. testConcreteEdgesGraphtoString():
- testEdgeGetSource():
- testEdgeGetTarget():
- testEdgeGetWeight():
测试结果:
3.1.3.2Implement ConcreteVerticesGraph
3.1.3.2.1 实现Vertex类
要利用点来构成图,首先要先实现一个表示点的类Vertex
field:
每个点都有一个指向该点的起点集,一个该点指向的终点集以及作为点的标识的名字。集合采用<L, Integer>构成的map来表示,这样还可以记录下权值:
AF,RI等:
// Abstraction function:
// AF(name)为点的名字
// AF(sources)为所有指向该点的点以及对应边的权值
// AF(targets)为所有该点指向的点以及对应边的权值
// Representation invariant:
// 所有点均不为空
// 所有权值均大于0
// Safety from rep exposure:
// name,sources,targets均设置为private
// 返回时采用防御式拷贝
构造方法:
按照点的名字进行点的初始化
checkRep:
作用:检查RI;
getName:
作用:返回点的名字;
getSources:
作用:返回<起点,权值>构成的起点表;
getTargets:
作用:返回<终点,权值>构成的终点表;
addSource:
作用:在起点表中移除或者加入或者更新一个起点,便于之后实现ConcreteVerticesGraph类中的set方法;
思路:类似set方法。其中移除通过后续编写的removeSource来实现,加入新的点或者更新通过HashMap提供的put方法实现;
addTarget:
作用:在终点表中移除或者加入或者更新一个终点,便于之后实现ConcreteVerticesGraph类中的set方法;
思路:同8;
removeSource:
作用:在起点表中删去一个起点;
思路:通过HashMap提供的remove方法实现。不过由于remove方法返回的是Integer,本方法的实际目的是将Integer转化成int。在Integer不是null的情况下,可以将Integer直接赋给int,所以当remove返回null时,我们需要返回0;
removeTarget:
作用:在终点表中删去一个终点;
思路:同10;
hashCode和Equals方法直接通过eclipse提供的插件来实现;
3.1.3.2.2 实现ConcreteVerticesGraph类(add,set等方法的规约和Graph接口中完全相同,在报告中不再重复)
field:本实现类是基于点来实现的,所以需要一个表示所有点的集合。为了便于检查RI,所以采用了List,而不是Set。
AF,RI等:
// Abstraction function:
// AF(vertices)为图中所有的点
// Representation invariant:
// vertices中的点不可重复
// Safety from rep exposure:
// vertices设置为private类型
// vertices在返回时使用了防御式拷贝
构造方法:
直接采用缺省的构造方式进行初始化;
checkRep:
作用:检查RI;
add:
作用:在图中加入一个新的点;
思路:通过name来判断该点是否是在图中。如果是已有的点,返回false;如果是新的点,通过name来初始化一个新的Vertex,然后加入图中;
set:
作用:将一条新的边加入图中,或者更新一条已有的边的权值,或者移除一条边;
思路:通过Vertex类中的addSource和addTarget方法就很容易实现;
remove:
作用:移除图中的一个点,以及和这个点相关的边;
思路:遍历图中的点。如果是该点,则移除;如果是该点的起点,则调用removeTarget方法;如果是该点的终点,则调用removeSource方法;
以下是每次遍历需要执行的内容:
vertices:
作用:返回点集合;
思路:由于vertices是List,所以需要再建立一个Set,依次加入点
实现:采取防御式拷贝
sources:
作用:返回<起点,权值>构成的map;
思路:返回指定点的起点表即可;
target:
作用:返回<终点,权值>构成的map;
思路:同8;
toString:
作用:返回图的字符串;
思路:依次返回每条点的字符串;
3.1.3.1.3 实现ConcreteVerticesGraphTest类
只需要测试Vertex类中的方法。
testConcreteVerticesGraphtoString:
testGetName:
testGetSources:
testGetTargets:
testAddSource:
思路:按照规约,需要分别就点的等价类和权值的等价类进行划分;
testAddTarget:
testRemoveSource:
testRemoveTarget:
testEquals:
思路:要测试点是否相等,需要考察起点表,终点表和名字;
测试结果如下:
3.1.4 Problem 3: Implement generic Graph<L>
3.1.4.1 Make the implementations generic
将所有之前使用String实现的地方全部修改为泛型实现,然后根据eclipse的语法错误提醒来完成所有的修改;
3.1.4.2 Implement Graph.empty()
选择一个具体的实现类就行了。这里选择的是使用ConcreteEdgesGraph。
补充了对于String,Integer,Float,Character这四种具体类型的测试;
这里的测试策略是利用Collection来创建一个空Set,然后测试empty方法是否也能正常生成一个空Set。
至此,已经完成了Graph的两个具体实现类的泛型实现以及测试类。
以下是所有的测试和覆盖率结果;
其中GraphInstanceTest类的测试结果已经包含在实现类的测试中。