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 并据此设计测试用例。
针对以下所有四个任务,请为每个你设计和实现的 ADT 撰写mutability/immutability 说明、AF、RI、safety from rep exposure。给出各 ADT 中每个方法的spec。为每个 ADT 编写测试用例,并写明 testing strategy。
2 实验环境配置
操作系统:macOS Mojave 10.14.4
硬件环境:CPU:Intel Core i7 @3.1GHz
RAM:16GB LPDDR3
开发、测试、运行环境:IntelliJ IDEA Ultimate 2019.1
GitHub Lab_2 URL:unavailable
3 实验过程
3.1 Poetic Walks
总体上,主要是要完成一个关于有向图的ADT并在此基础上完成相关的应用并加以测试。通过之前完成的ADT可以通过输入的源素材建立一个语料库,通过这个语料库,对输入的原文本进行扫描、查找和替换,在此基础上进行润色加工处理。
3.1.1 Get the code and prepare Git repository
从提供的网站上下载源代码并按照要求格式创建Project。在指定文件夹用git init命令初始化仓库并连接到Lab2,根据流程建立仓库。
3.1.2 Problem 1: Test Graph
根据每个方法(操作)的功能依此建立相应的测试,以考察到各个功能是否正确实现并排除各种特殊情况。
经过测试,成功实现了80%以上的覆盖。具体的细节测试受时间限制没有深入测试,但其主要功能没有问题。
3.1.3 Problem 2: Implement Graph
事实上根据要求,首先需要实现一个ConcreteEdgesGraph
和ConcreteVerticesGraph的String
版本,然后再实现一个泛型版本。而两者是非常相似的,后期只需稍加修改便可实现泛型。
3.1.3.1 Implement ConcreteEdgesGraph
需要建立一个用边描述并可对其进行直接操作的图。其中包括了各种方法还有一个Edge
类。首先描述``Edge类。它是图中的一条边,包含了source
点,target
点和权值3个信息,在此基础上定义了设置权值等方法。
对于add
操作,直接在边的点集中添加点即可。
对于set
操作,用一个辅助方法indexOfEdge
,它可以找到边中符合条件的边,并返回列表中的位置,如果没有满足条件的点则返回一个负数,表明没有满足条件的点。首先判断权重,如果大于0,那么考虑indexOfEdg
e,如果返回负数,就直接创造两个点,并建立一条边,否则直接在相应边上修改权值,并返回之前的权值。如果权值为0,那么就移除该边,返回之前权值。
对于remove
操作,可以使用函数接口Predicate
提取出满足条件的点和边,分别删除:对于点来说,只需删除该点即可;对于边,则挑选出所有原点或目标点包含该点的边,将其删除。最后使用断言确认是否删删除成功。
对于vertices
,直接返回点集即可。
对于source
,可以直接寻找满足条件的target
点是target
的点。
对于target
,可以直接寻找满足条件的source
点是source
的点。
对于toString
,其返回方式比较随意。如果为空就返回一个提示字符串,否则返回一个原点到目标点的Label
。
对于Edge
中的构造则无需赘述,并且其equals
和hashCode
并没有用到。
3.1.3.2 Implement ConcreteVerticesGraph
需要建立一个用点描述并可对其进行直接操作的图。其中包括了各种方法还有一个Vertex类。首先描述Vertex类。它是图中的一个点,包含了sourc
e边Map
,target
边Map
和Label
共3个信息,在此基础上定义了设置权值等方法。
对于add
操作,若判断已存在于列表中则返回false
,否则建立新的实例加入到List
中即可。
对于set
操作,用一个辅助方法indexOfVertices
,它可以找到点中符合条件的点,并返回列表中的位置,如果没有满足条件的点则返回一个负数,表明没有满足条件的点。判断source
和target
点,若不存在则直接创建,存在则进行下一步操作。利用Vertex
类中的辅助方法,可以直接对目标点的属性进行操作,并且并返回之前该边的权值。如果权值为0,那么就移除该点及对应边,返回之前权值。
对于remove
操作,如果不存在所要删除点则返回false
。否则再列表中遍历点,以此操作完成移除。
对于vertices
,直接返回所有点的Label
即可。
对于source
,如果不存在该点则返回空,否则直接返回该点的source
的Map
即可。
对于target
,如果不存在该点则返回空,否则直接返回该点的target
的Map
即可。
对于toString
,其返回方式比较随意。使用stream
和filter
完成操作,格式与之前类似。
对于Vertex
中的构造则无需赘述,但是ConcreteVerticesGraph
中难度较高的便是Vertex
中的方法,上面的各种操作底层实现源于Vertex
中的基本操作。下面介绍Vertex
中的基本操作。
addSource
:该方法可以在source
的Map
中添加一个(source, weight);
addTarget
:同理;
setSource
:若权值为0,则移除source
点,否则用addSource
添加source
点和权值,若source
已存在并且权值不同,则用新的source
和权值进行替换。最终返回之前的权值。
setTarget
:同上;
remove
:直接移除source
中的该点和target
中的该点。
toString
:返回一个描述该点source
和target
点的字符串。
3.1.3.3 Problem 3: Implement generic Graph<L>
Graph
接口,其实现上面已经完成。
3.1.3.4 Make the implementations generic
非常简单,只需将String
替换为泛型的参数L
。方法同上。
3.1.3.5 Implement Graph.empty()
只需利用ConcreteEdgesGraph
建立一个新的graph
实例即可。
3.1.4 Problem 4: Poetic walks
进入应用阶段。也印证了标题的作用,通过之前完成的ADT可以通过输入的源素材建立一个语料库,通过这个语料库,对输入的原文本进行扫描、查找和替换,在此基础上进行润色加工处理。
3.1.4.1 Test GraphPoet
测试了单个单词,一行句子和多行句子的情况。
只需根据语料库替换的条件,通过输入相应条件的等待句子,使其行为相符即可。
3.1.4.2 Graph poetry slam
使用列表corpusWords
储存输入的原素材单词,并用Graph结构储存语料库。
提取单词的过程中将单词均转换为小写形式便于判断。
将建立的词库转换为图:使用generateAffinityGraph
方法,循环提取紧接着的单词,用set进行输入即可,权值为先前的权值+1;
最后是生成新诗的阶段:先将输入的句子分割为单个单词,遍历句子,提取每个单词的target
和下一个的单词的source
,使用filter在两者中取交集并转换为List
即得到了bridge。如果bridge非空,则在bridge中用随机数随意选取一个单词插入到原来的语句相应位置。最后返回到String
类型。
3.1.5 Before you’re done
在这里给出你的项目的目录结构树状示意图。
项目目录(非上交版本)
建立并初始化本地仓库后,使用git add -A命令选择目标文件,并git commit -m alpha
上传,最后git push
到GitHub。
通过测试,覆盖率基本达到了预期标准。
3.2 Re-implement the Social Network in Lab1
使用之前建立的Graph ADT重构之前Lab的P3部分,并尽可能利用原有代码。
对于getDistance
由于储存结构的改变使用了全新算法,其他基本不变,由于使用了已有ADT,实现更加简洁。
3.2.1 FriendshipGraph类
主体结构与Lab1完全一致,只不过Lab1使用了邻接矩阵作为基本结构。
使用了nameList
储存name
,用以区分是否同名,若同名则退出。
主要介绍三个基本操作。
对于addVertex
,首先判断是否满足条件(重名),若满足则直接在graph
和nameList
直接添加即可。
对于addEdge
,直接add
两个人名和权值,权值设为1。
对于getDistance
,使用了类似于广度优先搜索的算法。先将所有点的distance
设为0,每次遍历当前点的target
集合,利用流和过滤器,该集合只包含已经visited过的Person
,并标记为当前点距离+1,设为已访问,并放入队列。每次出队点继续按照上述规则遍历。这样每个Person
都包含了距离源点的距离,利用当前点距离计算下一个点的距离,直到找到目标点,如果所有点都被遍历而未找到则返回-1。
3.2.2 Person
类
与Lab1的基本一致。
为了计算距离,新增加了visited
和distance
,用于储存当前点是否被访问过和到源点距离。
3.2.3 客户端main()
与Lab1的完全一致。
3.2.4 测试用例
与Lab1的完全一致。
由于主函数无需测试,已经达到了100%覆盖。
3.2.5 提交至Git仓库
项目目录(非上交版本)
建立并初始化本地仓库后,使用git add -A
命令选择目标文件,并git commit -m alpha
上传,最后git push
到GitHub。
3.3 Playing Chess
3.3.1 ADT设计/实现方案
设计了一个Action
接口。所用类包括Board
、Game
、MyChessAndGoGame
、Piece
、Player
、Position
。
其中Board
包括构造器和各种重要方法,描述了棋盘尺寸、名称及种类。主要方法有:显示棋盘(状态)、生成棋子(Chess
和Go
),检查棋子是否可以移动(判断各种条件)。
Game
类,描述了游戏名称、种类。
Piece
类,继承了Position
类。主要描述棋子性质,包括所属玩家名称,是否在棋盘上,棋子名称及代码(便于操作和识别)。
Player
类,继承了Action
,描述了玩家名称,并实现了Action
的具体方法,包括选择棋子和移动棋子。
Position
,记录棋子当前位置。
3.3.2 主程序MyChessAndGoGame
设计/实现方案
辅之以执行过程的截图,介绍主程序的设计和实现方案,特别是如何将用户在命令行输入的指令映射到各ADT的具体方法的执行。
主要为驱动程序。首先创建玩家,输入姓名,明根据输入0或1来选择游戏类别(国际象棋或围棋),接下来设置棋盘属性,无需输入并进入游戏环节。
对于国际象棋,需要输入棋子种类和代码,并输入指定位置,可以自动判断是否可以移动并且吃子,输出反馈信息和当前棋盘状态。
对于围棋,只需要输入坐标即可,可以自动判断是否可以移动,输出反馈信息和当前状态。
由于是命令行式设计,没有GUI,比较简陋,操作不方便,但是功能是相通的。围棋由于不清楚规则,没有判断胜负的功能。
3.3.3 ADT和主程序的测试方案
介绍针对各ADT的各方法的测试方案和testing strategy。
介绍你如何对该应用进行测试用例的设计,以及具体的测试过程。
首先建立两个Game
类分别为Chess
和Go
,建立两个玩家Billy和Van,分别建立棋子对象。
测试国际象棋是否存在可用棋子。
测试国际象棋指定棋子是否可以移动。
测试国际象棋游戏结束条件判定(一方棋子被吃光)。
测试围棋是否存在可用棋子。
测试围棋指定棋子是否可以移动。
主要功能都已测试,未测试到的实际上是暂时用不到的构造方法和主函数,没有任何影响。