HIT Software Construction Lab 2

 

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

 

 

 

 

Lab 2实验报告

姓名

刘帅

学号

1170500804

班号

1703008

电子邮件

[email protected]

手机号码

18945654966

 


 

目录

 

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 <String>· 1

3.1.3 Problem 2: Implement Graph <String>· 1

3.1.3.1 Implement ConcreteEdgesGraph· 2

3.1.3.2 Implement ConcreteVerticesGraph· 2

3.1.4 Problem 3: Implement generic Graph<L>· 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。

2 实验环境配置

https://github.com/ComputerScienceHIT/Lab2-1170500804

3 实验过程

3.1 Poetic Walks

让我们实现一个图的操作,接口已经提供了,需要实现两个implements Graph<L>的接口,类型使用的是String。

3.1.1 Get the code and prepare Git repository

从实验指导书给的链接中下载

3.1.2 Problem 1: Test Graph <String>

根据提供的spec:

  1. add方法是将一个节点加入图中。

如果原来有这个结点返回true,否则返回false

那么加一个节点看一下是否是返回true,再加一个相同的结点看是返回true还是false即可

  1. set方法是加一条边进去。

如果传进去的weight参数等于0就是删掉这条边,否则如果没有这条边就加一条边,返回0;如果有这条边就修改这条边的权值,并返回之前的权值

那么就可以加一个边进去看是否返回0,再加一条同样的边进去但修改权值看是否返回之前规定的权值,最后把这条边加进去但权值修改为0,看还有没有这条边即可。

  1. remove方法是移除一个结点并把与这个结点相连的边都移除

那么将一个结点移除并且看与这个结点相关的边是否存在即可

  1. source方法是返回以传入结点作为target的边的所有source结点

那么添加许多同一个target结点的边然后把所有source结点放在一个set里面最后调用source方法比较两个set是否相同即可

  1. target方法是返回以传入结点作为source的边的所有target结点

那么添加许多同一个source结点的边然后把所有target结点放在一个set里面最后调用target方法比较两个set是否相同即可

  1. vertices方法是返回所有结点。

那么添加结点的同时将这些结点添加入一个set中最后调用vertices方法比较这个两个set是否相同即可

3.1.3 Problem 2: Implement Graph <String>

3.1.3.1 Implement ConcreteEdgesGraph

Edge类:

域:private L head;

    private L tail;:

private int weight;

Abstraction function:

AF(head,tail,weight) = a directed edge from the head vertex to the tail vertex with a nonzero weight 'weight'.

Representation invariant:

weight!=0

Safety from rep exposure:

set all the fields to private, return copies and never return the reference to the inner object

方法:

  1. getWeight(), getHead(), getTail(),方法就是直接返回这几个域的defensive copy
  2. 重载了equals()方法

返回三个域的observational equality的取交

  1. 重写了toString()方法

ConcreteEdgesGraph类

Abstraction function:

AF(vertices,edges) = a graph with vertices saved in a 'Set' and edges saved in a 'List' between vertices

Representation invariant:

RI:size of 'vertices'>=0, size of 'edge'>=0, no duplicate elements in vertices and edges

Safety from rep exposure:

set fields to private,return copies of data structures or fields and never return references to the inner object.

  1. checkRep()

确定两个数据结构中的元素数量大于等于0

Vertices由于是个Set肯定不会有相同元素

利用Edge中重写的equals方法确定edges里面没有相同的元素

  1. add(L vertex)

先判定传入参数是否已经存在于vertices中,存在返回false,否则将传入参数加入vertices中并返回true

  1. set(L source, L target, int weight)

先判定传入的weight是不是为0,如果为0检查是否存在以传入结点作为source和target的边,如果有就删去并返回0,如果没有就抛出异常。

然后当weight不为0的时候检查是否是一个已经存在的边,如果是一个已经存在的边就修改边的权值并返回原来的权值

如果不是一个已经存在的边就添加新的边并返回0

  1. remove(L vertex)

先判定vertices中是否有传入的参数,如果没有返回false

如果有,就先将这个点从vertices中移除,然后遍历edges,删去所有以这个点为source或者target的边

  1. vertices()

返回vertices的defensive copy即可

  1. sources(L target)

遍历edges,建立一个新的Map,将以传入的结点作为target结点的边的source结点和权值放到Map中

  1. targets(L source)

与source雷同

Test的方法和3.1.2中所描述的雷同

3.1.3.2 Implement ConcreteVerticesGraph

Vertex类:

域:

private L name;

private Map<L, Integer> map = new HashMap<>();

Abstraction function:

AF(name,map) = a set of directed edges from source vertex name to

the target vertex represented by the String in the map with

a weight represented by the integer in the map

Representation invariant:

RI:map.size()>=0, every element in the valueset of map should be nonzero

Safety from rep exposure:

set every field to private, return copies and never return references to the inner object

方法:

  1. getName(), getMap()类返回defensive copy
  2. 重写了toString()和equals()方法

ConcreteVerticesGraph类

  1. checkRep()

确定vertices中的元素数量大于等于0

利用重写的equals方法确定是否有相同的元素

  1. add(L vertex)方法

与3.1.3.1雷同

  1. set

与3.1.3.1雷同,区别是一个使用Set另一个使用Map,操作不同但思想是相同的

  1. remove(L vertex)

与3.1.3.1雷同

  1. vertices()

与3.1.3.1雷同

  1. sources(L target)

与3.1.3.1雷同,区别是一个使用Set另一个使用Map,操作不同但思想是相同的

  1. targets(L source)

与3.1.3.1雷同,区别是一个使用Set另一个使用Map,操作不同但思想是相同的

3.1.4 Problem 3: Implement generic Graph<L>

3.1.4.1 Make the implementations generic

依照MIT的网页上,将<String>改成<L>,然后在new之后的类名后面都要加上<L>即可

3.1.4.2 Implement Graph.empty()

这里我选的是ConcreteVerticesGraph

3.1.5 Problem 4: Poetic walks

3.1.5.1 Test GraphPoet

选用的是Troye Sivan的Strawberries and Cigarettes中的部分歌词:

“ Remember when you taught me fate

Said it all be worth the wait

Like that night in the back of the cab of

When your fingers walked in my hand”

本来第三行末尾是没有of的,但是我为了加入一个特殊情况:a b c a d c这种情况输入a c之后要返回a b d c或者a d b c

之后我随机修改了每句话的大小写,然后测试了上述情况以及跨行的情况

3.1.5.2 Implement GraphPoet

3.1.5.2.1 public GraphPoet(File corpus) throws IOException

1. 将文件读入,按照空格分割

2. 将每一个独立的单词word作为一个结点插入图中,建立一个变量lastword存储上一个单词,如果word和lastword相等(观察等价)那么就不插入节点,并且将word到word自己的边的权值+1(如果没有就新建立一个)

3. 如果word跟lastword不相等,就将lastword和word之间建立一条边,距离为1

3.1.5.2.1 public String poem(String input)

1. 将input按照空格分割

2. 对分割出来的word每个String都转成小写,然后判断word是否与上一个词lastword相等(观察等价),如果相等两个词之间肯定不可能存在一个需要走两段路的路径

3. 如果不相等就在之前建立过的图中遍历,看看lastword走两段路是否能走到word,如果能够就将中间经过的结点插入到返回的字符串中

4. 返回最后得到的答案

3.1.5.3 Graph poetry slam

NO, I DON’T.

3.1.6 Before you’re done

Git add .

Git commit -m “    ”

Git push origin master

Lab2_1170500804

Src

    P1

           Graph

                  ConcreteEdgesGraph.java

                  ConcreteVertciesGraph.java

                  Graph.java

           Poet

                  GraphPoet.java

                  Main.java

                  Mugar-omni-theater.txt

                  Strawberries and Cigarettes.txt

Test

    P1

           Graph

                  ConcreteEdgesGraphTest.java

                  ConcreteVerticesGraphTest.java

                  GraphInstanceTest.java

                  GraphStaticTest.java

3.2 Re-implement the Social Network in Lab1

利用上一个Problem得到的数据结构Graph<L>来实现上一次实验的Social Network

3.2.1 FriendshipGraph类

域:private Graph<String> graph;

方法:

  1. AddVertex(Person p)

调用graph.add(String name)

  1. addEdge(Person p1, Person p2)

首先判断graph中的vertices中是否含有输入的两个人的信息,如果没有直接返回false

如果有就调用graph.set(String p1, String p2)方法

  1. getDistance(Person p1, Person p2)

大体跟上次实验差不多,这次用的不是dijkstra而是BFS

除了必须的队列之外还建立了三个数据结构

第一个是Map<String, Boolean> visited  用来存储某个结点是否遍历过

第二个是Set<String> now  用来存储当前正在遍历的一层的结点(所谓层就是由源点经过同样的路径数到达的结点)

第三个是Set<String> fut  用来存储遍历当前这一层的结点时发现的下一次层的结点

每在队列中遍历一个now中的结点就将这个结点从now中删除,标记为已经遍历过并将跟这个结点相连的所有没有遍历过的结点加入fut,当now空的时候就将distance+1然后把fut当中的结点全部加入now

3.2.2 Person类

跟上一个实验的person类一模一样

域:

private String name;

方法:

public String getName()

public void setName(String name)

public static boolean equals(Person p1, Person p2)//当两个人的名字相同就认为他们是等价的(观察等价性)

3.2.3 客户端main()

沿用了上个实验Rachel-Ross-Ben-Kramer的例子

3.2.4 测试用例

  1. testAddEdge

添加边的方法主要就是要检验true/false,那么可以新建结点但是不加入图中直接添加边检查是否是false,然后添加一个进去检查是否是false然后全添加进去检查是否是true

  1. testgetDistance

建立了好几个结点

然后判断每一个最短距离是否与预期相等

【令人赏心悦目的绿色】

3.2.5 提交至Git仓库

Git add .

Git commit -m “    ”

Git push origin master

Lab2_1170500804

Src

       P2

              FriendshipGraph.java

              Main.java

              Person.java

Test

       P2

              FriendshipGraphTest.java

3.3 Playing Chess

3.3.1 ADT设计/实现方案

3.3.1.1 Position:

Rep:

- Int x_coordinate

- Int y_coordinate

Methods:

+ int x_coord()

Spec:

+ int y_coord()

Spec:

+ Boolean equals(Piece p)

Spec:

+ String toString()

Spec:

3.3.1.2 Piece:

Mutability: immutable

Rep:

- String name à the name of the Piece

- String color à the color of the Piece

- Position pos à the position of the Piece

AF:

RI:

Methods:

+ String getName()

       Spec:

      

       + String getColor()

       Spec:

      

       + Boolean equals(Piece p)

Spec:

      

       + Position getPos()

       Spec:

      

3.3.1.3  Player

Rep:

-String name

- Set<Piece> playerpiece

- List<String> playHistory

Methods:

+ void addPiece(Piece p)

Spec:

+ void removePiece(Piece p)

Spec:

+ void removePiece(Position pos)

Spec:

+ Boolean ownPiece(Piece p)

Spec:

+ void addHistory(String op)

Spec:

+ Piece getPiece(Position pos, String name, String color)

Spec:

+ List <String> getHistory()

Spec:

3.3.1.4 Action:

Mutability: mutable

Rep:

-Map <String, Integer> sides

- Player P1

- Player P2

- Board

AF: AF(side, p1, p2, board) = the action operated on the 'board' by player p1 and p2, whose color is saved in 'side' respectively in the form of map.

RI:side.containsKey(p1,p2)

Method:

+ void put(Player player, Piece piece, Position pos):

Spec:

+ void move(Player player, String p, Position targetPos, Position sourcePos)

Spec:

+ void remove(Player player, Position pos)

Spec;

+ void eat(Player player, Position sourcePos, Position targetPos)

+ Piece Query(Position pos, Player player)

3.1.1.5  Board

Mutability: mutable

AF(board, length) = A board whose row and column number is length.

RI: length>0

Rep:

Piece[][] board: the pieces in the board are saved in the form of two-dimensional array

Int length à the row and column number of the board

Method:

+ void SetPiece (Piece p, Position pos)

Spec:
      

+ Boolean boardAvailable(Position pos)

Spec:

+ void removePiece (Position pos)

Spec:

+ Boolean insideTheBoard( Position pos)

Spec:

+ Piece getPiece(Position pos)

Spec:

+ String boardSituation(int x, int y)

+ Boolean equals(Piece[][] b)

Spec

+ int getNumber()

Spec:

3.1.1.6 Game

Spec:

+ void putPiece(Piece p)

+ void movePiece(Player player, String piece)

Spec:

+ void removePiece(Player player, Position pos)

Spec:

+ Boolean eatPiece(Player player, Position sourcePos, Position targetPos)

Spec:

+ int getPieceNum()

Spec:

+ Piece inquire(Position pos, Player player)

Spec:

+ String toString()

Spec:

+ String getAllHistory()

Spec:

3.1.1.6  关系

 

Board, Position和Player并不依赖于任何类。

Set, remove, move, eat, query, getnumber都在Action里面实现,Game类里Set, remove, move, eat, query, getnumber这些方法都是在传入参数时加上了player确定了是哪位玩家在操作后直接调用Action里面的方法

其他的类都是一些辅助的方法和getter、equals、toString

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

辅之以执行过程的截图,介绍主程序的设计和实现方案,特别是如何将用户在命令行输入的指令映射到各ADT的具体方法的执行。

主程序首先让用户选择是玩象棋还是玩围棋:

之后如果选择了

  1. 象棋:

新建棋盘通过读入描述文件完成

到达哪个玩家的回合时就会输出是这个玩家的回合,并且打印选项菜单

 

接下来对所有的选项做出说明:

1)     Move:

会请求用户输入想要挪动的棋子,起始位置和终止位置:

 

最后如果操作完成就会给出本次操作的字符串描述,并且添加到playhistory中,如果不能完成操作就会抛出异常(具体如何抛出请参见Acition.move的spec)。

2)     Eat

会请求用户输入想要吃别的棋子的棋子并且输入起始位置和终止位置,如果操作完成就会给出本次擦欧洲ode字符串描述,并且添加到playhistory中,如果不能完成就会抛出异常(具体如何抛出请参见Acition.eat的spec)

 

3)     Inquire

会请求用户输入想要查询的x坐标和y坐标

可以看到上面的一步黑棋用在(7,3)的King吃了(0,4)白棋的Queen,现在就来查询一下(0,4)是哪个棋子:

 

发现的确是黑棋的King

4)     计算棋盘上一共有多少个棋子:
会直接返回一个int是棋盘上棋子的个数

 

5)     最后会询问是否查询记录,输入Y/N,都会被转换成小写

 

       这里说明了其实我上面查询棋子剩下30个是没错的,虽然我只展示了一个eat但其实我做了两个eat的操作: )

  1. 围棋

与象棋相同,围棋也会到达哪个玩家的回合时就会输出是这个玩家的回合,并且打印选项菜单

接下来对每个选项进行说明:

1)     Set

会请求用户输入想要下的位置的横坐标和纵坐标,然后自动新建一个Piece对象然后放在board上,最后如果操作成功就会输出一个字符串描述并且加入到playhistory中

2)     Remove

会请求用户输入想要remove的横坐标和纵坐标,如果能够成功移除就会输出一个对操作的字符串描述,如果不能成功就抛出异常(具体如何抛出请参见Action中的spec)

 

剩下两个操作跟象棋中的是一样的

 

最后依旧是会询问是否查询历史:

3.3.3 ADT和主程序的测试方案

介绍针对各ADT的各方法的测试方案和testing strategy。

介绍你如何对该应用进行测试用例的设计,以及具体的测试过程。

首先我认为一些方法中的toString, getter/setter方法没有必要测试,最主要需要测试的是Action中的五个方法

测试equals方法我又写了一个静态方法,与原来的方法判断的方法完全相同,不同的是需要传入两个Piece[][]和一个代表行列数的int作为参数。

测试set的时候就是新建棋子,新建一个用来测试的棋盘然后通过set方法下到棋盘上去,同时用equals将两个棋盘进行比较,如果相同就通过,否则就不通过。

测试remove的时候先添加棋子,然后逐个删去棋子,每删去一个棋子都用equals判断一下两个棋盘是否相同

测试move的时候就移动棋子,每移动一个棋子用equals方法判断两个棋盘是否相同

测试eat的时候也是雷同,先新建棋子放到棋盘上(因为没有必要把整个棋盘都新建出来),然后不断地用棋子吃其他的棋子,用equals判断两个棋盘是否相同

3.4 Multi-Startup Set (MIT)

请自行设计目录结构。

注意:该任务为选做,不评判,不计分。

4 实验进度记录

请使用表格方式记录你的进度情况,以超过半小时的连续编程时间为一行。

每次结束编程时,请向该表格中增加一行。不要事后胡乱填写。

不要嫌烦,该表格可帮助你汇总你在每个任务上付出的时间和精力,发现自己不擅长的任务,后续有意识的弥补。

日期

时间段

计划任务

实际完成情况

2019.3.18.

实验课

了解实验内容,创建本地仓库并完成第一次推送

按时完成

2019.3.18

20:30-22:30

完成了GraphInstanceTest,阅读了MIT的Reading13以及各种链接,做了预备知识的储备

按时完成

2019.3.23

12:30-17:00

完成了ConcreteEdgesGraph和它的测试类(照我这个速度做不完啦要QAQ)

超出预计2小时

2019.3.24

10:30-14:00

完成了ConcreteVerticesGraph和它的测试类

超出预计1小时

2019.3.25

实验课

完成graphpoet的编写

按时完成

2019.3.25

18:00-19:15

完成graphpoet的测试类编写和调试

按时完成

2019.3.25

20:10-22:50

完成P2嘿嘿嘿今天效率还挺高的

按时完成

2019.3.30

10:00-12:00

15:00-17:15

18:30-23:00

完成P3的6个类

完成一部分main

按时完成

2019.3.31

14:00-15:15

完成P3main的一部分

按时完成

2019.3.31

21:00-23:00

完成P3 Game类的test

按时完成

2019.4.1

实验课

完成P3的main

按时完成

2019.4.1

18:00-19:40

Debug 做完了做完了做完了!愚人节快乐!(并不)

按时完成

2019.4.6

12:00-6:00

写完报告

按时完成

5 实验过程中遇到的困难与解决途径

遇到的难点

解决途径

P2 BFS的时候不知道怎么一层一层的搜索的时候确定层数

通过两个数据结构now&fut来分隔正在遍历的一层和即将遍历的一层

6 实验过程中收获的经验、教训、感想

6.1 实验过程中收获的经验和教训

6.2 针对以下方面的感受

(1)   面向ADT的编程和直接面向应用场景编程,你体会到二者有何差异?

感觉面向ADT编程就是设计一个总体的框架,这个框架是可以通过不同的应用方法进行一定程度上的变化的,可变性更加强,适用性也更加广,可复用性更加强。

(2)   使用泛型和不使用泛型的编程,对你来说有何差异?

使用泛型能够将自己的ADT适用于更加多的情形中,本质上没有什么区别,但是抽象化过程中会有一定的难度

(3)   在给出ADT的规约后就开始编写测试用例,优势是什么?你是否能够适应这种测试方式?

这样可以加深自己对于spec的了解,并且不会因为睡了一觉之后忘记spec的具体内容之后在实现的过程中由于忘记了一些边界条件的限制导致低效率的debug,效率更高,更加方便

可以适应这种测试方式

(4)   P1设计的ADT在多个应用场景下使用,这种复用带来什么好处?

代码应用场景更广,可以提升代码的价值

(5)   P3要求你从0开始设计ADT并使用它们完成一个具体应用,你是否已适应从具体应用场景到ADT的“抽象映射”?相比起P1给出了ADT非常明确的rep和方法、ADT之间的逻辑关系,P3要求你自主设计这些内容,你的感受如何?

所有ADT都要自己设计的确非常的困难,这需要我们对于抽象问题的能力有着进一步的提升,并且对于整个问题的spec有着更加深刻的把握才能将这些ADT设计好

(6)   为ADT撰写specification, invariants, RI, AF,时刻注意ADT是否有rep exposure,这些工作的意义是什么?你是否愿意在以后编程中坚持这么做?

帮助我们时刻保持清醒,spec确定自己代码应该满足的条件,RI, AF题型自己ADT是如何设计的,不会产生前后矛盾的情况,rep exposure保证了自己程序的健壮性、安全性,是程序重要的性质,这些工作为我们养成了良好的编程习惯,我很愿意在以后的编程中坚持这么做,虽然累了点,但的确是非常有必要的。

(7)   关于本实验的工作量、难度、deadline。

工作量、难度、deadline都很合理

(8)   《软件构造》课程进展到目前,你对该课程有何体会和建议?

我觉得老师上课讲的内容开始实际应用到实验的编写上去了,但是实验永远领先上课一步让人还是感觉有点不适应

猜你喜欢

转载自www.cnblogs.com/SebastianLiu/p/10662167.html