2020春软件构造lab2实验报告
目录
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 主程序ChessGame设计/实现方案 3
3.3.3 ADT和主程序的测试方案 3
3.4 Multi-Startup Set (MIT) 4
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 实验环境配置
延续Lab1中的实验环境,除此之外,本次实验在 Eclipse IDE中安装配置 EclEmma(一个用于统计JUnit测试用例的代码覆盖度测试用例的代码覆盖度plugin)
在这里给出你的GitHub Lab2仓库的URL地址(Lab2-学号)。
https://github.com/ComputerScienceHIT/Lab2-1180300503.git
3 实验过程
请仔细对照实验手册,针对三个问题中的每一项任务,在下面各节中记录你的实验过程、阐述你的设计思路和问题求解思路,可辅之以示意图或关键源代码加以说明(但千万不要把你的源代码全部粘贴过来!)。
3.1 Poetic Walks
在这里简要概述你对该任务的理解。
这个实验的主要目的是练习ADT的规约设计和ADT的不同实现。
3.1.1 Get the code and prepare Git repository
如何从GitHub获取该任务的代码、在本地创建git仓库、使用git管理本地开发。
初始代码: https://github.com/rainywang/Spring2019_HITCS_SC_Lab2/tree/master/P1
⚫ 在作业描述中若遇到“commit and push”的要求,将代码 push 到GitHub Lab2 仓库中。
⚫ MIT 作业页面提及的文件路径,请按照下表的目录结构进行调整。例如 “test/poet”应为“ test/P1/poet”, “src/poet”应为“ src/P1/poet”。
3.1.2 Problem 1: Test Graph
以下各部分,请按照MIT页面上相应部分的要求,逐项列出你的设计和实现思路/过程/结果。
选用ConcreteVerticesGraph作为Graph的具体实现,如需要测试,将Graph里原empty()方法修改为:
运行GraphStaticTest.java文件,得到结果如下:
根据提供的spec:
1.add方法是将一个节点加入图中。
如果原来有这个结点返回true,否则返回false
那么加一个节点看一下是否是返回true,再加一个相同的结点看是返回true还是false即可
2.set方法是加一条边进去。
如果传进去的weight参数等于0就是删掉这条边,否则如果没有这条边就加一条边,返回0;如果有这条边就修改这条边的权值,并返回之前的权值
那么就可以加一个边进去看是否返回0,再加一条同样的边进去但修改权值看是否返回之前规定的权值,最后把这条边加进去但权值修改为0,看还有没有这条边即可。
3.remove方法是移除一个结点并把与这个结点相连的边都移除
那么将一个结点移除并且看与这个结点相关的边是否存在即可
4.source方法是返回以传入结点作为target的边的所有source结点
那么添加许多同一个target结点的边然后把所有source结点放在一个set里面最后调用source方法比较两个set是否相同即可
5.target方法是返回以传入结点作为source的边的所有target结点
那么添加许多同一个source结点的边然后把所有target结点放在一个set里面最后调用target方法比较两个set是否相同即可
6.vertices方法是返回所有结点。
那么添加结点的同时将这些结点添加入一个set中最后调用vertices方法比较这个两个set是否相同即可
3.1.3 Problem 2: Implement Graph
以下各部分,请按照MIT页面上相应部分的要求,逐项列出你的设计和实现思路/过程/结果。
3.1.3.1 Implement ConcreteEdgesGraph
构造函数:public ConcreteEdgesGraph() {
}
Private void checkRep:
public boolean add(String vertex)
思路:如果在vertices的Set集合中成功添加了顶点string,则返回true
private int findEdge(String source, String target)
思路:My method findEdge() ,为了找到list是否存在一条指定的边,该边的source和target必须与传入的参数相等,返回该边的index
public int set(String source, String target, int weight)
思路:根据parameter找到指定边,并调用findEdge()返回index。
当weight>0,如果index<0,没找到指定边,则添加顶点和边;如果index>0,找到了指定边,则update这条边。
当weight>0且index<0时,找到了指定边,将其删除/
public boolean remove(String vertex)
思路:如果不含该点,返回false。否则遍历edges,如果某个edge的source或是target与vertex相等,则删除该边。最后删除vertex点。
public Map<String, Integer> sources(String target)
思路:建立一个map,遍历edges,如果某个edge的edge.getTarget()和传入参数target相等,则将该边的source和weight存入map中。
public Map<String, Integer> targets(String source)
思路:建立一个map,遍历edges,如果某个edge的edge.getSource()和传入参数source相等,则将该边的target和weight存入map中。
public String toString()
思路:graph空,则返回“Empty Graph”.
graph不为空,则将每个边的toString连接起来,调用String.concat()方法。
注意连接后为了可读性,有“\n”.
测试类:
即测试函数是否有合格的输入和输出,截图如下:
3.1.3.2 Implement ConcreteVerticesGraph
构造函数:
public ConcreteVerticesGraph()
{
}
检测函数:
Private void checkRep()
public boolean add(String vertex)
思路:如果在vertices的List集合中成功添加了顶点string,则返回true
public int set(String source, String target, int weight)
思路:首先遍历list点集,查看是否有名为source和名为target的点,如果有,再查看两个点之间是否有边,如果有,则重置边的权值,并且返回边的原权值。如果没有边或者没有顶点,首先新建边或顶点,然后加进集合中,返回0.
public boolean remove(String vertex)
思路:遍历List集合,如果没有该点,返回false,如果有该点,在点的集合中删除该点,并且遍历点集,如果发现有以该点为source或target的边,删除。返回true。
public Map<String, Integer> sources(String target)
思路:return target.getSources即可
public Map<String, Integer> targets(String source)
思路: return source.getTargets即可
public String toString()
思路:graph空,则返回“Empty Graph”.
graph不为空,则将每个边的toString连接起来,调用String.concat()方法。
注意连接后为了可读性,有“\n”.
测试类:
即测试函数是否有合格的输入和输出,截图如下:
3.1.4 Problem 3: Implement generic Graph
3.1.4.1 Make the implementations generic
将两个实例类中的所有String类的参数替换为泛型的参数(声明、函数参数、返回值、rep)
3.1.4.2 Implement Graph.empty()
3.1.5 Problem 4: Poetic walks
3.1.5.1 Test GraphPoet
3.1.5.2 Implement GraphPoet
rep:
private final Graph graph = Graph.empty();
constructor:
Function:
public Graph poemGraph(File corpus)
根据参数(文件)创建语料库,规则为:相邻两个单词直接建立一条权值为1的有向边,有重复边时权值+1。
首先将文件以字符串形式读入,按“ ”(空格)划分为各个单词,不区分大小写,然后线性扫描,调用corpusGraph.set()建边
public String poem(String input)
对input进行扩展,规则是:如果相邻两个单词a和b,在语料库中存在一个单词c使a->c和c->b都存在,则将c插入a和b之间;如果存在多个满足条件的c,取边权a->c较大的。
首先将input按“ ”(空格)划分为各个单词,不区分大小写,线性扫描,调用corpusGraph.target()和source()找出所有可能的“桥”(上面说到的c),然后找到边权最大的一个,插入。最后返回扩展好的字符串。
3.1.5.3 Graph poetry slam
更新语料库文件中的字符串和输入的字符串input
3.1.6 Before you’re done
请按照http://web.mit.edu/6.031/www/sp17/psets/ps2/#before_youre_done的说明,检查你的程序。
如何通过Git提交当前版本到GitHub上你的Lab2仓库。
在这里给出你的项目的目录结构树状示意图。
3.2 Re-implement the Social Network in Lab1
修改lab1中的FriendshipGraph,利用P1中的Graph类里面的函数,进行重新修改函数addVertex,addEdge和getDistance,不能修改父类rep。
3.2.1 FriendshipGraph类
public void addVertex(Person person)
遍历父类的vertices(),如果存在一个元素的name域与Person的name域相等,证明这个点已经存在,输出提示,否则调用父类的add(person)将该点加入。
public void addEdge(Person p1, Person p2)
先调用父类的set(p1,p2,1),如果返回值为0证明这两个点之间不存在边,否则证明这两个点之间已经有边存在,输出提示
public int getDistance(Person p1, Person p2)
利用lab1中的函数思想进行修改,主体基本不变,只是改变某些变量的输入方法。
3.2.2 Person类
3.2.3 客户端main()
客户端直接新建图,在图中加入边,输出查看与预期结果是否一致。
3.2.4 测试用例
测试结果:
3.2.5 提交至Git仓库
如何通过Git提交当前版本到GitHub上你的Lab3仓库。
在这里给出你的项目的目录结构树状示意图。
3.3 Playing Chess
3.3.1 ADT设计/实现方案
设计了哪些ADT(接口、类),各自的rep和实现,各自的mutability/ immutability说明、AF、RI、safety from rep exposure。
必要时请使用UML class diagram(请自学)描述你设计的各ADT间的关系。
3.3.2 主程序MyChessAndGoGame设计/实现方案
-
Position类
-
Piece类
-
Player类
public boolean addPiece(Piece newPiece)
添加一枚棋子,已经拥有返回false,否则添加并返回
True
public boolean removePiece(Piece removePiece)
移除一枚棋子,不存在返回false,否则移除并返回true
public int getNumberOfPieces()
获取棋盘上属于自己的棋子数,即统计自己的棋子里pieceState为1的棋子数
public void addStep(String step)
向historyList域中添加一个走棋步骤
4. Board类
public Piece getPiece(int x,int y)
给出一对横纵坐标,获取该位置上的棋子,如果没有,返回的为空,因此需要在客户端进行判断
public boolean setPiece(Piece p,int x,int y)
在位置(x,y)上放置piece
public void setPlaced(int x,int y)
将(x,y)对应的Placed状态置为true/false
public void setNotPlaced(int x,int y)
public boolean getPlaced(int x,int y)
获取(x,y)对应位置是否放置了棋子
5. Action类(接口:interface)
public boolean placePiece(Player player,Piece piece,int x,int y);
- 给定“棋手、一颗棋子、指定位置的横坐标、指定位置的纵坐标”作为输入参数,将该棋手的该颗棋子放置在棋盘上
*异常情况,例如:该棋子并非属于该棋手、指定的位置超出棋盘的范围、指定位置已有棋子、所指定的棋子已经在棋盘上等,返回false - 否则,将棋子落在指定位置,返回true
public boolean movePiece(Player player,int sx,int sy,int tx,int ty); - 给定“棋手、初始位置和目的位置的横纵坐标”,将处于初始位置的棋子移动到目的位置。
- 需要考虑处理各种异常情况,例如:
- 指定的位置超出棋盘的范围、目的地已有其他棋子、初始位置尚无可移动的棋子、两个位置相同、初始位置的棋子并非该棋手所有等,返回false
- 否则,将处于初始位置的棋子移动到目的位置,返回true。
public boolean removePiece(Player player,int x,int y); - 给定“棋手、一个位置的横纵坐标”,将该位置上的对手棋子移除。
- 需要考虑处理异常情况,例如:该位置超出棋盘的范围、该位置无棋子可提、所提棋子不是对方棋子、等,返回false。
- 否则,移除棋子并返回true。
public boolean eatPiece(Player player,int sx,int sy,int tx,int ty);
- 给定“棋手、两个位置横纵坐标”,将第一个位置上的棋子移动至第二个位置,第二个位置上原有的对手棋子从棋盘上移除。
- 需要处理异常情况,例如:
- 指定的位置超出棋盘的范围、第一个位置上无棋子、第二个位置上无棋子、两个位置相同、第一个位置上的棋子不是自己的棋子、第二个位置上的棋子不是对方棋子、等.
- 返回false。
- 否则,返回true。
- Game类(接口:interface)
Game类参与主要函数的构建,其中的方法与Action类中的基本相同,但是Gamestart参与主要游戏的构成。
ChessGame类
GoGame类
3.3.3 ADT和主程序的测试方案
介绍针对各ADT的各方法的测试方案和testing strategy。
介绍你如何对该应用进行测试用例的设计,以及具体的测试过程。
MyChessAndGoGame类:
主要是游戏玩家选择象棋游戏还是围棋游戏,选择之后进入Game类里面执行。
ADT和主程序的测试方案
主程序主要使用手动测试的方法,针对参数越界,控制权不符合要求,输入参数不足等情况手动测试。
具体操作主要是在board类,所以对board类详细测试:
主要测试putPiece,removePiece,move,eat几个重点操作
3.4 Multi-Startup Set (MIT)
请自行设计目录结构。
注意:该任务为选做,不评判,不计分。
4 实验进度记录
请使用表格方式记录你的进度情况,以超过半小时的连续编程时间为一行。
每次结束编程时,请向该表格中增加一行。不要事后胡乱填写。
不要嫌烦,该表格可帮助你汇总你在每个任务上付出的时间和精力,发现自己不擅长的任务,后续有意识的弥补。
日期 时间段 计划任务 实际完成情况
3.31 13:00-15:00 P1的Edge图的构建 基本完成
4.1 9:00-12:00 P1的vertices图的构建以及测试类 基本完成
4.3 10:00-12:00 P1的poet的实现以及测试类 基本完成
4.5 16:00-18:00 P2的实施 基本完成
4.6 8:00-12:00 P3的Position,Player,Board类的填写 基本完成
4.6 15:00-18:00 Action类,Game类的填写 完成80%
4.7 8:00-12:00 完成main函数以及测试类 基本完成
4.9 8:00-10:00 完成实验报告 基本完成
5 实验过程中遇到的困难与解决途径
遇到的难点 解决途径
P1的英文解释看不懂,不知道题意是什么。 参考以前学长留下来的报告说明,逐步弄明白了实验的基本要求。
P3棋类游戏没有思路,不会写。 寻求同学的帮助,在同学的帮助下理清了构建思路,进而完成了实验
每个实验的test类不知道测试什么。 参考网上的测试类自己重新编写。
6 实验过程中收获的经验、教训、感想
6.1 实验过程中收获的经验和教训
实验是自己完成的与不是自己完成的感觉肯定不一样,自己写完实验后有一种很强的成就感,在实验过程中千万不要盲目写代码,理清实验思路是最重要的。要好好规划好时间,千万不要有拖延症,事情越早完成越好。
6.2 针对以下方面的感受
(1) 面向ADT的编程和直接面向应用场景编程,你体会到二者有何差异?
面向ADT编程难度更高一些,要考虑的问题更多。
(2) 使用泛型和不使用泛型的编程,对你来说有何差异?
使用泛型可以极大简化重复的实验代码,但是使用泛型容易出现意想不到的错误。
(3) 在给出ADT的规约后就开始编写测试用例,优势是什么?你是否能够适应这种测试方式?
优势是可以不用考虑诸多不合格的测试情况,使测试更简单。可以适应。
(4) P1设计的ADT在多个应用场景下使用,这种复用带来什么好处?
简化代码,将重复性操作可以简化。很方便。
(5) P3要求你从0开始设计ADT并使用它们完成一个具体应用,你是否已适应从具体应用场景到ADT的“抽象映射”?相比起P1给出了ADT非常明确的rep和方法、ADT之间的逻辑关系,P3要求你自主设计这些内容,你的感受如何?
感觉自己从0开始做有些困难,需要考虑的东西瞬间增多,比起P1来,P3的实验难度在于自己根本没有实验思路,不知道从何下手。
(6) 为ADT撰写specification, invariants, RI, AF,时刻注意ADT是否有rep exposure,这些工作的意义是什么?你是否愿意在以后编程中坚持这么做?
可以有效避免一些不该发生的输入错误,减少考虑的方面,简化代码。今后在编程中要坚持这个好习惯。
(7) 关于本实验的工作量、难度、deadline。
工作量不小,难度适中,ddl把握的很好。
(8) 《软件构造》课程进展到目前,你对该课程有何体会和建议?
代码真的是自己写才会明白其中的奥妙,希望老师在实验课的时候可以适当讲一些关于实验的内容,有的东西不指点一下真的是做的很困难。
上一篇: Lab2-软件构造实验心得