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

软件构造Lab2

程序员文章站 2022-03-10 14:15:13
...

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:

  1. 从Eclipse菜单中选择Help → Eclipse Marketplace。
  2. 搜索“EclEmma”
  3. 下载
    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) 《软件构造》课程进展到目前,你对该课程有何体会和建议?

软件构造这门课程,设计思路新颖,教育方式紧赶潮流,通过实践与理论相结合的方式,极大提高了同学们的学习热情与思维活跃度。