目录
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
3.1.3 Problem 2: Implement Graph
3.1.3.1 Implement ConcreteEdgesGraph· 2
3.1.3.2 Implement ConcreteVerticesGraph· 2
3.1.4 Problem 3: Implement generic Graph
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
本次实验训练抽象数据类型(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 并据此设计测试用例。
在本地机器安装相应的开发环境JDK、Eclipse、Git
并在自己的Eclipse IDE中安装配置Junit
了解如何使用JUnit为Java程序编写测试代码并执行测试
没有遇到问题,一切都很顺利。
地址:https://github.com/ComputerScienceHIT/Lab2-1170300724
请仔细对照实验手册,针对三个问题中的每一项任务,在下面各节中记录你的实验过程、阐述你的设计思路和问题求解思路,可辅之以示意图或关键源代码加以说明(但千万不要把你的源代码全部粘贴过来!)。
分别新建两个类ConcreteEdgesGraph,ConcreteVerticesGraph 实现Graph接口。
Graph接口要求实现add(添加新节点),set(添加新边),remove(移除节点),vertices(获得所有的节点集合),sources(target)获得以target为目标节点的边的起始节点,targes(source)获得以source为起始节点的边的目标节点。
对于problem4,我们则需要根据文件中的输入,构造poet,再由input和图中的映射关系,得出最后的poem。
Git init
Git remote add origin [email protected]:ComputerScienceHIT/Lab2-1170300724.git
Git pull origin master
Git add .
Git commit -m “init”
Git push origin master
代码是从网上下载的
将Graph里原empty()方法修改为:
Return new concreteEdgesGraph();
再运行GraphStaticTest就可以得到结果了。
以下各部分,请按照MIT页面上相应部分的要求,逐项列出你的设计和实现思路/过程/结果。
public boolean add(L vertex)
直接调用add函数,判断如果原本就有就返回false
public int set(L source, L target, int weight)
如果weight>0 没找到指定的边,就把这个边加进去,如果找到了这个边,就更新这个边,如果weight=0 找到了这个边,就将这个边删除。
public boolean remove(L vertex)
如果没找到要删除的边,就返回false,否则就遍历,找到某个edge的source或是target与vertex相等,删除边,再删除vertex
public Set
返回vertex的set
public Map
建立一个map,遍历edges,如果某个edge的target和传入target相等,则将该边的source和weight存入map中。
public Map
建立一个map,遍历edges,如果某个edge的source和传入source相等,则将该边的target和weight存入map中。
public String toString()
主要就是增加可读性toString都是现成的
class Edge
public Edge(L start,L end,int value)
这个类要有三个重要的东西在里面,start,end,和value
所以可以知道,里面的方法就是调用这三个量。
private void checkRep()
主要是保证value大于0
public String toString()
增加可读性,表示里面的量都是啥。
public boolean add(L vertex)
若vertices()中已包含vertex,返回false,否则新建一个顶点将其加入vertices。
public int set(L source, L target, int weight)
如果存在vertices()中找到source,找到源点,否则以source为string创建一个新的源点,并将它添加进vertices。对于target操作同理。
得到源点和目标点后,分别对目标点调用addSource,源点调用addTarget即可。
public boolean remove(L vertex)
先判断存不存在这个点,如果不存在就返回false,如果存在遍历所有的点,删掉这个点,而且还要删掉所有有关系的东西
public Set
遍历vertices,找到每个点对应的name,添加进set。
public Map
如果找到了目标的target点,就返回对应的source,如果没找到,返回一个空的map
public Map
如果找到了目标的target点,就返回对应的target,如果没找到,返回一个空的map
public String toString()
增加可读性。
class Vertex
private L name;
private Map
private Map
里面需要有重要的三个东西,name,target,source
public L name() {
L nameString=this.name;
return nameString;
}
public Map
return this.target;
}
public Map
return this.source;
}
public void setName(L name) {
this.name=name;
}
public void setTarget(Map
this.target=targetMap;
}
public void setSource(Map
this.source=sourceMap;
}
public void addtarget(L target,int weight) {
this.target.put(target, weight);
}
public void addsource(L source,int weight) {
this.source.put(source, weight);
}
public void removetarget(L target) {
this.target.remove(target);
}
public void removesource(L source) {
this.source.remove(source);
}
需要实现的功能是,访问name,target,source,设置name,target,source,删除name,target,source
public String toString()
增加可读性。
public void checkRep()
检测不变性。
主要是把string都改成L,class <>L的实现分别写在两个java分析里面了
以前,可能已声明类型为Edge或变量List
同样,可能已经调用了类似new ConcreteEdgesGraph()或构造函数new Edge()要改为new ConcreteEdgesGraph
public static
return new ConcreteEdgesGraph
}
需要将empty改成这样
test测试如下
public class GraphPoetTest {
@Test(expected=AssertionError.class)
public void testAssertionsEnabled() {
assert false; // make sure assertions are enabled with VM argument: -ea
}
@Test
public void TestGraphPoet() throws IOException {
final GraphPoet graph = new GraphPoet(new File("src/P1/poet/mugar-omni-theater.txt"));
final String input = "Test the Theater system.";
System.out.println(graph.temp);
System.out.println(input + "\n>>>\n" + graph.poem(input));
assertEquals("test of the theater sound system.", graph.poem(input));
}
@Test
public void Testpoem() throws IOException {
final GraphPoet graph = new GraphPoet(new File("src/P1/poet/mugar-omni-theater.txt"));
final String input = "Test the Theater system.";
System.out.println(input + "\n>>>\n" + graph.poem(input));
assertEquals("test of the theater sound system.", graph.poem(input));
}
}
给定一个input。从文件中读取poet,调用Graph.poem()后看是否与预期相同
public GraphPoet(File corpus) throws IOException
从语料库的图形中创建一个新的poet。
先读文件,并把文件中的单词存在words中。使用BufferedReader读取文本文件中的数据,类Scanner用于将输入的文本分解成多个部分。
然后调用Graph类中的方法,将单词转化为图,添加顶点,set边,其中权值全部设置为1.
public String poem(String input)
遍历input中所有单词,调用Graph.targets()和Graph.sources()方法。如果该单词的targets和后面一个单词的sources有交集,则添加一条bridge,并且在两个单词的bridge中随机选择一个插入到字符串中。
public String toString() {
return temp.toString();
Main函数实现如下
public class Main {
/**
* Generate example poetry.
*
* @param args unused
* @throws IOException if a poet corpus file cannot be found or read
*/
public static void main(String[] args) throws IOException {
final GraphPoet graph = new GraphPoet(new File("src/P1/poet/mugar-omni-theater.txt"));
final String input = "Test the Theater system.";
System.out.println(graph.temp);
System.out.println(input + "\n>>>\n" + graph.poem(input));
}
在这里给出你的项目的目录结构树状示意图。
Lab2_1170300724
src
P1
graph
….java
poet
… .java
… .txt
test
P1
graph
…Test.java
poet
… Test.java
… .txt
继承P1中ConcreteEdgesGraph
public class FriendshipGraph extends ConcreteVerticesGraph
利用的是p1里的ConcreteVerticesGraph
public int addEdge(Person p1,Person p2)
调用set(p1,p2,1)
public boolean addVertex
调用.add添加人
public int getDistance(Person p1, Person p2)
利用广度优先搜索,需要用到队列。
广度优先遍历就是将p1入队,设置访问过出队,然后将它的相邻点入队,设置访问过,直到找到p2,给出距离,如果全都访问过也没找到p2,则返回-1
调用.target获得相邻点
private String name;
public Person(String name) {
this.name=name.toLowerCase();
}
public String Name() {
return this.name;
}
public void setName(String newname) {
this.name=newname;
}
存储名字,访问名字,设置名字
public boolean equals(Object obj) {
if (obj instanceof Person) {
Person person= (Person) obj;
return name.equalsIgnoreCase(person.Name().trim());
}
return false;
}
判断是否有重复的名字
测试代码和lab1的类似
需要测试addVertex,addEdge,其中包括重名情况,
测试getDistance其中包括没有路径的情况
Lab2_1170300724
src
P2
FriendshipGraph.java
Person.java ...
test
P2
FriendshipGraphTest.java
Board类
public class Board
public Board(Game game) 根据输入的游戏不同创建两种不同的棋盘。
国际象棋初始是有棋子的,所以要対初始棋子进行初始化,对应位置给对应的名字,围棋就简单的给出一个空白棋盘,记得空白也是要初始化的。
public int getsize()
返回棋盘的大小
public Position findPosition(int x,int y)
返回这个位置的Position类
Game类
public class Game
public Game(String name)
{
this.name=name;
}
public String getname()
{
return name;
}
就是一个存游戏名字的类
Piece类
public class Piece
public Piece(String name,boolean color)
{
this.name=name;
this.color=color;
}
public boolean getcolor()
{
boolean gtemp=this.color;
return gtemp;
}
public String getname()
{
return this.name;
}
就是个存棋子的类,可以分角色记录。
Player类
public class Player
public Player(String name,boolean color)
{
this.color=color;
this.name=name;
}
public boolean getcolor()
{
boolean gtemp=color;
return gtemp;
}
public String getname()
{
return name;
}
就是个存玩家名字和颜色(分边)的类
Position类
public class Position
这是一个用于存对应位置对应棋子的类,根据主要代码写的,有两种存储方式,一种是int int 输入,一种是int int Piece 输入,只输出了Piece,因为位置的两个int并没有用到。
public boolean set_piece(Piece piece)
在对应位置设置棋子
Action类
public boolean put_in(int x,int y,boolean color)
针对围棋的一种下棋动作
public boolean change(int x1,int y1,int x2,int y2,boolean color)
针对国际象棋的位移,就是把一个棋子下到另一个位置,因为国际象棋你也不能新建一个棋子出来啊
public boolean remove(int x,int y,boolean color)
吃子,判定,你不能吃自己的子,不能吃空白的子。(实际上只是移除,把原本位置变成空白写在了主函数里面)
Field:
class Chess
{
Player historyPlayer;
Piece historyPiece;
int command;
int x1;
int y1;
int x2;
int y2;
public Chess(Player player,int command)
{
this.historyPlayer=player;
this.command=command;
x1=-1;
}
public Chess(Player player,Piece piece,int x1,int x2,int y1,int y2,int command)
{
this.historyPlayer=player;
this.historyPiece=piece;
this.x1=x1;
this.x2=x2;
this.y1=y1;
this.y2=y2;
this.command=command;
}
@Override public String toString()
{
String temp=historyPlayer.getname()+" : "+command;
if(x1>=0)
temp=temp+" from "+x1+" "+y1+" to "+x2+" "+y2;
return temp;
}
}
class Go
{
Player historyPlayer;
int x;
int y;
int command;
public Go(Player player,int command)
{
this.historyPlayer=player;
this.command=command;
x=-1;
y=-1;
}
public Go(Player player,int x,int y,int command)
{
this.historyPlayer=player;
this.x=x;
this.y=y;
this.command=command;
}
@Override public String toString()
{
String temp=historyPlayer.getname()+" : "+command;
if(x>0)
temp=temp+" direction "+x+" "+y;
return temp;
}
}
得先创建两个游戏类的游戏类,方便实现以后的功能。
public boolean chushi()
{
Scanner Scanner=new Scanner(System.in);
String temp;
System.out.println("chess or go to play");
temp=Scanner.nextLine();
if(temp.toLowerCase().equals("chess")==true||temp.toLowerCase().equals("go")==true)
{
Game=new Game(temp);
Board=new Board(Game);
Action=new Action(Game,Board);
}
else
{
System.out.println("Err");
return false;
}
System.out.println("Please input Player1's name");
temp=Scanner.nextLine();
play1=new Player(temp, false);
System.out.println("Please input Player2's name");
temp=Scanner.nextLine();
play2=new Player(temp, true);
return true;
}
这个是初始棋盘的代码,没有单独的游戏菜单,在这步输入游戏类别和游戏玩家名。
public boolean step(boolean forward,Player now_Player)
主要的步骤函数
首先区别围棋和国际象棋,然后给出菜单选项,再根据输入的选项实现各个功能
围棋:
1:放子 2:提子 3:跳过 4:结束 5:查子 6:算子
如果是1:调用Action.put_in 判断下棋,然后加到围棋历史里面
如果是2:调用Action.remove 吃子,加到历史里面
如果是3:啥也不调用,直接加一次历史
如果是4:将判定游戏结束的变量改成true
如果是5:调用Piece.getname和Piece.getcolor 加一次历史
如果是6:遍历棋盘,记录所有不是空白的子(分颜色)
国际象棋:
1:移动 2:吃子 3:跳过 4:结束 5:查子 6:算子"
大部分都一样
特殊的是1:调用Action.change和Board.findPosition
2:调用Action.remove Action.change Board.findPosition Position.geiPiece
5查子:现根据输入的位置,调用Board.findPosition(x, y).getpiece(),先确定棋子,再根据棋子的不同使用不同的输出
public static void main(String[] args)
{
MyChessAndGoGame temp=new MyChessAndGoGame();
while(temp.chushi()==false);
for(int i=1;temp.gameover==false;i++)
{
if(i%2==1)
while(temp.step(false, temp.play1)==false);
else
while(temp.step(true, temp.play2)==false);
}
if(temp.Game.getname().equals("chess"))
{
for(Chess k:temp.Chess)
System.out.println(k.toString());
}
else
{
for(Go k:temp.Go)
System.out.println(k.toString());
}
}
最后main函数,实现整体的结构功能。
请自行设计目录结构。
注意:该任务为选做,不评判,不计分。
请使用表格方式记录你的进度情况,以超过半小时的连续编程时间为一行。
每次结束编程时,请向该表格中增加一行。不要事后胡乱填写。
不要嫌烦,该表格可帮助你汇总你在每个任务上付出的时间和精力,发现自己不擅长的任务,后续有意识的弥补。
日期 |
时间段 |
计划任务 |
实际完成情况 |
3.29 |
15:00-19:00 |
P1 的graph |
完成 |
3.30 |
15:00-19:00 |
P1的poet |
完成 |
4.3 |
16:00-18:00 |
P2 |
完成 |
4.5-4.7 |
14:00-16:00 |
P3的全部内容 |
完成 |
遇到的难点 |
解决途径 |
P1的实验要求理解不清楚
|
谷歌翻译,结合同学的实际情况 |
P3的结构无法理清
|
采用了网上的建议和结构 |
谈到Java编程那就不得不提及其中的抽象数据类型,以及面向对象编程。本次的实验而当中便是对这方面的学习进行的巩固。
如果思维方式是:我先做什么,再做什么……这叫面向过程;
如果思维方式是:我先做一个什么东西来做这件事,再做一个什么东西来做那件事,然后它们怎么相互配合……这叫面向对象。
面向过程更像是我们一开始接触编程,只是为了实现某个功能。而现在我们应该做的是多考虑面向对象,这样才有利于程序的可扩展性。
泛型在java中有很重要的地位,在面向对象编程及各种设计模式中有非常广泛的应用。泛型可以简化代码,保证代码质量。
这样可以加深自己对于spec的了解,不会忘记一些边界条件的限制导致低效率的debug,效率更高,更加方便,可以适应这种测试方式
复用可以减少相同代码再次编写的浪费时间,简化代码提高效率
所有的类都要自己设计肯定更复杂,但自由度更高,不会有理解上的困难
帮助我们时刻保持清醒,spec确定自己代码应该满足的条件,RI, AF题型自己ADT是如何设计的,不会产生前后矛盾的情况,rep exposure保证了自己程序的健壮性、安全性,是程序重要的性质,这些工作为我们养成了良好的编程习惯,是非常有必要的。
时间设计的很合理,充裕又不会太慢。