第一部分 实验目标概述
本次实验训练抽象数据类型(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 并据此设计测试用例。
第二部分 实验环境配置
编译Java代码的IDE我选择使用的是IntelliJ IDEA2022.1
将项目目录按照实验指导书的指定格式复制到IEAD的project目录中,然后同样在Project structure中进行源文件测试文件的设置。
第三部分 实验过程、
Poetic Walks
这个实验的目的是让我们练习设计、测试和实现抽象数据类型ADT。具体的要求是需要我们创建并维护一个图结构,并使用这个图结构来进行诗歌的创作和派生。在实现这个GraphPoet的过程中,需要我们对变量方法的可见性进行设计,以及需要我们进行测试优先的编程。
Get the code and prepare Git repository
点击前往指导书中给定地址获得这次试验任务的代码,并且按照指导书的项目目录结构要求创建Java项目,完成文件夹的设置。
在实验项目的文件夹中大开Git Bash,并输入命令git init
来新建一个本地仓库。接下来进行代码编写时,通过git add
指令跟踪任务文件,需要提交时,使用git commit -m ‘init’
指令为版本命名并提交至本地仓库。
运行git remote
指令连接GitHub上的远程仓库,然后在Bash中输入git push origin master
(默认只有一个master分支)指令将本地仓库的内容提交至GitHub远程仓库。
Test Graph
首先进行测是优先的编程,在GraphinstanceTest类中实现各个函数的测试类的编写。在这一部分我们使用String为Graph类泛型指定的类型,在后面的实验操作中进行泛型的更改。
在针对Graph类的每一个方法,进行等价类的划分,每种等价类挑选一个至两个测试用例进行测试。
Implement Graph
在这一部分先让我们将数据类型定为Srting进行代码的编写。而且在此要求我们使用两种数据结构来实现这个图的构建,在后面的任务中也会将String的限制取消掉,而转化成泛型进行改写。
Implement ConcreteEdgesGraph
设计思路:
根据给出的方法的规约来填写函数体实现部分。在此之前,我预先在这个实现中设计了一个Edges类来实现类中的构造方法,包含获取起始点和终点以及设置变得权值等等,如下:
方法名称 | 方法功能 |
---|---|
Public boolean twopoints(Edge e1) | 比较两条边的起止点是否相等 |
Public L getsource() | 获得边的起始点 |
Public L gettarget() | 获得边的终止点 |
Public void setweight(int weight) | 设置边的权重 |
Public void getweight() | 获得边的权重 |
实现方法:
根据接口类型中的函数规约进行方法的的具体实现。
add()方法:
检测即将添加的顶点标签是否已经存在,如果不存在则直接添加新的顶点标签,返回值为真;如果存在,则不进行添加,返回值为假。
set(L source, L target, int weight)方法:
首先检测输入的权值,如果为零的话则进入删除判断:如果边存在则删除,返回该边之前的权值,如果不存在则返回0;如果不为零则进入设置或更新权值判断,如果该边存在,则更新边的权值返回旧值,如果不存在则设置该边的权值并返回0。
remove(L vertex)方法:
如果该顶点标签存在,则删除所有由它发射或由他接收的所有边,并且返回真;如果不存在顶点标签,则返回假。
vertices()方法:
获得这个图中所有的顶点的标签并返回顶点标签的一个set。
sources(L target)方法:
输入的target为目标顶点的标签,返回所有以该顶点为终点的边的标签以及权值。
targets(L source)方法:
输入的source为目标顶点的标签,返回所有以该顶点为终点的边的标签以及权值。
进行方法的测试:
使用IDEA自带的coverage插件来进行代码测试覆盖度的检测。使用测试优先编程时设计的等价类和测试用例对所编的代码进行覆盖度测试。可以看到我的测试用例覆盖了全部的代码段,并且测试通过,说明测试方法以及等价类的选取比较正确并且代码实现也很OK。
Implement ConcreteVerticesGraph
设计思路:
根据给出的方法的规约来填写函数体实现部分。在此之前,我预先在这个实现中设计了一个Vertex类来实现类中的构造方法,包含获取起始点和终点以及设置变得权值等等
add()方法:
检测即将添加的顶点标签是否已经存在,如果不存在则直接添加新的顶点标签,返回值为真;如果存在,则不进行添加,返回值为假。
set(L source, L target, int weight)方法:
图的实现的核心方法。首先检测输入的权值,如果为零的话则进入删除判断:如果边存在则删除,返回该边之前的权值,如果不存在则返回0;如果不为零则进入设置或更新权值判断,如果该边存在,则更新边的权值返回旧值,如果不存在则设置该边的权值并返回0。
remove(L vertex)方法:
如果该顶点标签存在,则删除所有由它发射或由他接收的所有边,并且返回真;如果不存在顶点标签,则返回假。
vertices()方法:
获得这个图中所有的顶点的标签并返回顶点标签的一个set。
sources(L target)方法:
输入的target为目标顶点的标签,返回所有以该顶点为终点的边的标签以及权值。
targets(L source)方法:
输入的source为目标顶点的标签,返回所有以该顶点为终点的边的标签以及权值。
进行方法的测试:
同上一部分一样,使用coverage进行覆盖度的查看和代码测试。不出意外的,代码都通过了而且测试覆盖度高达100%。
Implement generic Graph
这一部分的任务要求我们将之前设计实现的Graph类进行类型的泛化,意思是说从原有的String的变量类型改为泛型类型L,以实现对不同参数类型的Graph使用以及方法的合法调用。
Make the implementations generic
设计思路:
按照实验指导书上的要求,在这一节我们需要实现Graph的泛型化。思路就是将代码中所有的包装类String有关的方法,全部改为L泛型。
实现方法:
以下面的代码片为示例。参数类型在传递时,由String改为L,传递参数;相应的变量声明时也需要添加表示,例如Iterator<Vertex>。
Implement Graph.empty()
设计思路:
返回任意一种之前实现的Graph的实现方案。
实现过程:
Graph接口中留下了一个静态方法empty(),这个方法通过返回一个任意的Graph实现来达到新建一个空图的目的。
Poetic walks
这一部分任务要求我们使用之前实现的Graph类,来完成一个Poetic Walks的功能,这个功能的结果是将给定的一段训练文本,通过一种叫做最大桥接边的方法,来对一段输入的诗歌的扩写。
Test GraphPoet
设计思路:
按照方法的规约进行测试的编写。本测试相对简单,因为没有很多的可以划分的等价类,只需要我们设计一个训练文本和输入文本,然后根据训练文本和设计文本的内容来推断我们的诗歌扩写之后应该是什么样子,之后再输出。
实现方法:
设计训练文本和输入文本,通过assertEauqls()方法来判断程序输出的诗歌扩写和我们预想的是否相同。
Implement GraphPoet
设计思路:
设计两个方法:Graph初始化和诗歌的具体实现。
实现方案:
GraphPoet()构造方法:
首先进行文件读写来实现对训练文本的读取,随后按照要求对进行训练,进行Graph的初始化。
初始化的方法为:针对读入的训练文本,两个词如果挨着则将两个词加入Graph中,并且边的权值设置为1,在之后如果再次遇到这两个词挨在一起,则将权值加1。
Graph poetry slam
在实现的基础上另找了一个英文美句,通过句子设计训练文本来完成main()函数,具体实现如下:
Before you’re done
Re-implement the Social Network in Lab1
这一部分实验要求我们完成一个社交关系记录图的任务,需要我们建立两个类。一个类是社交任务类(Person),另一个是社交关系图类(FriendshipGraph)类。然后通过在主方法中输入社交关系来构建这个关系图,并计算社交距离。
FriendshipGraph类
addVertex(Person p)方法调用Graph类中的方法add(),如果重复就不添加,如果没重复就新添加进去一个点。
addEdge(Person p1, Person p2)方法首先判断p1和p2这两个人是否存在,若否,则输出不存在这两个人。接着判断是否已经建立p1和p2的关系,若是,则输入重复输入。若上述两种情况都不符合,则加入二人的关系。
getDistance(Person p1, Person p2)方法使用广度搜索的方法。建立一个队列,将p1直接建立社交关系的人物入队,之后让队首人物出队,并将他的直接联系人入队。不断迭代这个过程,记录广搜的层数,即为两人的社交距离。若没有找到p2,则返回值-1表示两人没有社交关系。
main()方法在主方法中,新建社交人物类和新的社交关系图,并且输入人名和关系,即可调用方法建立图和计算社交距离。
Person类
类的成员变量Person类中设置一个变量。String name用来存储这个社交任务类的名字。
构造方法存储这个人的名字。
客户端main()
main()方法中实现可以通过调用Person类和FriendshipGraph类中的构造方法来实现这个社交关系图需要实现的功能。例如,新建一个人名成员,添加单项或者双向的社交关系,再或者向社交关系图中加入新的社交人物并且输出人与人之间的社交距离。经过测试,输出结果与期望值相同。
测试用例
给通过代码中的等价类划分编写测试用例,并使用IDEA自带的coverage来查看测试代码的覆盖度,覆盖度高达百分之90。
提交至Git仓库