2021年春季学期
计算学部《软件构造》课程
Lab 2实验报告
姓名 | 刘昕 |
学号 | 1190201116 |
班号 | 1903006 |
电子邮件 | |
手机号码 | 16725341728 |
3.1.1 Get the code and prepare Git repository· 2
3.1.2 Problem 1: Test Graph <String>· 2
3.1.3 Problem 2: Implement Graph <String>· 3
3.1.3.1 Implement ConcreteEdgesGraph· 3
3.1.3.2 Implement ConcreteVerticesGraph· 3
3.1.4 Problem 3: Implement generic Graph<L>· 4
3.1.4.1 Make the implementations generic· 4
3.1.4.2 Implement Graph.empty()· 4
3.1.5 Problem 4: Poetic walks· 4
3.1.5.2 Implement GraphPoet· 4
3.1.6 使用Eclemma检查测试的代码覆盖度··· 5
本次实验训练抽象类型(ADT)的设计、规约、测试,并使用面向对象编程(OOP)技术实现ADT。具体来说:
针对给定的应用问题,从问题描述中识别所需的ADT;
设计ADT的规约并评估规约的质量
根据ADT的规约设计测试用例
ADT的泛型化
根据规约设计ADT的多种不同的实现;针对每种实现,设计其表示、表示不变性、抽象过程
使用OOP实现ADT,并判定表示不变性是否违反、各实现是否存在表示泄露
测试ADT的实现并评估测试的覆盖度
使用ADT及其实现,为应用问题开发程序
在测试代码中,能狗写出testing strategy 并据此设计测试用例
打开help->Eclipse Marketplace 找到EclEmma然后点击安装即可。
在这里给出你的GitHub Lab2仓库的URL地址(Lab2-学号)。
https://github.com/ComputerScienceHIT/HIT-Lab2-1190201116.git
请仔细对照实验手册,针对三个问题中的每一项任务,在下面各节中记录你的实验过程、阐述你的设计思路和问题求解思路,可辅之以示意图或关键源代码加以说明(但千万不要把你的源代码全部粘贴过来!)。
设计抽象数据类型(ADT)Graph()的两个实现类,使用这个设计好的ADT完成Poetic Walks任务,并给出对应的测试类判断实现的正确性。
在github上下载代码并解压,文件夹命名为Lab2-1190201116
在文件夹中打开git bash 输入git init命令完成仓库创建
使用git remote add lab2 https://github.com/ComputerScienceHIT/HIT-Lab2-1190201116.git命令用lab2代替仓库网址。
-
-
- Problem 1: Test Graph <String>
-
·保留GraphStaticTest.java中的内容
·在GraphInstanceTest.java中写出测试策略并实现测试方法
在实例测试类中创建一个空对象,对5个需要重写的方法分别给出测试方法。
testAdd方法向图中增加3个不同的顶点1,2,3,预期返回true,然后向图中增加上述3个顶点中的1,预期返回false
testSet方法向图中增加三条不同的边,预期返回结果为0,然后向图中增加三条边中的一条,将权重进行修改,预期返回为刚才设置的权重
testRemove方法向图中增加一个顶点,然后删除这个刚增加的顶点,预期返回结果为true,然后删除一个不存在的顶点,预期返回结果为false
testVertices方法创建一个新的图对象,然后返回这个对象的顶点集,预期返回顶点集中元素个数为0,然后向其中添加一个顶点,再次返回这个对象的顶点集,预期返回顶点集中元素个数为1.
testSources方法向图中增加一个顶点a,这个新增加的顶点不和任何一个其他顶点相连,然后返回这个顶点的来源顶点和权重组成的图,预期图中不存放任何内容。接着再增加一个顶点b并且增加一条由b指向a的边,再次返回来源图,预期图中存放1条数据。
testTarget方法向图中增加一个顶点a,这个新增加的顶点不和任何一个其他顶点相连,然后返回这个顶点的目标顶点和权重组成的图,预期图中不存放任何内容。接着再增加一个顶点b并且增加一条由a指向b的边,再次返回目标图,预期图中存放1条数据。
-
-
- Problem 2: Implement Graph <String>
-
以下各部分,请按照MIT页面上相应部分的要求,逐项列出你的设计和实现思路/过程/结果。
-
-
-
- Implement ConcreteEdgesGraph
-
-
首先实现Edge()类,Edge类型要求是不可变类型的。一个边有一个起点,一个终点,并且和这条边的权重,这些变量都是不可变类型的,并且使用private和final修饰,确保Edge类是不可变的。
在ConcreteEdgesGraphTest.java中写对Edge的测试类,testGet()方法测试Edge的三个返回变量的方法,testEquals()方法测试equals方法,testToStingForEdge()测试toString方法。
Edge类中设计实现多个方法,getStart()\getEnd()\getWeight()三个方法返回边的三个变量。不可变类型要求构造函数即需确定对象的三个变量,equals方法对两条边的三个变量分别比较,如果三个变量完全一样则返回true,否则返回false. toString方法返回一个包含起点、终点、权重的字符串。
在ConcreteEdgesGraphTest.java中设计对toString()方法的测试策略,new一个图对象,在图中加入两个顶点和一条边,然后与预期输出字符串进行比对。
在ConcreteEdgesGraph.java中重写Graph中的方法,并重写toString()方法。
-
-
-
- Implement ConcreteVerticesGraph
-
-
首先实现Vertex,Vertex要求是可变类型,field里面有一个不可变类型变量name,存储顶点的名称,两个HashMap分别存储这个顶点作为source和target,这两个量作为可变类型的,需要进行暴露保护。
checkRep()函数对顶点的来源边和终点边进行检查。
对这个顶点可以get顶点的名称和它的各个连接边,并且可以检查相对应的权重。顶点需要能够增加边的方法,并且分别为增加作为边起点的方法addEdgeAsSource()和作为边终点的方法addEdgeAsTarget()。equals()方法用于判断两个顶点是否相等,按照顶点的名称判断两个顶点是否相等。removeEdges()方法将当前顶点中所有带需要删除的顶点的边全部删除。toString()方法按照顶点名称和顶点的邻接边的形式输出顶点。
在测试类中加入对Vertex类的测试和对toString方法的测试,基本上与Edge类的测试方法相同。
ConcreteVerticesGraph class的field中只有一个顶点的列表,列表本身属于可变类型,因此必须要进行防御型复制来避免用户对其进行修改。接下来重写Graph中的方法和toString()方法。
将两个实现类中的所有String改为L,并将Vertex和Edge中和其测试类中的String改为泛型L
-
-
-
- Implement Graph.empty()
-
-
使Graph.empty()返回ConcreteEdgesGraph<>();
-
-
- Problem 4: Poetic walks
- Test GraphPoet
- Problem 4: Poetic walks
-
通过读文件将自己准备好的词库读入程序,语料库:you can do anything you want if you really want it.
输入字符串为you do anything you want.
期望输出字符串为you can do anything you really want.
-
-
-
- Implement GraphPoet
-
-
类中实现两个方法,一个是构造方法,要求在方法中读入文件并对文件进行处理,将信息放置在图中,完成整个对象的初始工作。
将相邻接的单词用权重为1的边连接起来,如果这条边已经存在,那么在原来权重基础上加1,并且判断之后的单词有没有在这个图中,如果没有需要先添加这个顶点到图中再修改边的情况。
第二个方法是生成诗歌的poem()方法,存入参数为输入的初始的字符串,首先将完整的句子进行拆分分解成一个个单词,然后在图中查找这两个单词,如果没有,则进行下一对的判断,如果有则调用图的target()和source()方法,获得前一个单词的target()和后一个单词的source(),如果两个词中间可以插入其他单词,则这两个HashMap中一定有相同的单词。再选出权重最高的单词即可。
不更改main()函数中的代码,运行即可。
-
-
- 使用Eclemma检查测试的代码覆盖度
-
请按照http://web.mit.edu/6.031/www/sp17/psets/ps2/#before_youre_done的说明,检查你的程序。
如何通过Git提交当前版本到GitHub上你的Lab2仓库。
在这里给出你的项目的目录结构树状示意图。
Lab2-1190201116
src
P1
graph
ConcreteEdgesGraph.java
ConcreteVerticesGraph.java
Graph.java
Poet
GraphPoet.java
Main.java
Mugar-omni-theater.txt
test
P1
graph
ConcreteEdgesGraphTest.java
ConcreteVerticesGraphTest.java
GraphInstanceTest.java
GraphStaticTest.java
poet
GraphPoetTest.java
seven-words.txt
利用P1中实现的ADT重新实现FriendshipGraph类,要求功能完全一样,所以之前的测试函数和main函数仍然可以使用。
FriendshipGraph类继承自ConcreteEdgesGraph类,因此添加Person对象和增加边可以直接用父类中的add方法和set方法。在获得两个人间的最短距离时,依然采用广度优先搜索算法,首先将来源对象压入队列中,然后每弹出一个对象,返回他的终点边,然后判断终点边中是否有终点的Person对象,如果有返回距离,如果没有则将这个顶点压入队列中,并记录这个顶点,重复的顶点不会被压入队列中。如果最后队列被清空了还是没有这个顶点,则返回-1;如果source和target是两个相等的对象,则返回0.
Person类中只有一个name的字符串型变量,故Person是不可变类型。构造函数要求在一开始给定name的值,getName()方法返回对象的name,equals()方法通过判断两个对象的name是否相等来判断两个对象是否相等,如果相等返回true,否则返回false
-
-
- 客户端main()
-
Main函数仍然使用Lab1中给定的main函数
public static void main(String[] args) {
FriendshipGraph graph=new FriendshipGraph();
Person rachel=new Person("Rachel");
Person ross=new Person("Ross");
Person ben=new Person("Ben");
Person kramer=new Person("Kramer");
graph.addVertex(rachel);
graph.addVertex(ross);
graph.addVertex(ben);
graph.addVertex(kramer);
graph.addEdge(rachel,ross);
graph.addEdge(ross,rachel);
graph.addEdge(ross,ben);
graph.addEdge(ben,ross);
System.out.println(graph.getDistance(rachel, ross));
//should print 1
System.out.println(graph.getDistance(rachel, ben));
//should print 2
System.out.println(graph.getDistance(rachel, rachel));
//should print 0
System.out.println(graph.getDistance(rachel, kramer));
//should print -1
}
-
-
- 测试用例
-
测试用例仍然使用Lab1中的测试函数。
-
-
- 提交至Git仓库
-
如何通过Git提交当前版本到GitHub上你的Lab3仓库。
git add 命令加入暂存区,然后git commit 命令提交到本地库
通过git push命令上传至github远程库。
在这里给出你的项目的目录结构树状示意图。
Lab2-1190201116
src
P2
FriendshipGraph.java
Person.java
test
P2
FriendshipGraphTest.java
请使用表格方式记录你的进度情况,以超过半小时的连续编程时间为一行。
每次结束编程时,请向该表格中增加一行。不要事后胡乱填写。
不要嫌烦,该表格可帮助你汇总你在每个任务上付出的时间和精力,发现自己不擅长的任务,后续有意识的弥补。
日期 | 时间段 | 计划任务 | 实际完成情况 |
6.1 | 16:00-18:30 | ADT problem1 problem2.1 | 按时完成 |
6.1 | 20:00-21:30 | ADT problem2.2 | 按时完成 |
6.2 | 16:00-17:00 | ADT problem3.1 | 按时完成 |
6.2 | 20:10-21:30 | ADT problem3.2 | 提前30分钟完成 |
6.3 | 15:00-18:00 | ADT problem4 | 按时完成 |
6.7 | 14:00-16:00 | Problem2 | 提前30分钟完成 |
遇到的难点 | 解决途径 |
不懂得如何检查不变性 | 通过查阅网上相关资料掌握如何写checkRep()方法并且在什么情况下使用 |
对于可变类型的数据不知道如何避免暴露 | 通过查阅资料和看ppt,可变类型数据可以返回一个复制的版本,而不可变类型数据可以直接返回。 |
映射关系处理不妥当 | 通过画图等方式理清映射关系并形成文字。 |
对类和接口的理解更加深刻,理解了继承的含义。
对泛型有了初步的认识
对OOP和ADT编程更加熟练,同时对课堂内容的理解更加深刻。
- 面向ADT的编程和直接面向应用场景编程,你体会到二者有何差异?
面向ADT编程可以直观的对于ADT的各种功能进行编程,不需要先考虑应用场景,等到实现了ADT后再在具体的场景使用它即可。并且代码的可移植性更高。
面向应用场景编程使用场景单一,但是可以更加贴向具体问题进行编程,有时候会简化代码,但是代码的可移植性差。
- 使用泛型和不使用泛型的编程,对你来说有何差异?
使用泛型可以有更多的使用形式,而不需要单纯限制一种数据类型。而不使用泛型,应用场景仍然较为单一。
- 在给出ADT的规约后就开始编写测试用例,优势是什么?你是否能够适应这种测试方式?
给出规约后,相当于类的各种方法和功能都已经固定了,此时写测试程序正好针对写的规约进行测试,在写完程序后运行测试程序,可以检查刚才实现的方法有没有完全按照规约进行完成。
- P1设计的ADT在多个应用场景下使用,这种复用带来什么好处?
简化代码量,同时使编程更高效
- P3要求你从0开始设计ADT并使用它们完成一个具体应用,你是否已适应从具体应用场景到ADT的“抽象映射”?相比起P1给出了ADT非常明确的rep和方法、ADT之间的逻辑关系,P3要求你自主设计这些内容,你的感受如何?
- 为ADT撰写specification, invariants, RI, AF,时刻注意ADT是否有rep exposure,这些工作的意义是什么?你是否愿意在以后编程中坚持这么做?
防止rep暴露是很重要的事情,如果一个对象的数据被暴露出来,并且可以被用户随意修改,那么程序就会出现很多错误并且难以发现。在今后的编程中,依然需要严格判断类中的每个数据是可变类型还是不可变类型,做好防止暴露的操作。
- 关于本实验的工作量、难度、deadline。
工作量适中,难度较低,能够在比较短的时间内完成。
- 《软件构造》课程进展到目前,你对该课程有何体会和建议?
该课程介绍了ADT\OOP等编程思想和编程方法,从一些细节角度讲了工程上如何做到安全性,教导如何使用面向对象的思想编程。希望这门课能多开设一些习题课,并且增加一些学时,讲述面向对象部分的时候能够更细致一些。