软件构造Lab2
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。具体来说:
l
针对给定的应用问题,从问题描述中识别所需的ADT;
l
设计ADT规约(pre-condition、post-condition)并评估规约的质量;
l
根据ADT的规约设计测试用例;
l
ADT的泛型化;
l
根据规约设计ADT的多种不同的实现;针对每种实现,设计其表示(representation)、表示不变性(rep invariant)、抽象过程(abstraction function)
l
使用OOP实现ADT,并判定表示不变性是否违反、各实现是否存在表示泄露(rep
exposure);
l
测试ADT的实现并评估测试的覆盖度;
l
使用ADT及其实现,为应用问题开发程序;
l
在测试代码中,能够写出testing strategy并据此设计测试用例。
2 实验环境配置
安装EclEmma:
- 从Eclipse菜单中选择Help → Eclipse Marketplace。
- 搜索“EclEmma”
- 下载
4.若工具栏上有该图标代表安装已完成
实验过程
请仔细对照实验手册,针对三个问题中的每一项任务,在下面各节中记录你的实验过程、阐述你的设计思路和问题求解思路,可辅之以示意图或关键源代码加以说明(但千万不要把你的源代码全部粘贴过来!)。
3.1 Poetic
Walks
该任务的主要目标为:学会抽象类与抽象函数的设计方法以及继承类的设计方法。
需要实例化的类:ConcreteEdgesGraph与ConcreteVerticesGraph;
并测试实例化的类。
3.1.1 Get the code and prepare Git repository
从课程群中下载相关代码文件。
3.1.2 Problem 1: Test Graph
测试思想:测试其返回的是否为一个空的ConcreteEdgesGraph对象
3.1.3 Problem 2: Implement Graph
Graph为一个接口,其主要的工作是为其他实现它的类提供方法定义。当然,其也实现了一个empty方法。Empty方法的主要功能为:返回一个空的初始ConcreteEdgesGraph对象。
以下各部分,请按照MIT页面上相应部分的要求,逐项列出你的设计和实现思路/过程/结果。
3.1.3.1 Implement ConcreteEdgesGraph
思路:设计该图,核心在于设计Edge类的内容,以及图如何通过对各种Edge对象的操作来达到实验要求的功能。其中,图中的每个顶点的类型为String。
Edge类设计:
注:由于Edge为不可变类,所以Edge中的属性与方法遵循以下规则:
1)类声明为final,不可以被继承
2)所有成员是私有的,不允许直接被访问
3)对变量不要setter方法
4)所有可变的变量是final的,只能赋值一次
5)通过构造器初始化所有成员,进行深拷贝
6)在getter方法中不能返回对象本身,返回对象的拷贝
属性:
String source: 该边的源点,即该边从那个顶点射出。
String target: 该边的目标点,即该边输入的那个顶点。
Int weight: 该边的权值。
RI:source与target都不可为空。
方法:
构造函数:
参数:String
source,String target,int weight;
功能:将Edge的source,target,weight分别赋值。
gettarget ():
返回Edge的target值。
getweight ():
返回Edge的weight值。
CheckRep()
返回RI关系是否变更。
ConcreteEdgesGraph设计:
属性:
Set<String>
vertices:存储顶点的集合
List<Edge>
edges:存储边的集合
方法:
add(String
vertex):往图中增加一个点。
如果vertex在点集中,那么返回false;
如果vertex不在点集中,那么调用vertices.add(vertex)方法,返回true;
set(String
source, String target, int weight):在图中设一条边。
如果edges中存在source与target都与参数相等的边,那么就将其删去,新增一条Edge(source,target,weight);
如果不存在,那么新增一条Edge(source,target,weight),将其添加到edges中。
remove(String
vertex)
在vetices中搜寻vertex,如果找到,调用集合的remove方法清除掉,并将所有与vertex有关的边(即以vertex为起始点或终点的边)全部删除。返回true;若没有找到,返回false。
vertices()
返回集合vetices。
targets(String
source)
返回所有以source为起点的边的指向点为键,以每对source与指向点之间的权值作为值的Map。
sources(String
target)
返回所有以target为指向点的边的起点为键,以每对原点与指向点之间的权值作为值的Map。
重写方法:toString():返回” this class is Concrete1”。
3.1.3.2 Implement ConcreteVerticesGraph
思路:以顶点类Vertex为核心构建该图,方法大致与上述Edge图相似。
Vertex设计:
属性:
String name:该点的名字/标签
Map<Vertex,Integer> pointout:该点所有指向的点为键,与该点构成的边的权值为值的集合。
Map<Vertex,Integer> pointin:所有指向指向的点为键,与该点构成的边的权值为值的集合。
方法:
Checkup():
检查RI。RI:该点的所有指向点字典与被指向点字典是否有重复键。
Get方法:
getname():
返回name;
getout():
返回pointout;
getin():
返回pointin;
其他方法:
setinpoint(Vertex
v,int weight)
将v作为键,weight作为值插入到pointin字典中。
setoutpoint(Vertex
v,int weight)
将v作为键,weight作为值插入到pointout字典中。
removeoutpoint(Vertex
v)
将v从pointout中删除。
removeinpoint(Vertex
v)
将v从pointin中删除。
ConcreteVerticesGraph
属性:
vertices:存储图中的所有点的集合。
方法:
getV(String name)
在vertices中找到标签为name的点,返回该点。若没有,返回null.
add(String vertex):往图中增加一个点。
如果vertex在点集中,那么返回false;
如果vertex不在点集中,那么调用vertices.add(vertex)方法,返回true;
set(String source, String target,
int weight):在图中设一条边。
在source的pointout中添加target, 在target的pointin中添加source,
并将权值都设为weight.
remove(String vertex)
在vetices中搜寻vertex,如果找到,调用集合的remove方法清除掉,返回true;若没有找到,返回false。
vertices()
返回集合vetices。
targets(String source)
返回所有以source为起点的边的指向点为键,以每对source与指向点之间的权值作为值的Map。
sources(String target)
返回所有以target为指向点的边的起点为键,以每对原点与指向点之间的权值作为值的Map。
重写方法:toString():返回” this class is Concrete2”。
Problem 3:
Implement generic Graph
3.1.3.3 Make the implementations generic
将所有的String变为L,并将Edge与Vertex的类声明后面加,相关属性方法也改成泛型适用。
3.1.3.4 Implement Graph.empty()
返回一个空的ConcreteEdgesGraph类。
3.1.4 Problem 4: Poetic walks
3.1.4.1 Test GraphPoet
输入一段字符串,输出经GraphPoet处理后的字符串。将输出后的字符串与模板字符串对比,如果相同,则证明该图完成。
3.1.4.2 Implement GraphPoet
属性:
ConcreteEdgesGraph graph:一个参数类型为String 的图。
构造器:
将文件中的单词按照空格分割开来,其中任意两个单词之间大小写不区分。
从第一个单词开始遍历,设该单词为v1,下一个单词为v2。
如果v1 in graph,v2 in graph,且v1、v2之间有边,
则将边的权值加1;
如果v1 in graph,v2 in graph,且v1、v2之间无边,
则新设一条source为v1,target为v2的边,权值为1;
如果v1 not in graph或v2 not in graph,
将没有的单词全部添加进graph中,并新设一条source为v1,target为v2的边,权值为1。
poem(String input)
将输入的String按照空格切分后按顺序放到列表中。
遍历列表的第0号到第n-2号(n为字符串切分后列表的长度)
设遍历的字符串为v1,下一个字符串为v2;
如果图中没有v1或v2,进入下一个循环;
如果图中有v1、v2,那么在图中寻找一个点w,使得v1->w->v2的权值最大。
如果找到,那么将w插入v1与v2之间,进入下一个循环;没有找到,就进入下一个循环。
最后,将列表中的字符串中间插入空格,然后将列表按顺序输出。返回的即为输出的字符串。
CheckRep()
检查图的边的权值是否有负数,有的话返回false;没有返回true.
3.1.4.3 Graph poetry slam
略
3.1.5 Before you’re done
请按照http://web.mit.edu/6.031/www/sp17/psets/ps2/#before_youre_done的说明,检查你的程序。
如何通过Git提交当前版本到GitHub上你的Lab2仓库。
在这里给出你的项目的目录结构树状示意图。
Src
P1
Poet
GraphPoet.java
Main.java
Test
P1
Poet
GraphPoetTest.java(junit测试用例)
3.2 Re-implement
the Social Network in Lab1
用P1所实现的两个图,重新实现Lab1中的Social Network中的FriendshipGraph类。
3.2.1 FriendshipGraph类
继承自ConcreteEdgesGraph类
属性:ConcreteEdgesGraph中的各种属性;存类型参数为Person
方法:
addVertex(Person
vertex):调用add(vertex);
addEdge(Person
source,Person target):如果set(Person
source,Person target,1)方法返回true,那么将两个Person互加好友。
getDistance(Person
a,Person b):利用弗洛伊德算法计算后返回a与b之间的最短距离。
CheckRep():检查任意两个人的边的权值是否为负数。
3.2.2 Person类
属性:
FriendSet:存储该人的朋友的集合。
name:这个人的姓名。
num:这个人的编号。
方法:
构造器:初始化name;
AddFriend(Person
Friend):将Friend添加到FriendSet中。
getName():返回这个人的name;
setNum():设置这个人的num;
CheckFriend(Person
Friend):检查Friend是否在FriendSet中。如果在,返回true;不在,返回false。
3.2.3 客户端main()
新设一个fgraph图,测试它的getDistance方法,看其是否与默认样例相同。
3.2.4 测试用例
与main的思路大致相同
testAddvertex:调用fgraph.addVertex(Person
p)后, 将fgraph的vertices()与只有p的testSet作比较。
testaddEdge:调用fgraph.addEdge(Person
a,Person b)后, 检验a的朋友圈中是否有b,b的朋友圈中是否有a。
testgetDistance:与main的测试方法相同,最后比对各个样例的distance是否与默认相同。
3.2.5 提交至Git仓库
如何通过Git提交当前版本到GitHub上你的Lab3仓库。
在这里给出你的项目的目录结构树状示意图。
Src
P2
FriendshipGraph.java
Person.java
Test
P2
FriendshipGraphTest.java
3.3 Playing
Chess
3.3.1 ADT设计/实现方案
*注:由于设计原因,在报告中只写出了几种基础或重要的类/接口的实现方案。
Action(接口)
方法:
action(int choice, Player player,Piece
piece,int x,int y)
输入:选择,玩家,棋子,目标横坐标,目标纵坐标
功能:根据游戏内容与棋盘上目标横坐标与目标纵坐标执行行动。
返回:是否执行成功。
Player
属性:
name:姓名
number:编号
color:棋手的棋的颜色
方法:普通的set,get方法
Piece
属性:
chessPlayer :该棋子的主人
positon : 该棋子的位置
ChessCatogary : 该棋子的种类
Position
属性:
x,y:横纵坐标
方法:
构造器:初始化x与y
setX()
:设定横坐标
setY()
:设定纵坐标
getX()
:返回横坐标
getY()
:返回纵坐标
Board
属性:
Boardname:棋盘的名字
(由于其继承类较为重要,在下方着重介绍)
继承类1:CHESSBoard
属性:
Player
p1,p2:两个玩家
Map<Position,Piece> CHESSMap:以位置为键,棋子为值的Map
方法:
Initialize():将所有的棋子摆放在它应该在的位置(按照国际象棋的规则)
getMap():返回CHESSMap的句柄
getPosition(intx,int y):返回CHESSMap中横纵坐标属性为x和y的Position.
getn():返回棋盘的坐标最大值
getPiecePosition(Piecepiece):返回Piece的Position变量
getPieceCandP(intx,int y):返回字符串:棋子种类+“,”+棋手
Move(Playerp,Position source,Position target):将source处的棋子移动到target处。遇到以下特殊情况:该棋子并非属于该棋手、指定的位置超出棋盘的范围、指定位置已有棋子、所指定的棋子已经在棋盘上等时,返回false;否则执行相应操作并返回true。
继承类2:GOBoard:
属性:
Player p1,p2:两个玩家
Map<Position,Piece> GOMap:以位置为键,棋子为值的Map
getMap():返回GOMap的句柄
getn():返回棋盘的坐标最大值
getPosition(intx,int y):返回GOMap中横纵坐标属性为x和y的Position.
getPiecePlayername(intx,int y):返回坐标(x,y)处的棋子的玩家姓名
setpiece(Playerplayer, int x,int y):在(x,y)处放置一个玩家为player的围棋棋子。当(x,y)处的地方已经有棋子,或者x或y超出范围,返回fasle,否则执行操作返回true;
RemoveChess(Playerexecuter,int x,int y):移除掉在(x,y)处的围棋棋子.。当(x,y)处的地方没有棋子,或者x或y超出范围,返回fasle,否则执行操作返回true。
Game
该类实现了Action接口
属性:
String gamename;游戏名称
GOBoard goboard;围棋棋盘
CHESSBoard chessBoard;国际象棋棋盘
public static final String
GO=“GO”;GO常量
public static final String
CHESS=“CHESS”;CHESS常量
public int choice;游戏选择
Player p1=new Player("a", 1);选手1
Player p2=new Player("b", 2);选手2
方法:
MainMenu():显示菜单,返回选择
getPlayer(int num):返回编号为num的玩家
(Override) action(int
choice,Player player,Piece piece,int x,int y):根据参数执行命令。如果选择为1,那么执行围棋棋盘的相应方法;如果选择为2,那么执行象棋棋盘的方法。以上两种任意完成一种即可返回true,否则,返回false.
3.3.2 主程序MyChessAndGoGame设计/实现方案
思路:首先显示菜单栏,提示输入,之后将自己的命令输入后,就可以看到结果。可以通过查看棋盘上的棋子为何来检验操作是否成功
在选择棋类游戏的类型之后,按照提示的格式输入即可。
注:由于两人来回下太过死板,于是我把输入改成了可以自行选择本回合行子的玩家。操作*,悔棋简单,因此更受欢迎。
下方即为操作截图;
围棋:
国际象棋:
3.3.3 ADT和主程序的测试方案
由于各个类的目的都是服务于最终的Game,因此只要测试Game中最有聚集性代表性的方法action没有问题,即可证明ADT没有问题。
测试方法:输入正确与错误的操作,看其是否能够返回正确的答案。
3.4 Multi-Startup Set (MIT)
请自行设计目录结构。
注意:该任务为选做,不评判,不计分。
4 实验进度记录
请使用表格方式记录你的进度情况,以超过半小时的连续编程时间为一行。
每次结束编程时,请向该表格中增加一行。不要事后胡乱填写。
不要嫌烦,该表格可帮助你汇总你在每个任务上付出的时间和精力,发现自己不擅长的任务,后续有意识的弥补。
日期
时间段
计划任务
实际完成情况
3.22
9:00-15:00
P1完成
完成
3.26
9:00-15:00
P2完成
完成
3.31
9:00-15:00
P3完成
完成
5 实验过程中遇到的困难与解决途径
P3:如何在一个游戏中执行两种不同的操作
设立了两种棋盘,围棋棋盘与象棋棋盘,选择哪个用哪个
的经验、教训、感想
6.1 实验过程中收获的经验和教训
编码一定要先规划好类结构再写,要不然会一团糟
一定要提前写明函数功能再实现,不然很容易忘记操作要求
6.2 针对以下方面的感受
(1) 面向ADT的编程和直接面向应用场景编程,你体会到二者有何差异?
二者差异在于,面向ADT的编程其重点在ADT的架构设计,在于子类对父类的继承与父类对子类方法的调用的实现,对于实用性的考虑比较少;而面向应用场景编程则在ADT的基础上更进一步,需要从整体来考虑文件结构、类型关系与各种意外情况。
(2) 使用泛型和不使用泛型的编程,对你来说有何差异?
使用泛型的编程,
不能使用某个具体类型的方法来操作泛型,而是应该通过其他类的通用方法来操作泛型。
(3) 在给出ADT的规约后就开始编写测试用例,优势是什么?你是否能够适应这种测试方式?
优势:此时编程时思想想法以及对问题的考虑还存留在脑海中,
这时编写测试可以很容易地考虑全面。能。
(4) P1设计的ADT在多个应用场景下使用,这种复用带来什么好处?
使得编程效率大大提高。
(5) P3要求你从0开始设计ADT并使用它们完成一个具体应用,你是否已适应从具体应用场景到ADT的“抽象映射”?相比起P1给出了ADT非常明确的rep和方法、ADT之间的逻辑关系,P3要求你自主设计这些内容,你的感受如何?
是。找寻rep不是一件容易的事情。
(6) 为ADT撰写specification, invariants,
RI, AF,时刻注意ADT是否有rep exposure,这些工作的意义是什么?你是否愿意在以后编程中坚持这么做?
是
(7) 关于本实验的工作量、难度、deadline。
(8) 《软件构造》课程进展到目前,你对该课程有何体会和建议?
软件构造这门课程,设计思路新颖,教育方式紧赶潮流,通过实践与理论相结合的方式,极大提高了同学们的学习热情与思维活跃度。
下一篇: (软件构造博客)List的拷贝