目录
1 实验目标概述 1
2 实验环境配置 1
3 实验过程 1
3.1 Poetic Walks 1
3.1.1 Get the code and prepare Git repository 1
3.1.2 Problem 1: Test Graph 1
3.1.3 Problem 2: Implement Graph 1
3.1.3.1 Implement ConcreteEdgesGraph 2
3.1.3.2 Implement ConcreteVerticesGraph 2
3.1.4 Problem 3: Implement generic Graph 2
3.1.4.1 Make the implementations generic 2
3.1.4.2 Implement Graph.empty() 2
3.1.5 Problem 4: Poetic walks 2
3.1.5.1 Test GraphPoet 2
3.1.5.2 Implement GraphPoet 2
3.1.5.3 Graph poetry slam 2
3.1.6 Before you’re done 2
3.2 Re-implement the Social Network in Lab1 2
3.2.1 FriendshipGraph类 2
3.2.2 Person类 3
3.2.3 客户端main() 3
3.2.4 测试用例 3
3.2.5 提交至Git仓库 3
3.3 Playing Chess 3
3.3.1 ADT设计/实现方案 3
3.3.2 主程序MyChessAndGoGame设计/实现方案 3
3.3.3 ADT和主程序的测试方案 3
4 实验进度记录 4
5 实验过程中遇到的困难与解决途径 4
6 实验过程中收获的经验、教训、感想 4
6.1 实验过程中收获的经验和教训 4
6.2 针对以下方面的感受 4
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实验环境配置
在老师指导下从eclipse marketplace下载了eclemma
3实验过程
请仔细对照实验手册,针对三个问题中的每一项任务,在下面各节中记录你的实验过程、阐述你的设计思路和问题求解思路,可辅之以示意图或关键源代码加以说明(但千万不要把你的源代码全部粘贴过来!)。
3.1Poetic Walks
分别新建两个类ConcreteEdgesGraph,ConcreteVerticesGraph 实现Graph接口。
Graph接口要求实现add(添加新节点),set(添加新边),remove(移除节点),vertices(获得所有的节点集合),sources(target)获得以target为目标节点的边的起始节点,targes(source)获得以source为起始节点的边的目标节点。
Poet:给定一组单词(文件输入),对于两个相邻的单词a和b,认为存在一条由a到b的有向边,通过Graph接口构造有向图。再给定一由单词组成的句子,如果句子中两个相邻单词之间在Graph图中有一个中间单词则将中间单词插入到两单词之间(如果有多个则插入权重最大的那个)。
3.1.1Get the code and prepare Git repository
访问实验指导手册中提供的网址,在githubDesktop上clone到本地
3.1.2Problem 1: Test Graph
选用ConcreteEdgesGraph作为Graph的具体实现
把Graph的empty方法改为
运行GraphStaticTest得到
3.1.3Problem 2: Implement Graph
该部分要求重写Graph里的方法,分别以点为基础的图和以边为基础的图。
3.1.3.1Implement ConcreteEdgesGraph
(1)class Edge
①fields:
②构造函数
③检查数据合法性private void checkRep()
④各field的get函数
⑤public String toString()
(2)public class ConcreteEdgesGraph implements Graph
①fields:
②构造函数public ConcreteEdgesGraph()
③检查数据合法性private void checkRep()
④public boolean add(L vertex)
如果在vertices的Set集合中成功添加了顶点string,则返回true
⑤public int set(L source, L target, int weight)
遍历edges,找到source和target都与传入参数相同的边,设置weight并返回原来的权值
⑥public boolean remove(L vertex)
如果不含该点,返回false。否则遍历edges,如果某个edge的source或是target与vertex相等,则删除该边。最后删除vertex点。并checkRep。
⑦ public Set vertices()
⑧public Map<L, Integer> sources(L target)
建立一个map,遍历edges,如果某个edge的edge.getTarget()和传入参数target相等,则将该边的source和weight存入map中。
⑨public Map<L, Integer> targets(L source)
建立一个map,遍历edges,如果某个edge的edge.getSource()和传入参数source相等,则将该边的target和weight存入map中。
⑩public String toString()
遍历edges,调用edge.toString()
(3)ConcreteEdgesGraphTest
3.1.3.2Implement ConcreteVerticesGraph
(1)class Vertex
①fields:
②构造函数
③检查数据合法性 private void checkRep()
④各fields的get函数
⑤public int setSource(L source, int weight)
遍历sources,找到source,更新weight并返回原权值
⑥public int setTarget(L target, int weight)
遍历targets,找到target,更新weight并返回原权值
⑦public String toString()
(2)public class ConcreteVerticesGraph implements Graph
①fields:
private final List<Vertex> vertices = new ArrayList<>();
②构造函数public ConcreteVerticesGraph()
③检查数据合法性 private void checkRep()
④public boolean add(L vertex)
若vertices中已包含vertex,返回false,否则新建一个顶点将其加入vertices即可
⑤public int set(L source, L target, int weight)
Vertex vertexOfsource = new Vertex(source);
Vertex vertexOftarget = new Vertex(target);
遍历vertexOfsource.targets(),找到target,更新weight并返回原权值
调用vertexOfsource.setTarget和vertexOftarget.setSource
⑥public boolean remove(L vertex)
如果vertices不包含vertex,返回false。否则遍历所有点,如果某点和vertex存在映射关系,则将这种关系删除。最后将vertex对应的点从vertices中删除即可
⑦public Set vertices()
遍历vertices,找到每个点对应的string,添加进set即可
⑧public Map<L, Integer> sources(L target)
返回target的源点图
⑨public Map<L, Integer> targets(L source)
返回source的目的点图
⑩public String toString()
遍历vertices,调用Vertex.toString()
(3)ConcreteVerticesGraphTest
3.1.4Problem 3: Implement generic Graph
3.1.4.1Make the implementations generic
把所有String改成L,在ConcreteEdgesGraph,ConcreteVertices,Edge,Vertex,Graph后加
3.1.4.2Implement Graph.empty()
3.1.5Problem 4: Poetic walks
给定一个语料库corpus,根据corpus中的文本生成一个单词图,然后给定一条语句输入,在图中搜索词之间的关系,自动补全语句中可能可以完善的部分。
图的构建规则是,在corpus中,对每一个不一样的单词看作一个顶点,相邻的单词之间,建立一条有向边,相邻单词对出现的次数,作为这条有向边的权值。在输入信息补全时,对相邻单词A和B做检查,如果存在一个单词C,在图中可以由前一个单词A通过这个单词C到达单词B,那么就在A和B之间补全C,补全的优先级按照权值越大者优先。
3.1.5.1Test GraphPoet
在基于预设的测试用例基础上,增加等价类划分的多种情况。
等价类划分:两个单词之间不存在连接词,两个单词之间只有一个连接词,两个单词之间有多个连接词。
此外还要注意句末的句号,测试当一个句子最后一个词是“桥”的一端。
3.1.5.2Implement GraphPoet
①fields:
②构造函数public GraphPoet(File corpus) throws IOException
用文件输入单词,String.split()分割为数组,通过String.toLowerCase()小写化。接下来构建图,相邻的单词加边。首先要在加边前通过Graph.add()加点,加边时要判断是否存在:由于Graph.set()能返回之前加的边的值,以此来判断是否存在,存在则在之前的值加一(之前的边的值保存为lastEdgeWeight)。
③public String poem(String input)
当相邻两个单词任意一个不在之前创建的图里,则将后者单词加入即可(再加个空格)当存在时,由于Bridge长度只能为2,所以:分别求两个单词的sources和targets,将该Map转换为Set求交集;若交集为空,则无桥,若交集不空,则在交集中找最短的桥(可以在Map的value中查询weight)。
3.1.5.3Graph poetry slam
测试文件 This is a test of the Mugar Omni Theater sound system.
3.1.6Before you’re done
请按照http://web.mit.edu/6.031/www/sp17/psets/ps2/#before_youre_done的说明,检查你的程序。
如何通过Git提交当前版本
git add Lab2_1180300324
git commit -m “第一次提交”
git push origin master到GitHub上你的Lab2仓库。
在这里给出你的项目的目录结构树状示意图。
项目名称: Lab2_1180300324
src
P1
graph
….java
poet
… .java
… .txt
test
P1
graph
…Test.java
poet
… Test.java
… .txt
3.2Re-implement the Social Network in Lab1
这部分任务就是用我们在3.1中写的ADT,把第一次实验中的FriendshipGraph重新实现一遍,图中的节点仍然是Person类型,所以泛型L一律为Person. 而对于已经写好的FriendshipGraph中的方法,要用3.1中的Graph ADT中的方法来实现它们。
3.2.1FriendshipGraph类
Graph < Person > graph:
直接调用Graph的静态方法.empty()生成一个空的图。
boolean addVertex():
直接调用graph.add()添加点。
int addEdge():
调用graph.set()两次,添加双向边,默认权值为1,并记录可能存在的旧边的权值。
int getDistance():
首先判断起止点是否相等。再新建Map<Person, Integer> dis表示从起始点开始到该Person的距离,以及Map<Person, Boolean> vis表示该Person是否访问过。将两个Map初始化后,把起点标记为已经访问(所有涉及这两个Map的操作均需要remove后再put,后文不再阐述)。然后开始BFS搜索,找到终点为止。
Person类
3.2.2客户端main()
3.2.3测试用例
3.2.4提交至Git仓库
如何通过Git提交当前版本到GitHub上你的Lab3仓库。
git add Lab2_1180300324
git commit -m “第二次提交”
git push origin master
在这里给出你的项目的目录结构树状示意图。
项目名称: Lab2_1180300324
src
P2
FriendshipGraph.java
Person.java …
test
P2
FriendshipGraphTest.java
3.3Playing Chess
3.3.1ADT设计/实现方案
设计了哪些ADT(接口、类),各自的rep和实现,各自的mutability/ immutability说明、AF、RI、safety from rep exposure。
必要时请使用UML class diagram(请自学)描述你设计的各ADT间的关系。
(1)Piece类
①fields:
②构造函数public Piece(String name,int state, int x ,int y)
③各fields的get和set函数
④ public void remove()
把pieceState,pieceX,pieceY都设为-1
(2)Position类
①fields:
②构造函数
③各fields的get和set函数
(3)Player类
①fields:
②构造函数
③各fields的get和set函数
④public Piece getPiece(String pieceName)
根据传入的pieceName,从player的剩余棋子remaining中返回一个未放置的棋子。如果没有,返回null。
⑤public void addPieces(Piece piece)
如果该棋手remaining有这个棋子,则返回false;否则在remaining中添加该piece并且返回true
⑥public void addHistory(String oneHistory)
传入的参数是每个操作后需要添加的玩家历史,判断其为非空。再调用String.concat函数将传入的历史连接在this.history后面即可。
⑦public boolean judgeOwnPiece(Piece piece)
参数是棋子piece,判断remaining中是否有该棋子
⑧ public int countQuantityOfPieceInBoard()
计算player在棋盘上的棋子总数,初始化num = 0,遍历remaining,调用piece.getPieceState()方法,如果棋子状态为1,num++,最后返回num。
(4)Board类
①fields:
②boardsize的get和set函数
③public boolean check(int x ,int y)
检查坐标是否越界
④public Piece getBoardPiece(int x , int y) throws Exception
获得指定坐标的棋子。如果参数坐标超出棋盘范围,抛出异常。
⑤public void setBoardPosition(Piece piece, int x , int y) throws Exception
将棋子放置在指定的位置,如果参数x、y超出棋盘范围,或者该棋盘的此位置有棋子,则抛出异常。
⑥public void setBoardPositionState(int x , int y ,int newState) throws Exception
改变指定位置棋子的pieceState。如果参数x、y超出棋盘范围,抛出异常。
(5)Action类
①fields:
②各fields的get和set函数
③public void putPiece(Player player , Piece piece , Position position) throws Exception
将棋子放置在指定的位置。先调用Board.getBoardPosition获得该position处的棋子,设置棋子的坐标,设置pieceState为1,将棋盘的position处棋子设置为piece。添加玩家历史。
④public void movePiece(Player player , Position oldPosition , Position newPosition) throws Exception
将棋子移动到指定的位置。先获取oldPosition处的棋子,判断棋子归属正确,存在性等,new一个Piece对象piece,复制原棋子参数,将原位置棋子的pieceState设置为0,将newPosition处棋子设置为piece。添加玩家remaining,添加玩家操作历史。
⑤public void removePiece(Player player , Position position) throws Exception
先判断移除的是对方的且在棋盘上的棋子。将棋盘上对应的棋子pieceState设置为-1,调用Piece.remove,增加玩家历史。
⑥public void eatPiece(Player player , Position position1 , Position position2) throws Exception
通过position1和position2获得要吃子piece1和被吃子piece2。判断棋子归属正确,存在性,将棋盘上position1处棋子pieceState设置为0,position2处pieceState设置为-1,并且在position2处新添加一个piece1的对象。移除piece2,添加newPiece到玩家的remaining中,添加玩家历史。
(6)Game类
①fields:
②各fields的get和set函数
③putPiece,movePiece,removePiece,eatPiece
调用gameAction的函数即可,并更新gameBoard
④public Piece getOccupationOfPosition(Position position) throws Exception
在指定的position处获得一个棋子。调用Board.getBoardPiece()即可
⑤public void initByChess(String name1, String name2) throws Exception
把boardSize初始化为8,初始化国际象棋的32个棋子并放置在棋盘上,分别将其加入两个player的remaining中
初始化棋盘如下:
⑥public void initByGo(String name1, String name2) throws Exception
把boardSize初始化为19,初始化181个black棋子,180个white棋子,分别加入到两个player的remaining中
初始化棋盘如下:
⑦ public void printBoard() throws Exception
打印棋盘,行倒序,列正序
3.3.2主程序MyChessAndGoGame设计/实现方案
辅之以执行过程的截图,介绍主程序的设计和实现方案,特别是如何将用户在命令行输入的指令映射到各ADT的具体方法的执行。
(1)fields:
备注:设计玩家链表是为了后面while循环能通过索引值的变化,实现两个玩家的操作的切换。turn = (turn +1) % 2
为了调用非静态方法,在main函数中new了MyChessAndGoGame对象,并且调用自己创建的myMain()方法。其中主体部分在myMain之中。
(2)打印菜单(展示了实现的功能):
(3)主函数如下:
(4)public void myMain() throws Exception
①BufferedReader提供通用的缓冲文本读取,readLine()读取一个文本行。InputStreamReader将字节流转化字符流。
.②让用户输入棋类名称chess or go,来确定调用Game类中哪种初始化方法,再读取用户输入的两个棋手的名称,游戏即可开始。
③放置棋子(围棋)
读取line,调用String.split()将字符串分开。white映射到棋子名称,数字1和1映射到棋盘上的位置,player则通过对players索引获得。最后调用Game.putPiece()即可。其中异常情况的处理在Game类中已介绍。
④提子(围棋)
读取line,调用String.split()将字符串分开。数字3和4映射到棋盘上的位置,player通过对players索引获得。最后调用Game.removePiece()即可。其中异常情况的处理在Game类中已介绍。
⑤移动棋子(国际象棋)
读取line,调用String.split()将字符串分开。数字1和1映射需要移动的棋子点坐标,4和5映射目的地坐标,player通过对players索引获得。最后调用Game.movePiece()即可。其中异常情况的处理在Game类中已介绍。
⑥吃子(国际象棋)
读取line,调用String.split()将字符串分开。数字6和7映射需要主动吃子的棋子点坐标,2和1映射被吃棋子坐标,player通过对players索引获得。最后调用Game.eatPiece()即可。其中异常情况的处理在Game类中已介绍。
⑦查询某个位置的占用情况
读取line,调用String.split()将字符串分开。数字2和1映射需要待查位置坐标,player通过对players索引获得,该棋子通过Game.getOccupationOfPosition()方法得到,棋手名players.get(i).getPlayerName(),棋子名piece.getPieceName()。其中异常情况的处理在Game类中已介绍。
⑧查看棋盘
调用Game.printBoard()方法打印棋盘。
⑨计算两个玩家分别在棋盘上的棋子总数
调用Player.getPlayerName()和Player.countQuantityOfPieceInBoard()即可
⑩结束查看玩家历史
调用Player.getPlayerName()和Player.getHistory()来打印对应玩家的操作历史。其中每次历史中包括玩家名、棋子名和棋子移动的坐标。
3.3.3ADT和主程序的测试方案
介绍针对各ADT的各方法的测试方案和testing strategy。
介绍你如何对该应用进行测试用例的设计,以及具体的测试过程。