1 实验环境配置
本次实验需要在eclipseIDE中配置EclEmma,在eclipse中的Marketplace搜索EclEmma如图:
然后按步骤安装即可。
2 具体过程
2.1 Poetic Walks
本次实验给出了一个图接口,要求我们建立一个边图类、一个点图类分别继承自这个图接口,并在里面实现一系列方法,并且实现抽象数据型,并用这个图的抽象数据型完成poet的工作。
(1)完善Graph接口类,并运用用泛型的思想,将String拓展为泛型L类;
(2)实现Graph类的方法:add, set, remove, vertices, sources, targets;
(3)利用实现的Graph类,实现GraphPoet类,若输入的文本的两个单词之间存在衔接的词,则插入该词;若存在多个这种词,则选取边权值较大者。
2.1.2 Problem 1: Test Graph <String>
(1)思路:为Graph<String>设计相应的测试策略,主要针对其各个方法进行等价类划分的测试。
(2)过程:
测试策略如下,按照其编写6个测试
2.1.3 Problem 2: Implement Graph <String>
实现Edge类
(1)属性
Edge应包括边的权值、起点和终点,因此定义private类型变量如下:
(2)方法
Edge | 初始化构造方法,初始化新边的两个点和边的权值 |
checkRep | 检查表示不变性,边不为空且权值大于等于0 |
getsource | 返回边的一个点source |
gettarget | 返回边的一个点target |
getweight | 返回边的weight |
toString | 返回一条边的字符串,“起点->终点权重为weight” |
(3)AF, RI, Safety from rep exposure
(4)测试策略
实现ConcreteEdgesGraph类
(1)属性
ConcreteEdgesGraph类中应该包括顶点set表和边权值list表,定义private类型的两个表如下:
(2)方法
ConcreteEdgesGraph | 构造方法 |
checkRep | 检查表示不变性,edges长度是大于0的实数,有起始点 |
add | 顶点不为空时,添加一个顶点进入点表中 |
set | 输入source,target,weight,分别为边的起点、终点和权值 若权值为负,返回-1。若权值为正且新边已经存在,则除去原边并添加新边。若权值为正且新边不存在,则直接添加新边。若权值为0且新边已经存在,则除去原边 只要改变了原边权值,都返回原边权值,没有权值则返回0 |
remove | 除去某个点及与它相邻的所有边。只需使用迭代器,遍历edges,寻找是否有边的起点或者终点为该点并删去 |
vertices | 返回所有的点集 |
sources | 输入一个终点,返回与它相连的所有边和起点构成的Map |
targets | 输入一个起点,返回与它相连的所有边和终点构成为的Map |
toString | 将整个图中所有点的指向转化为一条字符串输出 |
(3)AF, RI, Safety from rep exposure
(4)测试策略
继承Graph的测试策略GraphInstanceTest,只需增加toString的测试:
实现Vertex类
(1)属性
Vertext类中应包括点的名字,点的起点表Map,点的终点表Map,因此定义Private类型的表如下:
(2)方法
Vertex | 构造方法,用点的名字创建 |
checkRep | 检查表示不变性,每个边的权值应该大于0 |
getname | 返回点的名字name |
getsource | 返回能到达该点的所有点和边权值构成的Map |
gettarget | 返回某个点能到达的所有点和边权值构成的Map |
addsource | 在起点Map中加入某起点 若weight不为0,则将其加入allsource中(若起点已存在,则更新其weight并返回原weight,不存在则直接构建新点并返回0) 若weight为0,则移除起点(不存在返回0,存在返回原weight)
|
addtarget | 在终点Map中加入某终点 若weight不为0,则将其加入alltarget中(若终点已存在,则更新其weight并返回原weight,不存在则直接构建新点并返回0) 若weight为0,则移除终点(不存在返回0,存在返回原weight)
|
removesource | 在起点表中删除某起点,并返回原来的边权值 |
removetarget | 在终点表中删除某终点,并返回原来的边权值 |
toString | 得到一个点的字符串表示 |
(3)AF, RI, Safety from rep exposure
(4)测试策略
实现ConcreteVerticesGraphTest类
(1)属性
ConcreteVerticesGraph只需包括Vertex构成的List点集,因此定义private类型的表如下:
(2)方法
ConcreteVerticesGraph | 构造方法 |
checkRep | 检查表示不变性,vertices中不能有重复点 |
add | 顶点不为空时,添加一个顶点进入点表中 |
set | 输入source,target,weight,分别为边的起点、终点和权值 若权值为负,返回-1。若权值为正且新边已经存在,则除去原边并添加新边。若权值为正且新边不存在,则直接添加新边。若权值为0且新边已经存在,则出去原边。只要改变了原边权值,都返回原边权值,没有权值则返回0 |
remove | 除去某个点及与它相邻的所有边。只需使用迭代器,遍历vertices,寻找是否有与待删除点相同的名字的点直接删去即可,如果名字不同,则在该点的起点表和终点表中寻找删去即可 |
vertices | 返回所有的点集 |
sources | 输入一个终点,返回与它相连的所有边权值和起点构成的Map |
targets | 输入一个起点,返回与它相连的所有边权值和终点构成为的Map |
toString | 将整个图中所有点的指向转化为一条字符串输出 |
(3)AF, RI, Safety from rep exposure
(4)测试策略
继承Graph的测试策略GraphInstanceTest,只需增加toString的测试:
2.1.4 Problem 3: Implement generic Graph<L>
(1)首先将具体类的声明及属性改为如下图所示:
(2)类似地,再将代码中的String用L替换,可根据eclipse的报错找到位置并修改,将方法等其他部分修改完毕即可。
(3)测试代码中的也需修改,在声明对象时添加<String>,意为泛型在该测试中运用String类型。如:
2.1.5 Problem 4: Poetic walks
测试策略
Implement GraphPoet
(1)属性
(2)方法
GraphPoet | 输入文件的路径,一行行读入,储存在List中,接着每次根据相邻的元素,在图中添加新的边 |
checkRep | 检查不变性,必须保存从语料库文件生成的图 |
poem | 输入需要进行扩充的字符串,由StringBuilder保存。 每读取一个词,当前词作为source,下一个词作为target,在garph中寻找source的终点表中是否有与target的起点表中相同的元素,若存在,找到权值和最大的点加入source和target之间。最后返回扩充后的字符串。
|
toString | 调用ConcreteEdgesGraph中的toString方法,输出图结构 |
(3)AF, RI, Safety from rep exposure
2.2 Re-implement the Social Network in Lab1
实验要求我们基于在 3.1 节 Poetic Walks 中定义的 Graph<L>及其两种实现,尽可能复用ConcreteEdgesGraph<L>或ConcreteVerticesGraph<L>中已经实现的方法,重新实现 Lab1 中 3.3 节的 FriendshipGraph 类,通过基本操作实现FriendshipGraph中addVertex,addEdge和getDistance三个接口,要求不能修改父类rep,需再运行提供的main()和执行Lab1中的Junit测试用例,使之正常运行。
3.2.1 FriendshipGraph类
(1)属性
FriendshipGraph需要一个图,为Person构成的ConcreteEdgesGraph,定义private类型的表如下:
(2)方法
FriendshipGraph | 构造方法 |
addVertex | 在图中增加新Person,调用ConcreteEdgesGraph中的add
|
main | 复制Lab1的main即可 |
addEdge | 为某个人a增加朋友b,调用ConcreteEdgesGraph中的set
|
getallpeople | 直接返回即可
|
getDistance | 得到两个人之间的最短距离。与Lab1类似,稍加修改即可。先判斷person1和person2是不是同一个人,如果是,直接返回距离为0。 如果不是则继续通过BFS算法计算距离:首先将c1入队,并且把第一个元素c1和下标0入集合,当队列非空时,弹出队首元素top,并且得到top在集合waypoints中的下标distance,然后调用gethisfriend函数得到队首元素的所有朋友allfriend,只要allfriend中的元素与c2不同,就把这些元素全部入队,并且把这些元素及下标distance+1放入集合。只要队列非空,继续执行以上步骤,直到找到某个元素与c2相同并且返回这个元素在集合waypoints中的下标。如果直到队列为空还没找到c2,则返回-1。
|
(3)AF, RI, Safety from rep exposure
3.2.2 Person类
(1)属性
Person类应有两个属性,分别为String类型的名字,以及字符表以记录所有已存在的名字(可用于防止重复的人名),定义private类型变量如下:
(2)方法
Person | 构造方法,如果是新名字则创建person |
getmyname | 返回本人名字 |
(3)AF, RI, Safety from rep exposure
3 实验过程中遇到的困难与解决途径
遇到的难点 | 解决途径 |
进行Problem2时,报错 | 发现声明ConcreteEdgesGraph类时,多加了一个<L> |
进行Problem3时,转换泛型时对泛型不是很理解 | 复习课件,网上查询资料 |
ConcreteVerticesGraphTest运行测试时,报错 | 根据failure trace找到了错误之处,发现是在设计测试时,自己将预计输出写错了 |
4 经验感想
(1)ADT编程,使得写过的类的属性、方法可以通过继承等方法复用,与直接面向应用场景编程相比,代码的复用性更高。
(2)泛型能够适应更多变化,应用范围更广泛,可以使得代码用于更多的场景。
(3)在给出ADT的规约后就开始编写测试用例,可以更好地保证代码的正确性,及时修改错误。