欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

软件构造lab-2

程序员文章站 2022-03-10 14:52:44
...

2020年春季学期
计算机学院《软件构造》课程

Lab 2实验报告

姓名 邹松龄
学号 1182100101

目录

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实验环境配置
本次实验下载安装了EclEmma。根据网站提示,直接从Eclipse
商店下载安装,没有遇到问题。

https://github.com/ComputerScienceHIT/Lab2-1182100101.git
3实验过程
3.1Poetic Walks
分别通过两种实例类实现图类Graph以及根据spec完成上面的各种操作,并扩展ADT泛型化,通过Graph实现Graphpoet的功能,在对编写的各个方法编写测试,完善各类的AF,RI。
3.1.1Get the code and prepare Git repository

无法直接连接MIT网站,从上处获取实验代码
3.1.2Problem 1: Test Graph
以GraphStaticTest进行junit测试的结果如下:

下面是GraphInstanceTest实现策略:
// Testing strategy
// 测试add():
// 判断点存在时返回false,不存在时返回true,通过vertice判断是否成功加入点集
// 测试set():
//输入 weight=0若已有该边边去掉该边,否则不做改变,若>0,点没有时加点,有点时返回两点间之前距离
//针对以上设计判断是否能加入不在的点,以及是否set返回值在不同情况下正确。
//测试remove()
//测试该点已在点集中的情况,以及不在点集中的情况的返回值,以及是否成功去掉该点
//测试vertices():判断该集合是否包含全部点,在增删点后是否仍旧包含?
//测试source:判断得到map是否为输入点所有起始点,权值是否正确
//测试target:与前一个类似。
ConcreteVerticesGraphTest及ConcreteEdgesGraphTest中tostring的测试方法为比较返回字符串与预期字符串是否相同
下面是junit测试结果:

ConcreteVerticesGraphTest:

ConcreteEdgesGraphTest:

3.1.3Problem 2: Implement Graph
选择ConcreteEdgesGraph进行具体实现empty方法,也可以选择另外一个实例类来实现

Junit
3.1.3.1Implement ConcreteEdgesGraph
成员不变量以及相应的AF RI Safety from rep exposure情况如下图所示

其中vertices为点集,edges为有向加权边集
// Abstraction function:
// vertices集合中对应着图中所有的点,edges中对应着所有的有向加权边
// Representation invariant:
// 每一个edge中weight大于0,edges中顶点存在于vertices中,每个点是不同的
// Safety from rep exposure:
// 所有成员变量定义为private final,
// set,list是mutible的,所以在vertices函数中涉及到返回vertices时进行了防御性拷贝

Checkrep用来检查边权值是否大于0,以及每条边起点和终点是否在点集中

public int set(L source, L target, int weight)
实现方法:根据spec,首先判断输入两点是否在集合中,若没有,直接加入,其次遍历边集,若没有该边,加入边集后返回0;若有该边,去掉原来那条边,加入新边,注意weight=0时不用加入新边。

public boolean remove(L vertex)
判断vertex是否在点集内,不在返回false,再删除点集中vertex以及含vertex的边
public Set vertices()
返回点的集合的set类。
public Map<L, Integer> sources(L target)
输入一个target,返回与它相连起始点及对应权值

public Map<L, Integer> targets(L source)
实现与前一个函数基本类似。
public String toString()
遍历边集调用edge里得tostring函数得到总的字符串。
Spec:/**
*
*
* @return 返回图的所有边,每行一个边字符串以(x,y–w)形式给出,x,y为顶点名字,w为权重
*/

Edge的实现:
private final L start;
private final L end;
private final int weight;
// Abstraction function:
// start表示为边出发点,end为目标点,weight为边的权值
// Representation invariant:
// weight必须为一个正整数,俩节点不为空
// Safety from rep exposure:
// 以private final 定义的变量类型为String和int,都是为immutable类型

构造器:

输入顶点和权值。
Checkrep:

两节点不为空,边值大于0.
观察器:三个成员变量的getter

Spec:/**
*
*
* @return 返回一个描述边的字符串以(x,y–w)形式给出,x,y为顶点名字,w为权重
*/
Tostring函数,把边按照(x,y–weight)的形式打印

3.1.3.2Implement ConcreteVerticesGraph
下面是成员变量以及AF,RI等信息。

用来checkrep()判断有没有相同的两个点

public boolean add(L vertex)

实现:判断点vertext在不在集合中,若在返回false,不在加入该点并返回true

public int set(L source, L target, int weight)

实现:根据spec,首先判断输入两点是否在集合中,若没有,直接加入,若找到该边,返回之前的值。然后找到两点,分别更改他们的起始map和终止map
public boolean remove(L vertex)
实现:首先判断其是否在点集中,删除与它相关的起点和终点。
public Map<L, Integer> sources(L target)
遍历点集找到target对应的点,返回它的起始map

Vertext的实现:
首先是成员变量AF,RI等信息
private final L s;
private final Map<L, Integer> starts = new HashMap<L, Integer>();
private final Map<L, Integer> ends = new HashMap<L, Integer>();
// Abstraction function:
// s表示节点的名称,starts表示与s相连起始点及边权值的映射,ends表示与s相连终点及边权值的映射
// Representation invariant:
// 起始和终点不包括该点本身
// Safety from rep exposure:
// 使用 private final声明,需要返回starts,ends时使用了拷贝防御
构造器:

Checkrep用来确定起始点和终点不为同一个点

三个成员变量的getter
public int putstart(L s,int weight)
判断s是否在starts里,以及weight=0时删去原来边(若存在),>0时更改添加权值
public int putend(L s,int weight)
和前一个函数实现类似

Tostring函数,把有向加权边按照(x,y–weight)的形式打印。
3.1.4Problem 3: Implement generic Graph
3.1.4.1Make the implementations generic
使用占位符L代替String。
以前,可能已声明类型为Edge或的变量List。那些将需要成为Edge和List<Edge>。
同样,可能已经调用了类似new ConcreteEdgesGraph()或的构造函数new Edge()。例如,那些将需要成为new ConcreteEdgesGraph()和new Edge()。
3.1.4.2Implement Graph.empty()

3.1.5Problem 4: Poetic walks
3.1.5.1Test GraphPoet

测试已给出样例和自己设置文档,input得到的样例
Junit测试结果:

3.1.5.2Implement GraphPoet
首先是GraphPoet类的成员变量以及RI,AF等信息
private final Graph graph = Graph.empty();
private final List ver = new ArrayList<>();
//Abstraction function:
// graph 存储通过文档生成的图,里面的每个字对应一个顶点,相邻两字有一条有向边,边的权重边等于出现的次数
// Representation invariant:
// 有向图不为空
// Safety from rep exposure:
// 成员变量以private final声明,不会返回成员变量
public GraphPoet(File corpus) throws IOException(用来生成图)
实现思路:读取文件,将其以空格换行符,文件结束划分并小写存入ver里

然后挨个读取List ver中元素将其作为顶点加入graph中,相邻两字符用set函数加上一条权值为1有向边,通过返回值大于0判断是否之前是否存在并做相应修改。

public String poem(String input)
实现思路:将字符串input划分后存入一个数组,遍历使判断相邻两数第一个数在图里的后继与下一个点的前驱是否有交集,在交际中寻找权值最大的点作为桥加入con,最后返回con

3.1.5.3Graph poetry slam
已给出的测试样例与结果

我自己的文档与

input及结果

3.1.6Before you’re done

3.2Re-implement the Social Network in Lab1
而对于lab1已经写好的FriendshipGraph中的方法,用3.1中的Graph ADT中的方法来实现它们,泛型L设计为Person。
3.2.1FriendshipGraph类
首先是FriendshipGraph类的成员变量以及RI,AF等信息
private final Graph graph = Graph.empty();
private final List plist =new ArrayList();
// Abstraction function:
// graph对应一个图,通过3.1用到的 Graph.empty()来实现边权值只为一
//plist用来存储广度优先算法时遍历过的点。
// Representation invariant:
//
// Safety from rep exposure:
// 以private final 定义的变量类型,防御性编程

@param newPerson 输入一个需要加入图中的点
* @return 当图中不存在该点时返回true
*/

public boolean addVertex(Person newPerson)

通过graph里add方法实现

/**在图中加入一条边
*
* @param p1 起始点
* @param p2 终点
*/
public void addEdge(Person p1, Person p2)
需要判断输入两点是否为同一点以及这条边是否已经存在

/** 求两点在有向图中距离
*
* @param a 起始点
* @param b 终点
* @return 若两点联通,返回距离,若不连通返回-1
*/
public int getDistance(Person a, Person b)
该方法利用广度优先搜索,通过队列实现,方法与lab1大致相同

3.2.2Person类

里面有一个构造器和一个返回人名的观察器。比较简单。
3.2.3客户端main()

客户端social沿用上次样例

输出了正确结果

3.2.4测试用例

生成了另外一个较为复杂的图

测试结果应当是答案应当是1 3 2 2 2
测试输入相同人名情况:

Junit测试结果为

3.2.5树状结构图

3.3Playing Chess
3.3.1ADT设计/实现方案
1.Piece(immutiable)
Rep:
private final String player;
private final String name;

// Abstraction function:
//  player对应该棋子所属玩家
// name用来存储棋子的名字
// Representation invariant:

//变量不为空
// 以private final 定义的变量类型,防御性编程

/**
*
* @return 返回棋子所属玩家的名字
*/
public String getplayer()

/**
*
* @return 返回棋子的名字
*/
public String getname();

2…Player(mutiable)
Rep:
private String his="";
private final String name;

// Abstraction function:
//  name存玩家名字
// his用来存储玩家所下步骤
// Representation invariant:
// name不变
//    Safety from rep exposure:

//以private final 定义的变量类型,防御性编程

/**
*
* @return 返回该玩家名字
*/
public String getname ()

/**
 * 
 * @return返回该玩家路径
 */
public String gethis() {




/**记录该玩家路径的方法
 * 
 * @param i    下一个操作棋子
 * @param x    下一个动作横坐标
 * @param y    下一个动作纵坐标
 */
public void  addstep(Piece i,int x,int y)
实现:通过添加字符串his

3…Position(immutiable)
Rep:
private int x;
private int y;

// Abstraction function:
// x对应该位置横坐标
// y对应位置纵坐标
// Representation invariant:
// x,y大于等于0
//    Safety from rep exposure:
//以private  final 定义的变量类型,防御性编程

/**
*
* @return 返回该位置横坐标
*/
public int geth()

/**
 * 
 * @return  返回该棋子纵坐标
 */
public int getz()

4.Action(mutiable)
通过empty函数创建相应子类

/**
* 初始化棋盘,将棋子分配后放入棋盘
*/
public void init();

/**
 * 
 * @param 创建棋手1
 */
public void creatplayer1(String name1);


/**
 * 
 * @param 创建棋手2
 */
public void creatplayer2(String name2);



/**
 *  围棋落子操作
 * @param player   落子的玩家
 * @param p       落子的位置
 * @return        在围棋游戏中,若落子合法,如位置未超界,落子处无棋子返回true,否则返回false
 */
 public  boolean   luozi(String player,Position p);
/**
 * 国际象棋移动棋子的操作
 * @param player  玩家
 * @param p1   起点
 * @param p2   终点
 * @return     在象棋中位置未超界,起点有玩家棋子,终点无棋子,起点终点不同返回true,否则返回false
 */
public  boolean   move(String player,Position p1,Position p2);

/**
 *  围棋提子操作
 * @param player   提子的玩家
 * @param p       落子的位置
 * @return        在围棋游戏中,若提子为对方棋子,位置未超界,提子处有棋子返回true,否则返回false
 */
public  boolean   remove(String player,Position p);


/**
 * 国际象棋移动棋子的操作
 * @param player  玩家
 * @param p1   起点
 * @param p2   终点
 * @return     在象棋中位置未超界,起点有玩家棋子,终点有对方棋子,起点终点不同返回true,否则返回false
 */
public  boolean   eat(String player,Position p1,Position p2);
/**
 * 	
 * @param player  玩家
 * @return 返回该玩家走过的路径
 */
public String  step(String player); 


/**
 * 
 * @param p  输入想查询的棋子位置
 * @return   若该位置没有棋子输入提示,否则输出棋子信息
 */
public String search(Position p);


/**
 * 
 * @param player  玩家
 * @return  查询该玩家棋子
 */
public int sum(String player);

5.Board(mutiable)
Rep:
private Piece[][] k;
private final int size;
private boolean m[][];

// Abstraction function:
// k对应一个正方形棋盘,数组里存Piece
// m用来判断棋子某位置是否有妻子
//size为棋盘大小

// Representation invariant:
// 两玩家名字不同
// Safety from rep exposure:
//以private final 定义的变量类型,防御性编程

/查询某位置是否有棋子
* @param p 输入位置
* @return 若有棋子返回true,否则返回false
/
public boolean isempty(Position p)
/

*
* @return 返回棋盘大小
/
public int getsize()
/

* 增加棋子位置要为空
* @param i增加横坐标
* @param j增加纵坐标
* @param p增加的棋子
*/
public void add(int i,int j,Piece p)

/**
*
* @param p
* @return 返回某位置棋子
*/
public Piece getpieces(Position p)

/**
*
* @param p 位置一定要有棋子
*/
public void remove(Position p)

下面两个子类spec与Action相同:
Chessaction:
Rep:
private Board b=new Board(8);
private Player player1;
private Player player2;
// Abstraction function:
// b对应一个国际象棋棋盘,数组里存Piece
// player1为玩家一姓名
//player2为玩家2姓名

//  Representation invariant:
   // 两玩家名字不同	  

// Safety from rep exposure:
//以private final 定义的变量类型,防御性编程

GoAction:
private Player player1;
private Player player2;
private Board b=new Board(19);

   // Abstraction function:
    // b对应一个围棋棋盘,数组里存Piece
	// player1为玩家一姓名
	//player2为玩家2姓名
      //  Representation invariant:
   // 两玩家名字不同	 
    //    Safety from rep exposure:
	//以private  final 定义的变量类型,防御性编程

6…Game(mutiable)
Rep :
private Action act;
private final String type;

   // Abstraction function:
    //type是选择游戏的类型
	// act根据游戏类型选择相应的动作
    //    Safety from rep exposure:
	//以private  final 定义的变量类型,防御性编程

/**
* 落子操作
* @param player
* @param x
* @param y
* @return 操作成功返回true,否则false
*/
public boolean luozi(String player,int x,int y)

/**移动棋子
*
* @param player
* @param x1
* @param y1
* @param x2
* @param y2
* @return 操作成功返回true,否则false
*/
public boolean move(String player,int x1,int y1,int x2,int y2)

/**提子操作
*
* @param player
* @param x
* @param y
* @return 操作成功返回true,否则false
*/
public boolean remove(String player,int x,int y)

}
/**
* 吃子操作
* @param player
* @param x1
* @param y1
* @param x2
* @param y2
* @return 操作成功返回true,否则false
*/
public boolean eat(String player,int x1,int y1,int x2,int y2)

/**
* 打印玩家路径
* @param player 玩家名
*/
public void step(String player)

/**
* 查找棋盘某位置信息
* @param x
* @param y
*/
public void search(int x,int y)

/**获得某玩家棋子总数
*
* @param player 玩家名称
*/
public void num(String player)

3.3.2主程序MyChessAndGoGame设计/实现方案
首先初始化参数,输入游戏类型,当输入错误时重新输入,输入玩家名字

下面以国际象棋为例

接着打印提示信息

下面进入二层循环

外层永远为真,直到有一方进入选项end,内层循环有两个并列的,通过选项调用方法改变tar值,从而决定是否结束循环换到下一个玩家。

首先进行查询棋子操作,通过调用game.search实现,坐标是0到7

张三棋子为白色士兵
然后李四进行move操作,通过game.move实现,几个选项都可以通过game封装好的办法直接实现,下面不再一一赘述

这时候查询(5,6)

得到李四移动过去的棋子。
如果遇到错误操作,会打印事先准备的报错提示,也是在game对象内部实现的。
例如在国际象棋中选择落子

移动不属于自己的棋子

直接输入错误选项等等

跳过选项直接改变tar

最后选择end操作

打印玩家路径。

函数流程为:

3.3.3ADT和主程序的测试方案
1.PieceTest
// Testing strategy
// 函数能否正确返回棋子的名字和所属者
//

Junit测试结果:

2.Playertest

对Player几个接口进行简单地验证
Junit测试结果:

3.Board
//测试策略
//testIsempty():
//在棋盘某一空位置加上棋子,判断返回值是否为false和true
//testAdd():
//测试棋盘上是否增加棋子以及增加棋子信息是否正确
//testremove():
//棋子拿走后棋盘是否改变
Junit测试结果:

5.GameTest
//测试策略
//testLuozi()
//查看选用国际象棋及围棋时,位置越界时,棋子不属于你时等各种违法行为返回值情况是否为false,以及输入正确后的影响
//testMove() testRemove()testEat()与之类似
//testNum()
//查看其返回数字与实际棋子数是否相等
Junit测试结果:

4实验进度记录
请使用表格方式记录你的进度情况,以超过半小时的连续编程时间为一行。
每次结束编程时,请向该表格中增加一行。不要事后胡乱填写。
不要嫌烦,该表格可帮助你汇总你在每个任务上付出的时间和精力,发现自己不擅长的任务,后续有意识的弥补。
日期 时间段 计划任务 实际完成情况
5.10 15:00–16:00 完成p1test设计,完成测试用例 未完成
5.10 17:00-19:00 完成p1test,并完成graph的两种实现 未完成
5.10 19:30-24:00 完成两种实现 未完成
5.11 22:00-24:00 完成实现并扩展graph 完成
5.11 14:00-18:00 完成p2及其测试 完成
5.11 18:00-24:00 设计p3测试及Piece,Player类 未完成
5.12 14:00-22.00 完成p3及其测试 未完成
5.13 完成实验内容 完成
5实验过程中遇到的困难与解决途径
遇到的难点 解决途径
在判断两个set类的交集时

通过set类的addall和retainall解决。

不熟悉类的继承

通过查阅资料解决

不会把list类型copy一个set类型返回
利用stream解决
6实验过程中收获的经验、教训、感想
6.1实验过程中收获的经验和教训
在实验的设计中意识到了防止表示泄露的重要性,以及使用不变量的好处,意识到设计一个好的详细的spec对编程人员以及客户端双方有极大的益处,而AF和RI对程序编写的指导以及对他人理解程序有帮助。在自己设计ADT时要注意兼顾全局。
6.2针对以下方面的感受
(1)面向5ADT的编程和直接面向应用场景编程,你体会到二者有何差异?
思考的角度不同,对于编程人员的要求侧重不同,
(2)使用泛型和不使用泛型的编程,对你来说有何差异?
使用泛型的编程在设计时要注意客户端的使用不能因为函数内部实现的改变而改变确保独立性。
(3)在给出ADT的规约后就开始编写测试用例,优势是什么?你是否能够适应这种测试方式?
这样可以更好地协调客户端和编程人员的对接,同时对编程人员内部实现提供了保护,以免泄露了不必要的内容给怀有恶意的客户端。
(4)P1设计的ADT在多个应用场景下使用,这种复用带来什么好处?
使程序设计出来有更好的兼容性和可扩展性,同时对客户端使用更加友好
(5)P3要求你从0开始设计ADT并使用它们完成一个具体应用,你是否已适应从具体应用场景到ADT的“抽象映射”?相比起P1给出了ADT非常明确的rep和方法、ADT之间的逻辑关系,P3要求你自主设计这些内容,你的感受如何?
并不是非常适应,在自主设计时会发生很多缺陷而更改之前的设计,不能从全局上很好地把控,在设计时应当尽量减少模块之间的耦合,简化程序
(6)为ADT撰写specification, invariants, RI, AF,时刻注意ADT是否有rep exposure,这些工作的意义是什么?你是否愿意在以后编程中坚持这么做?
当然愿意,因为在以后的长线编程中,涉及到和其他编程人员的合作,我们这样做有利于进行好的规划同时确保其他人员不会因为过失或者有意地对你涉及的程序造成影响。
(7)关于本实验的工作量、难度、deadline
工作量比上次大了许多,难度因为需要自己的设计也相应增大了deadline其实给的时间比较充裕。
(8)《软件构造》课程进展到目前,你对该课程有何体会和建议?希望增加一些习题和具体事例,不要太抽象。

相关标签: java

上一篇: 软件构造Lab2

下一篇: 第一节课!