1.实验目标概述
本次实验训练抽象数据类型(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 并据此设计测试用例。
2.实验过程
2.1Poetic Walks
该任务主要是通过实现一个图的模块来练习ADT的规约设计和ADT的不同实现。
(1)完善Graph接口类,并运用泛型的思想,将String拓展为泛型L类;
(2)实现Graph接口类:以边和点两种方式实现接口;
(3)利用实现的Graph类,应用图的思想,实现GraphPoet类。如果输入的文本的两个单词之间存在桥接词,则插入该桥接词;若存在多个单一桥接词,则选取边权重较大者
2.1.1Get the code and prepare Git repository
从老师给定的网站中提取出实验相关代码。
2.1.2Problem 1: Test Graph <String>
根据测试的策略进行划分:
@Test public void addTest()
{
测试相关边是否存在,根据边的有无进行测试
@Test public void setTest()
{
测试相关点是否存在,测试点是否存在
@Test public void toStringTest()
{
测试相应tostring内容是否和输入内容相符
@Test
public void GetSourceTest() {
@Test
public void GetTargetTest() {
@Test
public void GetWeightTest() {
测试结果如下:
2.1.3Problem 2: Implement Graph <String>
2.1.3.1Implement ConcreteEdgesGraph
在Edge中需要实现的方法如下:
checkRep:检查表示不变性,边不为空且权值大于等于0
private void checkRep()
{
assert this.target!=null;
assert this.source!=null;
assert this.weight>0;
getSource:返回边的一个点source
public L getSource(){return this.source;}
getTarget:返回边的另一点target
public L getTarget(){return this.target;}
getWeight:返回边的weight
}public int getWeight(){return this.weight;}
toString:返回一条边的字符串
public String toString()
{
return "source:" + this.source +"target:" + this.target +"weight:" + this.weight +'\n';
}
在ConcreteEdgesGraph中需要实现如下方法:
checkRep
检查表示不变性,edges长度是大于0的实数,有起始的节点
private void checkRep() {
add
顶点不为空时,添加一个顶点进入点表中
@Override public boolean add(L vertex) {
set
输入source,target,weight,分别为边的起点、终点和权值。若权值为负,返回-1。若权值为正且新边已经存在,则除去原边并添加新边。若权值为正且新边不存在,则直接添加新边。若权值为0且新边已经存在,则出去原边。只要改变了原边权值,都返回原边权值,没有权值则返回0
@Override public int set(L source, L target, int weight)
remove
除去某个点及与它相邻的所有边。只需要遍历edges,寻找是否有边的起点或者终点为该点,直接删去即可,使用迭代器实现。
@Override public boolean remove
vertices
返回所有的点集
@Override public Set<L> vertices()
sources
输入终点,返回与它相连的所有边与其构成的Map
@Override public Map<L, Integer> sources
targets
输入起点,返回与它相连的所有边与其构成的Map
@Override public Map<L, Integer> targets
toString
将整个图中所有点的指向转化为一条字符串并将其输出
@Override
public String toString(){
- .测试策略:继承Graph的测试策略,在其基础上增加toString的测试
- 测试结果:
测试策略:增加toString测试即可
2.1.3.2Implement ConcreteVerticesGraph
首先要实现Vertice类
实现函数如下
Vertex
初始化构造方法,用点的名字创建
public Vertex(L name)
checkRep
检查表示不变性,每个边的权值应该大于0
private void checkRep()
getName
返回点的名字Name
public L getName()
getSource
返回能到达该点的所有点和边构成的Map
public Map<L,Integer>getSources()
getTarget
返回某个点能到达的所有点和边构成的Map
public Map<L,Integer>getTargets()
setSource
在源点Map中加入某源点,若weight不为0,则将其加入source中(若源点已存在,则更新其weight并返回原weight,不存在则直接构建新点并返回0)。若weight为0,则移除源点(不存在返回0,存在返回原weight)
public int setSource(L source,int weight)
setTarget
在终点Map中加入某终点,若weight不为0,则将其加入target中(若终点已存在,则更新其weight并返回原weight,不存在则直接构建新点并返回0)。若weight为0,则移除终点(不存在返回0,存在返回原weight)
public int setTarget(L target,int weight)
toString
得到一个点的字符串表示
public String toString()
{
return "" + "name:" + this.name + '\n' + "sources:" + this.sourceMap + '\n' + "targets:" + this.targetMap + '\n';
}
测试类如下:
@Test
public void toStringTest() {
@Test
public void GetNameTest()
@Test
public void GetSourceTest()
@Test
public void GetTargetTest()
@Test
public void AddSourceTest()
@Test
public void AddTargetTest()
@Test
public void SetSourceTest()
@Test
public void SetTargetTest()
接下来需实现ConcreteVerticeGraph类
实现该类需要以下方法
checkRep
检查表示不变性,vertices中不能有重复点
private void checkRep()
add
顶点不为空时,添加一个顶点进入点表中
@Override public boolean add(L vertex)
set
输入source,target,weight,分别为边的起点、终点和权值。若权值为负,返回-1。若权值为正且新边已经存在,则除去原边并添加新边。若权值为正且新边不存在,则直接添加新边。若权值为0且新边已经存在,则出去原边。只要改变了原边权值,都返回原边权值,没有权值则返回0
@Override public int set(L source, L target, int weight)
remove
除去某个点及与它相邻的所有边。只需要遍历vertices,寻找是否有与待删除点相同的名字的点直接删去即可,如果名字不相同,则在该点的源点表和终点表中寻找删去即可,使用迭代器实现。
@Override public boolean remove(L vertex)
vertices
返回所有的点集
@Override public Set<L> vertices()
sources
输入一个终点,返回与它相连的所有边和起点构成的Map
@Override public Map<L, Integer> sources(L target)
targets
输入一个起点,返回与它相连的所有边和终点构成为的Map
@Override public Map<L, Integer> targets(L source)
toString
将整个图中所有点的指向转化为一条字符串输出
@Override public String toString()
测试策略:
与edge相同增加tostring测试类
测试结果如下:
2.1.4Problem 3: Implement generic Graph<L>
2.1.4.1Make the implementations generic
使用泛型实现,在修改过程中依照IDEA的报错修改即可
2.1.4.2Implement Graph.empty()
调用ConcreteEdgesGraph具体实现即可
2.1.5Problem 4: Poetic walks
任务要求我们实现一个类,利用之前实现的图结构,能够将语料库转化为该种图结构,并且在图中搜索,完成对输入的诗句的句子进行扩充,从而输出扩充后的新句子。
2.1.5.1Test GraphPoet
测试结果:
2.1.5.2Implement GraphPoet
函数如下:
public GraphPoet(File corpus) throws IOException {
private void checkRep()
public String poem(String input) {
public String toString()
- GraphPoet:输入文件的路径,一行一行读入,储存在List中,然后每次取相邻的元素,在图中添加新的边
- checkRep:检查不变性,必须保存从语料库文件生成的图
- Poem:输入需要进行扩充的字符串,声明一个StringBuilder保存,每次读取一个词,当前词作为source,下一个词作为target,然后在garph中寻找source的终点表中是否有与target的源点表中相同的元素,并且找到权值最大的和的点加入source和target之间.返回扩充后的字符串
- toString:调用ConcreteEdgesGraph中的toString方法,将整个图中所有点的指向转化为一条字符串输出
5.3Graph poetry slam
增加tostring()
Tostring输出结果如下:
2.1.6使用Eclemma检查测试的代码覆盖度
2.1.7Before you’re done
使用IDEA进行上传
在这里给出你的项目的目录结构树状示意图。
2.2Re-implement the Social Network in Lab1
这次实验要求我们基于Poetic Walks中定义的Graph<L>及其两种实现(本人使用的是ConcreteVerticesGraph<L>),实现Lab1中Social NetWorek中的各种功能,并且尽可能复用ConcreteVerticesGraph<L>中已经实现的方法,然后运行提供的main()和执行Lab1中的Junit测试用例,使之正常运行。
2.2.1FriendshipGraph类
(1).FriendshipGraph的字段为Person构成的ConcreteEdgesGraph,定义私有类型的表如下图所示:
(2).在FriendshipGraph需要实现的方法如下图所示:
1.main:与Lab1一致;
2.addVertex:向图中添加点,调用Graph类中的add即可;
3.addEdge:向图中添加边,因为是无向图,调用两次Graph类中的set即可;
4.getDistance:得到两点间的距离,利用DFS算法,通过调用Graph类中的sources等方法即可;
5.getGraph:因为构造方法graph设置为为private,为方便测试,故设置该方法以返回graph
结果:
2.2.2Person类
(1).FriendshipGraph的字段为Person构成的ConcreteVerticesGraph,定义私有类型的表如下图所示:
(2)在FriendshipGraph需要实现的方法如下图所示:
Person加入姓名
addFriend添加Friend信息
getName和getFriend输出一个返回值
2.2.3客户端main()
使用lab1中的客户端
测试结果如下:
2.2.4测试用例
测试结果如下:
2.2.5提交至Git仓库
使用idea直接PUSH和COMMIT
在这里给出你的项目的目录结构树状示意图。
4.实验进度记录
请使用表格方式记录你的进度情况,以超过半小时的连续编程时间为一行。
日期 | 时间段 | 计划任务 | 实际完成情况 |
3.20 | 18:00-19:00 | 完成边图集 | 延期一小时后完成 |
3.24 | 19:00-22:00 | 完成P1中点图集相关内容 | 按时完成 |
3.27 | 17:00-20:00 | 研究诗歌集 | 完成 |
3.29 | 18:00-22:00 | 编写测试类 | 诗歌类测试没有完成 |
4.4 | 19:00-21:00 | 编写P2相关代码 | 按时完成 |
4.6 | 18:30-20:00 | 编写出P2TEST | 按时完成 |
4.9 | 14:00-17:00 | 检验程序并写报告 | 按时完成 |
5.实验过程中遇到的困难与解决途径
遇到的难点 | 解决途径 |
Test测试时出现错误 | 检查相关报错根据错误更改源代码和test测试代码 |
测试代码覆盖率时出现报错 | 更改JDK的版本后可正常使用 |
编写poet测试用例时总是达不到预期标准 | 查询关于poet的相关方法,了解到该程序工作相应原理,根据原理编写出合适的测试用例 |
FriendshipGraph引用图的时候出现问题 | 查询相关代码将people作为source和target后引用成功 |
6实验过程中收获的经验、教训、感想
6.1实验过程中收获的经验和教训(必答)
收获:学习到了更多关于JAVA编程语言的相关的知识,可以更好的使用JAV来解决部分问题,可以更熟练的使用测试,以及提交代码。对仓库更加熟悉。
教训:测试类十分重要对于监测代码有着重要作用,在编写测试类时要更加用心
6.2针对以下方面的感受(必答)
1.面向ADT的编程和直接面向应用场景编程,你体会到二者有何差异?
面向ADT编程要求对编写的程序整体有充分的认识,要考虑接口、抽象类、 实例类的整体设计,要考虑到哪些部分能够复用、哪些部分需要具体实现; 而面向应用场景编程则是就事论事,编程思路简单,但是工作量大,复用性 低。
2.使用泛型和不使用泛型的编程,对你来说有何差异?
使用泛型可以使编写好的程序适用于不同的数据类型,如果需要只要在相应 位置添加相应的类型即可,而不需要重复开发,很方便,也能提高软件开发 的效率。不使用泛型,在功能相同而类型不同的情况下,将会导致重复开发, 函数版本冗杂。
3.在给出ADT的规约后就开始编写测试用例,优势是什么?你是否能够适应这种测试方式?
优势是更加专注于ADT的规范细节和边界情况,从而确保我们的实现是正 确的、完整的、可靠的。这样做可以减少频繁试错时间使得完成时间大大缩 短。
不是十分适应,经常不太习惯。
4.P1设计的ADT在多个应用场景下使用,这种复用带来什么好处?
提高代码利用率,节约更多时间,减少消耗。
5.为ADT撰写specification, invariants, RI, AF,时刻注意ADT是否有rep exposure,这些工作的意义是什么?你是否愿意在以后编程中坚持这么做?
这些工作为了使得编程工作更加准确无误,能够使得本身程序更加完整,操 作减少不必要的麻烦,节约了许多实验时间,使得本身更快完成。
尽可能在以后实验中按照上述要求操作,使得实验更加规范。
6.关于本实验的工作量、难度、deadline。
工作量较大,难度适中,deadline足够,可以在规定时间完成。
7.《软件构造》课程进展到目前,你对该课程有何收获和建议?
获得了许多关于Java的编程知识,并且能够更好地对所写代码进行测试,能够更好的实现相应的程序要求,使得代码更完整。