软件构造实验2
2019年春季学期
计算机学院《软件构造》课程
Lab 2实验报告
目录
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 实验环境配置
在已有的java开发环境下在Eclipse IDE中安装配置EclEmma(一个用于统计JUnit测试用例的代码覆盖度的plugin)。安装过程较为简单未遇到明显困难
遇到困难:eclipse缺少文件无法正常启动,java虚拟机损坏
解决方案:重装eclipse和jdk
3 实验过程
请仔细对照实验手册,针对三个问题中的每一项任务,在下面各节中记录你的实验过程、阐述你的设计思路和问题求解思路,可辅之以示意图或关键源代码加以说明(但千万不要把你的源代码全部粘贴过来!)。
3.1 Poetic Walks
在这里简要概述你对该任务的理解。
设计,实现和测试抽象数据类型graph,通过两种不同方法实现graph,需要满足对不可变数据类型图实现增删改等操作,并保证数据安全不泄露。
在已有的抽象数据类型graph的基础上,实现Graph Poet,一个使用单词亲和图生成诗歌的类。通过单词在参考文本中的亲和程度生成图,并以此为依据在给出的语句中插入单词。
3.1.1 Get the code and prepare Git repository
首先修改目录到eclipse-workplace,使用cd-clone命令从给出的地址下拷贝代码,获得源代码。
在指定文件夹用git init命令初始化仓库并连接到Lab2,根据流程建立仓库。
3.1.2 Problem 1: Test Graph
以下各部分,请按照MIT页面上相应部分的要求,逐项列出你的设计和实现思路/过程/结果。
GraphInstanceTest 是一个抽象类,不能实例化,并且要通过emptyInstance()方法来获得空图。因为这是一个抽象方法,将图的两种实现重叠的部分写入,写入对生成空图的测试。具体测试在实例中。
通过对graph中的每个操作的功能依此建立相应的测试,测试各个功能是否正确实现并排除各种特殊情况。
3.1.3 Problem 2: Implement Graph
以下各部分,请按照MIT页面上相应部分的要求,逐项列出你的设计和实现思路/过程/结果。
分两种方式,实现string类型的带权有向图。
3.1.3.1 Implement ConcreteEdgesGraph
满足需求的edge需要包含起点,终点和边的权重,并且为保证不出现泄漏,全部用private权限,同时提供get方法和set方法,提供以起点、终点和权值为参数的构造器。设置顶点集合和边数组存储图的信息。
1,public boolean add(L vertex)
直接向顶点集合中添加L类型的vertex即可
2,public int set(L source, L target, int weight)
以起点、终点权值为参数设置一条边。首先判断起点终点是否在顶点集合中,如果不在则添加到点集中。之后查询这个边是否已存在,若已存在,则只需修改权重值即可,不存在则生成新的边并添加到边表中。
3,public boolean remove(L vertex)
查询这个点是否在点集中,如果不在则不操作,在就从点集中删除这个点。然后遍历边表,在边表中查询所有和这个点有关的边,将边删除。
4,public Set vertices()
返回点集,使用Collections.unmodifiableSet 方法返回不可变类型的顶点集合。
5,public Map<L, Integer> sources(L target)
返回到一个顶点的所有边的起点与权值的map。遍历边表,找到所有终点为target的边的起点和权重放入map中,返回这个map。
6,public Map<L, Integer> targets(L source)
大体思路同上
7,public String toString()
判断图是否为空,为空则返回字符串提示空图,否则遍历边表,调用Edge中的tostring方法并将得到的每个字符串连接起来。返回这个字符串。
3.1.3.2 Implement ConcreteVerticesGraph
满足要求的vertex需要包含该点的名称和所有从它除法到达的所有点的和对应的权重(用map类型存储)。为保证不出现泄漏,全部用private权限,同时提供get方法和set方法,提供以顶点名称为参数的构造器。设置顶点动态数组存储顶点。
1,public boolean add(L vertex)
遍历顶点数组,查看是添加顶点已经顶点数组中,若不在生成新的顶点,直接向顶点集合中添加即可,返回true,否则返回false。
2,public int set(L source, L target, int weight)
遍历顶点数组,查看起点顶点是否存在,若存在则查看该点的到达点中是否包含target顶点,若包含,则记录并返回原来的权重,并通过vertex包装的put方法设置边的权重,若不包含,则返回0,设置边的权重;如果顶点数组中不包含source顶点,则创建新的vertex,添加目标顶点然后加入顶点数组中。
3,public boolean remove(L vertex)
遍历顶点数组,查询这个点是否在顶点集中,如果不在则不操作,在就从点集中删除这个点;同时查看每个顶点的目标顶点map中是否有这个点,如果有则删除这条map。
4,public Set vertices()
遍历顶点数组,把所有在顶点数组中出现的顶点名称和在其目标顶点中出现过的顶点名成加入set中,将这个set返回。
5,public Map<L, Integer> sources(L target)
遍历顶点数组,在每个顶点的目标顶点map中查找target顶点,如果存在,则将这个顶点加入map中,返回map。
6,public Map<L, Integer> targets(L source)
在顶点数组中查找source顶点,找到后将其目标顶点map返回即可。
7,public String toString()
判断图是否为空,为空则返回字符串提示空图,否则遍历边表,调用vertex中的tostring方法并将得到的每个字符串连接起来。返回这个字符串。
3.1.4 Problem 3: Implement generic Graph
3.1.4.1 Make the implementations generic
简单的来说将所有个的string换成L。
将之前的Edge 或 List换成Edge 和 List<Edge>.
修改构造器,例如将构造器new ConcreteEdgesGraph() 或 new Edge()换成new ConcreteEdgesGraph() 或 new Edge()。
3.1.4.2 Implement Graph.empty()
申请一个vertices类型的空图。
3.1.5 Problem 4: Poetic walks
3.1.5.1 Test GraphPoet
运用已经给出的的参考文本,输入语句需要有首字母大小写,语料库含有输入语句没有的单词,将其倒入生成单词亲和图,然后根据这个图,输入语句,判断能否返回正确的语句正确的结果,如果可以,则通过测试。
测试结果如下:
覆盖率如下:
3.1.5.2 Implement GraphPoet
首先读取参考文本生成一个图,按照给出的实例,如果一个单词跟随另一个单词则生成一条从前向后指向的权重为1 的有向边,若果再次遇到相同的组合则边的权重加1。这样,根据单词的亲和关系,生成了一个有向图。
然后利用广度优先搜索额想法,开始对出入文本进行处理。从第一个单词开始,将这个单词作为在图中搜索的起点,记录两次广度优先搜索的信息,第二步广度优先搜索得到的所有顶点中如果有第二个单词,则比较所有从第一个单词经过一个单词指向第二个单词的边的权重和,找最大的权重和,则这个中间节点就是我们要添加到第一第二个单词之间的桥,如果没有这样的桥,则不添加。为了优化处理时间,我们使用链表的形式存储待处理文本。处理结束后将待处理文本链表转化为字符串形式输出即可。
3.1.5.3 Graph poetry slam
输入时在语料库加入一些语句
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
基于在Poetic Walks中定义的Graph及其两种实现,重新实现Lab1中3.3节的FriendshipGraph类。要求尽可能复用在Graph中所写的方法实现addvertex和addedge而不是从零开始重新写这两个方法。写完的代码依然要通过lab1中friendshipgraph的测试。
3.2.1 FriendshipGraph类
1,public boolean addVertex(Person person)
首先判断是否重名,若不重名则直接调用Graph中的add方法添加即可,若重名则直接返回,输出提示信息该人已存在。
2,public void addEdge(Person person1, Person person2)
调用Graph中的set方法set两个的权值,权值设为1,其中set的实现确保了即使重复添加也不会出现错误。
3,public int getDistance(Person p1, Person p2)
使用了基于广度优先搜索的算法,通过队列实现。先将dis设为0,每一轮广度优先搜索+1;遍历当前节点的target集合,如果该集合中有我们要找的目标人物p2,那么当前的dis就是最短距离;若果没有,则将该target集合中未被访问过的person入队,并标记为visited已经访问过,进行下一轮广度优先搜索。当队列为空的时候说明我们已经对这个图中的所有连通顶点进行了遍历,如果没有找到路径则不存在链接这两个Person的路径,返回-1。
3.2.2 Person类
给出你的设计和实现思路/过程/结果。
在这个实验要求下person类属性只需包含姓名即可,防止内存泄漏使用private权限。构造参数为string name的构造器,因name是private的,所以构造getName()方法返回姓名。
3.2.3 客户端main()
*按照实验说明给出的要求,首先定义一个新的FriendshipGraph,添加人物,然后添加人物关系。调用getDistance查看人物关系距离。
3.2.4 测试用例
首先测试addvertex和addedge,添加多个人物,多条人物关系,并且存在重复添加情况,来测试代码。然后添加特定已知的人物关系,调用getdistance函数来计算距离。
测试结果如下:
覆盖率如下:
3.2.5 提交至Git仓库
如何通过Git提交当前版本到GitHub上你的Lab3仓库。
在这里给出你的项目的目录结构树状示意图。
3.3 Playing Chess
3.3.1 ADT设计/实现方案
1,Position类
位置信息
属性:int横坐标x,纵坐标y;
构造器以很纵坐标为参数,包含横纵坐标
主要方法:
public boolean equal(Position p2)方法断给定位置是否与参数位置相同
public int getX() 方法进行防御性拷贝,返回该位置x坐标
public int getY()方法进行防御性拷贝,返回该位置y坐标
2,Piece类
棋子类
属性:position p棋子的位置、string namestring 棋子名称、player ownerplayer 所属玩家
构造器参数:名称 位置 所属玩家
主要方法:
public Position getPosition() 进行防御型拷贝,返回棋子位置
public void setPosition(Position pos) 设置棋子位置
3,Player 类
玩家类
属性:String nameString 玩家姓名,private int tag;玩家标号,private Set playerPieces 玩家所拥有的棋子集合,
主要方法:
public Piece getPiece(Position position) 返回给定位置上的棋子
public int pieceCounter() 返回玩家拥有的棋子数
……
4,Board类
棋盘类
属性:int nx,ny 棋盘大小,String gametypeString 游戏种类,int[][] occupied=new int[nx][ny] 棋盘被占用情况。
主要方法:
public void init():根据游戏种类初始化棋盘,围棋初始为空,象棋初始在特定位置白方棋子
public boolean setBoard(int x,int y,int statue):设置棋盘某一位置被占用的状态
public int getCondition(Position position):查询棋盘被占用情况
public boolean isLegle(Position position):判断位置是否合法
5,Action类
活动类
属性:无
主要方法:
public static boolean putPiece:在特定位置下子
public static boolean removePiece:提子
public static Player winner:获得胜利玩家
public static boolean movePiece:移动棋子操作
public static boolean eatPiece:吃子操作
6,Game类
属性:
String gameString:游戏类别;
Player player1,player2:玩家一号二号;
构造器:
游戏名称,玩家一号,玩家二号
主要方法:
public void initgame():初始化游戏(主要是针对象棋初始化棋子)
3.3.2 主程序MyChessAndGoGame设计/实现方案
运行截图
设置计数器在一定的操作时加一模2使得两个玩家轮流操作。
按照要求选择完游戏类型输入玩家名称后,开始游戏。
1,放子操作针对围棋,检查输入合法性,将新生成坐标棋子添加到玩家的棋子集合中,并改变棋盘状态即可
2,移动棋子,检查输入合法性,检查现在位置棋子状态和目标位置状态,目标位置为空且现位置为本人棋子,改写棋子状态,棋盘状态,操作完成。
3,提子,检查输入合法性,合法则从检查棋盘状态,从玩家的棋子集合中删除该棋子,改写棋盘状态
4,吃子,检查输入合法性,检查现在位置棋子状态和目标位置状态,目标位置为对方棋子且现位置为本人棋子,改写棋子状态,从对方棋子集合中删除该棋子改写棋盘状态,操作完成
5,查询某位置占用情况,检查输入合法性,根据输入位置直接检查棋盘状态,根据棋盘状态返回值判断该位置被哪个棋手占用
6,获得玩家棋子数,调用玩家中的piececonter方法,直接得到棋子个数,输出
7,跳过,改写计数器,写一个玩家操作
3.3.3 ADT和主程序的测试方案
模拟玩家进行操作,检查个数函数调用的返回值。采用assert断言。
其中chessgame经过实际操作验证为正确,不再进行测试。
,1,public void positionTest():对其中的添加返回等操作数值模拟
,2,public void playerTest():生成一个玩家,在玩家内对getTag(),getname()输入具体值进行测试,对.addPieces,pieceCounter,getPiece,remove返回值真假进行测试
3,public void pieceTest():生成piece,对其中的getposition,setposition模拟数值,对返回真假进行验证
4,public void boardTest():生成board,对其中每一条函数进行测试
5,public void gameTest(),对是否能正常生成初始化进行测试
6,public void actionTest():生成一个完整的游戏,对每一步操作的结果进行真假测试
3.4 Multi-Startup Set (MIT)
请自行设计目录结构。
注意:该任务为选做,不评判,不计分。
4 实验进度记录
请使用表格方式记录你的进度情况,以超过半小时的连续编程时间为一行。
每次结束编程时,请向该表格中增加一行。不要事后胡乱填写。
不要嫌烦,该表格可帮助你汇总你在每个任务上付出的时间和精力,发现自己不擅长的任务,后续有意识的弥补。
日期 时间段 计划任务 实际完成情况
2019.3.18 13:00-14:30 进行lab2实验规划 按时完成
2019.3.19 18:00-20:00 进行P1的分析和理解 基本完成
2019.3.20 17:00-21:00 完成P1 的graph部分 按时完成
2019.3.21 18:00-22:30 进行P1的poet部分书写 基本完成
2018.3.22 14:30-16:00 进P1test的书写 按时完成
2018.3.22 18:00-20:00 进行P1 test以及P1的检查 按时完成
2018.3.25 14:00-18:00 完成P2的src部分 基本完成
2018.3.26 20:00-23:00 完成P2的test部分并进行P2的完全检查 按时完成
2018.3.28 10:00-15:00 进行P3的ADT分析 基本完成
2018.3.29 13:00-16:00 进行P3的全部接口 没有完成
2018.3.30 10:00-17:00 进行P3接口书写工作 按时完成
2018.3.31 13:00-15:00 进行主程序书写 基本完成
2018.4.1 14:00-18:00 完成P3 的test书写 基本完成
2018.4.2 20:00-22:00 进行P3 的完全性检查 按时完成
2018.4.7 16:00 进行lab2的提交工作 完成
5 实验过程中遇到的困难与解决途径
遇到的难点 解决途径
eclipse缺少文件无法正常启动
重装ecpilse,完美解决
eclipse无法正常编译,经查java虚拟机出问题
重装无效,于是卸载360及相关杀毒软件,再次重装完美解决
实验内容超前于课程,对很多知识点不理解
百度搜索,阅读说明文本,阅读hit课程网站基本解决
6 实验过程中收获的经验、教训、感想
6.1 针对以下方面的感受
P1:
首先看到这个题目我觉得很复杂,关于graph的创建以及应用是一个难点。但是通过慢慢分析发现其实情况没有这个复杂。对语料库的处理时很常规的一种能里。
P2:
对于P2问题这种有向图的处理下手还是很快的。这次主要是应用到了泛型的只是来处理问题。也算是得到了一种提升
P3:
首先对于自主设计使用ADT,我面临了巨大的挑战,但是有P1和P2的铺垫。P3的认知要好很多。其次对于棋类游戏的理解很重要。
总之通过本次实验我学会了很多关于面向ADT编程问题,同时对于graph的理解也更深入了。
6.2 针对以下方面的感受
(1) 面向ADT的编程和直接面向应用场景编程,你体会到二者有何差异?
答:面向ADT编程极大提高了代码的服用率并扩大了适用范围,无需重新编程即可将原有代码带入到新的使用场景,减少了不必要的麻烦。同时也解放了大量的生产力。
(2) 使用泛型和不使用泛型的编程,对你来说有何差异?
答:使用泛型可以对任何类型进行相同的操作,扩大了代码适用范围,同时也加强了变成的适用性
(3) 在给出ADT的规约后就开始编写测试用例,优势是什么?你是否能够适应这种测试方式?
答:优势是在一开始便规划出具体的应用需求和功能,可以避免对功能的不清晰理解导致变成过程中偏离最初目标设定,通过一开始进行测试可以减少代码的错误。使的编程过程更加清晰了
(4) P1设计的ADT在多个应用场景下使用,这种复用带来什么好处?
答:提高效率,减少了代码重写,并扩大代码复用率,缩减了工作量。
(5) P3要求你从0开始设计ADT并使用它们完成一个具体应用,你是否已适应从具体应用场景到ADT的“抽象映射”?相比起P1给出了ADT非常明确的rep和方法、ADT之间的逻辑关系,P3要求你自主设计这些内容,你的感受如何?
答:适应些许关于ADT的抽象映射,同时也感受到了。自主使用ADT的强大之处。自主使用ADT需要我们多问题分析比较明了,加深对于ADT的使用很重要。
(6) 为ADT撰写specification, invariants, RI, AF,时刻注意ADT是否有rep exposure,这些工作的意义是什么?你是否愿意在以后编程中坚持这么做?
答:通过使用rep exposure可以减少运行过程中的错误,增加了数据的安全性,增强了代码的健壮性。非常愿意继续使用。
(7) 关于本实验的工作量、难度、deadline。
答:工作量正常,难度较大。
(8) 《软件构造》课程进展到目前,你对该课程有何体会和建议?
答:希望老师能够授课进度和实验进度相匹配,软件构造对于我使用Java,有了新的认识。
推荐阅读