Lab 2 Software Construction

3.1 Poetic Walks

这个问题实际上是在帮助我们建立对ADT的基本印象,包括ADT设计的一些规约,方法,如何设计一个能够泛型化的ADT。通过P1,我们在做实验的过程中加深了对AF(Abstract Function),RI(Represent Invariant),rep exposure 这些抽象概念的理解,积累了一些如何避免表示泄露的方法。
这个实验很好的给出了一个如何设计泛型ADT的方法,首先先针对某一个特定实例设计具体的ADT:

  1. 首先给出接口Graph
  2. 通过不同方法实现该接口,如ConcreteVertexGraph。对于该类的实现遵循ADT设计的基本方法,首先需要写好Spec,AF,RI,同时为了避免表示泄露,一定要尽量用private修饰成员变量,如果遇到observer方法,也要注意defensive copy。
  3. 编写具体类的测试方法,通过junit框架,同时用EclEmma插件检查覆盖度,尽量避免deadcode。
  4. 将具体类转换成抽象泛型,转换方法很简单,只需要注意将声明和引用修改即可
    具体参考:http://web.mit.edu/6.031/www/sp17/psets/ps2/

3.1.1 Get the code and prepare Git repository

老师在实验报告中给出了github仓库的的地址,可以通过git clone 指令直接将该仓库克隆下来,也可以通过pull 指令。当然最简单的方法可以直接在QQ群中下载实验包,直接导入到新建的project中。
通过git实时同步项目进程。
命令:
1、 git status
2、 git add
3、 git commit -m “first version”
4、 git push(第一次需要 git push -u origin master)

3.1.2 Problem 1: Test Graph

利用等价类划分和笛卡尔积的策略对每一种情况进行测试。

3.1.3 Problem 2: Implement Graph

分别用两种方式实现Graph。

3.1.3.1 Implement ConcreteEdgesGraph

3.1.3.1.1.1 Edge类实现

首先在实现这个类之前,需要设计相应的AF,RI,还要想好合理的防止表示泄露的方法。AF定义了相应的成员变量,RI定义了成员变量的合理取值范围。

如上图所示,Edge包含两个私有String类型source和target存放每个边的起止点,一个私有int类型weight保存这条边的权重(长度)。
Edge中包含的方法如下:
Edge 构造方法,使用上述三个数据域声明一个新的边
getSource 返回source域
getTarget 返回target域
getWeight 返回weight域
isSame 判断顶点是否是相同的,是返回true,否则返回false
checkRep 检查表示不变量,其中source和target必须非空且不等,weight必须大于0

3.1.3.1.1.2 ConcreteEdgesGraph实现

ConcreteEdgeGraph类中含有两个私有成员变量:
vertices 记录当前graph含有的点
edges 记录当前graph含有的边
ConcreteEdgeGraph中包含的方法如下:
add 如果顶点不为空,添加一个顶点
toString 对每条边调用toString方法

checkRep 检查表示不变量,其中source和target必须非空,weight必须大于0
Set 输入source,target,weight,确定一条有向边。
具体做法:如weight!=0,移去可能已经存在的相同起始点的边,然后加入新的边,如weight=0,寻找可能已经存在的相同起始点的边,删去。
如果
remove 从vertices中删去,传入的参数vertex点,遍历edges,寻找是否有边的起点或者是终点是该vertex,删去。
vertices 返回vertices集合
sources 参数:target。根据传入的target参数寻找以targe为终点的边。返回一个键值对为(点,权重)的map。
targets 参数:source。根据传入的source参数寻找以source为起点的边。实现同上。

关于AF,RI和rep exposure:

这里给出defensive copy 的实例:

这样做的目的是为了在保证客户端能够访问一些成员信息后,不会直接修改该成员信息的信息,保证了内部数据的安全性。用private修饰成员变量的原因是为了防止client在调用该类是直接知道其内部的数据表示方式,也保证了安全性。
关于测试:对于该类中的每一个方法用等价类划分方式测试,测试每一种情况:

覆盖度:利用EclEmma插件测试代码覆盖度:

3.1.3.2 Implement ConcreteVerticesGraph

3.1.3.2.1 Vertex类:

Vertex包含一个私有String类型 label存放该顶点的标签名称,两个私有的Map类用于存放顶点的出边和入边,key值为边的另一个顶点,value为边的权值。
方法如下:
Vertex 构造方法,传入参数name创建新的点。
checkRep 检查表示不变性,各边weight的值应该永远大于0
setInedge 为当前点新增一个source,
如果weight=0,删去当前点的source,成功返回删去source的weight,不存在返回0。如果weight!=0,为当前点新增一个source,长度为weight,如果该点已存在,返回旧的weight,否则返回0
setOutedge 为当前点新增一个target,
如果weight=0,删去当前点的target,成功返回删去target的weight,不存在返回0。如果weight!=0,为当前点新增一个target,长度为weight,如果该点已存在,返回旧的weight,否则返回0
toString 输出能看懂的当前点信息
getTarget 返回给定点的出边的目标点Map,使用防备性拷贝
getSouce 返回给定点的入边的源点Map,使用防备性拷贝

关于AF,RI和rep exposure:

关于测试:测试策略同上面关于edge的测试

3.1.3.2.2 ConcreteVerticesGraph类

ConcreteEdgeGraph类中含有一个私有成员变量顶点列表。
方法如下:
add 参数:vertex,判断vertices中无重复点就加入
set 参数:source, target, weight。先将可能不在vertices中的source点和target加入vertices。随后遍历vertices,找到source对它增加一个target,找到target为它增加一个source,并设置权值。
remove 参数:vertex。遍历vertices,如果当前点是vertex,删去(使用iterator.remove方法),如果不是,检查它的source和target是否包含vertex,如果有删去。
sources 参数:target。遍历vertices找到target点返回它的sources map(注意防御性拷贝)
targets 参数:source。遍历vertices找到source点返回它的targets map(注意防御性拷贝)
toString 输出当前图的表示信息

关于AF,RI和rep exposure:

Defensive copy实例:

关于测试:

关于覆盖度:

3.1.4 Problem 3: Implement generic Graph

改成泛型:只需要将函数声明和调用修改一下即可

可以使用eclipse的查找功能实现快速替换:

3.1.4.1 Make the implementations generic

改成泛型:

3.1.4.2 Implement Graph.empty()

调用其中一个具体实现:
测试覆盖度:

关于测试:

3.1.5 Problem 4: Poetic walks

这个问题主要是调用之前我们实现的一个GraphADT图结构实现是个创作,具体的原理可以查看:http://web.mit.edu/6.031/www/sp17/psets/ps2/

3.1.5.1 Test GraphPoet

测试策略:

测试对于给定的输入是否有相同的输出。

3.1.5.2 Implement GraphPoet

函数声明:在Graphpoet类中有一个私有成员变量graph存放词类关系。
方法如下:
GraphPoet 参数:corpus文件路径。打开文件,读取文件输入,识别序列,构建图结构。具体:利用BufferedReader.readLine方法读取全部输入后用string.split以空格划分,保存在数组中,随后每次取相邻元素,在图中新增边。
poem 参数:input。
还是利用相同方法分割输入字符串,声明一个StringBuilder保存返回结果。每次读取一个词,然后以当前词为source,下一个词为target,在graph中寻找符合此条件的边,记录权值,结束后选择权值最大的,利用StringBuilder. Append方法,将节点名字加入字符串。

Getbridge 输入两个图中的顶点,在图中遍历查询是否有可以当做桥的顶点,如果存在就返回该顶点 ,否则返回null
关于AF,RI和rep exposure:

3.1.5.3 Graph poetry slam

原始数据是普希金的《假如生活欺骗了你》

输入为:Heart living tomorrow

输出:

3.1.6 Before you’re done

提交流程:
1、 git status
2、 git add
3、 git commit -m “first version”
4、 git push(第一次需要 git push -u origin master)

目录结构:

3.2 Re-implement the Social Network in Lab1

利用前面实现的graph,重新实现Social Network。

3.2.1 FriendshipGraph类

函数声明:

将以前的邻接表结构改为新的有向图结构。更改一下函数的成员变量,再做一个简单的替换即可。每个人作为关系图上的一个顶点,朋友关系以图中的边来表示,每条边的权值都为1。
addVertex
addEdge
getDistance 参数:Person1和Person2
获取二人距离,具体是使用队列+深度优先搜索,

关于AF,RI和rep exposure:

3.2.2 Person类

之前每一个person会需要维护一个朋友列表,但是现在person只需要一个label表示姓名即可:

关于AF,RI和rep exposure:

3.2.3 客户端main()

测试结果:

3.2.4 测试用例

测试用例直接复用lab1中的即可:

3.2.5 提交至Git仓库

提交过程同P1,不再赘述。

3.3 Playing Chess

3.3.1 ADT设计/实现方案

ADT设计最关键的是要做好类的封装以及如何管理类之间的交互。这个chessADT我写了两遍 ,第一遍考虑的比较草率,没有很好的构思就开始动手敲代码,以至于有的类显得特别冗长,而且和其他的类有很高的耦合度;而有的类直接沦为的僵尸类,没有任何的作用。这样做的还有一个后果就是有的时候为了实现功能不得不要将一些类的表示暴露出来,具有很高的危险性。第二遍写这个ADT的时候,我和同学交流了一下思路,决定Board类,Action类都只做一些简单的操作,以Game类将这两个类联系起来。比较好的实现的数据的分装。但是还是有一些问题存在:比如异常的处理显得比较低级;未能实现GUI。在后期会改进这方面的不足。
在这个类的设计中,我以一下顺序给出解释:
1、 AF,RI的设计
2、 成员参数
3、 内置方法

3.3.1.1 Board类

关于AF,RI和rep exposure:

参数:

方法:
initBoardwithtype 参数:String type, Player player1,Player player2
以type类型初始化棋盘,分别有chess和go类型
rmfromBoard 参数:Position position
(对于任意给定位置,都会检查其边界合法性,下面不再赘述)
若给定位置上的存在棋子,从棋盘上删去该棋子,返回true。
否则返回false
setPiece 参数:Piece piece,Position position
给定棋子和位置,在棋盘上set该棋子(需要检查该位置是否为空)
设置成功返回true,否则返回false
movePiece 参数:Player player, Position p1, Position p2
给定位置,在棋盘上将位置p1的棋子移动到p2(需要检查p1是否存在棋子,p2位置是否为空,还需要确定棋手是否对该棋子有操作权)
设置成功返回true,否则返回false
checkPosempty 参数:Position position
检查给定位置是否为空
为空返回true,否则返回false
checkPosevalid 参数:Position position
检查给定位置是否合法:是否超出棋盘边界

getPiece 参数:Position position
如果位置存在且合法,返回给定位置处的棋子
printboard 打印棋盘

3.3.1.2 Action类

关于AF,RI和rep exposure:

参数:

方法:
setPiece 参数:Player player, Board board ,Piece piece
放置棋子,成功返回true,否则返回false
movePiece 参数:Player player, Board board, Position p1, Position p2
移动棋子,成功返回true,否则返回false
checkPose 参数:Board board,Position position
检查指定位置的使用情况。
removePiece 参数:Player player,Board board ,Position position
提子,成功返回true,否则返回false
eatPiece 参数:Player player, Board board, Position p1, Position p2吃子,成功返回true,否则返回false
printHistory 打印走棋历史

3.3.1.3 Game类

关于AF,RI和rep exposure:

参数:

方法:
menu() 菜单选项
goGame 参数:String typeString, String player1name, String player2name
以type类型初始化游戏

3.3.1.4 Piece类

关于AF,RI和rep exposure:

参数:

方法:
Piece 构造器参数:Position position, Player ownerPlayer, String pName
初始化棋子
getposition 获取该棋子的位置
setPosition 参数:int px,int py
设置该棋子的位置
getname 获取该棋子的名字
getPlayer 获取该棋子的归属者
toString 输出棋子信息

3.3.1.5 Player类

关于AF,RI和rep exposure:

参数:

方法:
Player 构造器参数:String playername
初始化玩家
addPiece 参数; Piece piece
向玩家的棋子集合中添加棋子
getPieces 获取玩家的棋子集合
rmfromSet 参数:Piece piece
将指定棋子从玩家的棋子集合中移除
getname 返回姓名
equals 重写了equals方法,只需要player名字相同即可认定相同

3.3.1.6 Position类

关于AF,RI和rep exposure:

参数:

方法:
Position 构造器参数:int px, int py
初始化位置
getx 返回X坐标
gety 返回Y坐标
tostring 输出位置信息
equals 重写equals方法,认定条件为px和py均相同

3.3.2 主程序MyChessAndGoGame设计/实现方案

因为重构了代码,所以在第二次的代码中主程序的结果相当简单:

用户根据程序提示键入棋类游戏类型,玩家一二的姓名,程序调用Game类,进入游戏。
未重构的代码的部分:

游戏执行实例:

进入菜单选项:提示玩家操作

移动棋子

打印棋盘

查看棋子数目和走棋历史

3.3.3 ADT和主程序的测试方案

测试的方法还是基于等价类划分,对于每一个类中的每个方法中的每一种情况都做测试。

3.4 Multi-Startup Set (MIT)

请自行设计目录结构。
注意:该任务为选做,不评判,不计分。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值