软件构造实验

诗人漫步

  • 本任务通过测试优先的软件构造,用两个具体的类实现泛型GRAPH,理清了面向对象OOP的基本思路。并通过具体的实例使用这个泛型类。

Problem 1: Test Graph

  • 首先编写测试add。查看graph中该方法规约:
  • 参数是顶点的标签。如果图中不存在给定的标签的顶点则返回真,否则的话返回假。最后判断顶点集合中是否存在这三者
  • 编写测试Set:将输入划分:
    对source和target看成一个整体进行划分:source存在,target不存在;均不存在;source存在,target存在,组成的边存在;source存在,target存在,组成的边不存在。
    对weight的划分:等于0,大于0
  • 划分的笛卡尔积,每个区域选择一个测试用例。并选择一个具有较好代表性的测试路径,在适当的位置判定顶点集合如图所示:
    测试方法
    测试方法:先测试不存在的顶点。再删除一个存在的顶点,看边是否被删除。建立的图如图所示。

首先利用set方法加入如图所示的三条边。首先判断一个不在图中的顶点e,应该返回false;其次,删除顶点a,应该返回true,并且b,d的target集合不应该包含a,c的source集合不应该包含点a。测试代码如图所示:

Vertices方法:利用add方法加入几个顶点,判断这几个顶点组成的集合是否就是得到的顶点集合。

Sources方法:
建立图形,测试孤立点的集合为空,测试只有发出的点的集合(没有边指向他)为空集,测试既有发出的点又有到达的点的集合为只有到达的点组成的集合,测试只有到达的点的集合为到达的全部点组成的集合。

target方法:与测试source方法是一致的。

3.1.3.Problem 2: Implement Graph
以下各部分,请按照MIT页面上相应部分的要求,逐项列出你的设计和实现思路/过程/结果。
3.1.3.1Implement ConcreteEdgesGraph
首先填写edges类的规约:Represent an edge according to source and target;填写防止表示泄露的方法。再填写表示不变性:weight>0;然后分别实现获取权值,获取开头,获取结尾的函数再重写to string方法。
完成对于edges类的测试。
首先测试构造函数,直接判断对象构造的属性是否和预期一致即可。
其次测试checkrep函数。划分输入weight>0与weight<=0。判断前者不抛出异常,后者抛出异常。
再测试getsource和gettarget方法。分别获取再与预期比较即可。
最后测试tostring方法。测试方法与前两者是一致的。

下面开始实现ConcreteEdgesGraph。
根据规约写出表示不变性:顶点的标签是不一致的。边的权重均是>0的。边中的顶点一定要包含在顶点集合中。
防止表示泄露的方法是所有字段均为Private且使用了防御式拷贝;
实现该类的checkrep方法:

实现vertices方法,使用防御式拷贝,新建立一个对象,对象里面的值就使用原先顶点里的每一个。

实现remove方法:集合中不存在给定的顶点直接返回false,否则删除所有关联的边并且删除该顶点。
实现add方法:顶点集中存在当前顶点则返回false,否则将顶点加入顶点集后返回true。
实现set方法:利用一个edge类型的迭代器实现。遍历边的链表,如果找到对应的边,且要求的权重为0,那么将这条边删除。如果要求的权重不为0,那么将这条边的权重设置为对应的值。否则的话,如果找不到对应的边,那么将这条边加入链表中,并向顶点集合中加入顶点。(注意顶点集合的类型为set,因此可以不论如何重复加入顶点都可以使得顶点保持不重复)
实现source方法:首先新建一个map对象。键值为顶点的标签类型string类,对应的值为Integer类型。遍历边链表中的每一条边,当这条边的target与参数target一致时,将这个source点的标签作为键值,边的权重作为值加入map。最后返回即可。
实现target方法:方法与实现source方法是一致的。
重写tostring方法:按照一定的格式返回字符串即可。
测试tostring方法:测试策略:划分测试图形为空图和一般图。检查得到的字符串是否和预期是一致的。
测试的结果如图所示:

3.1.3.2Implement ConcreteVerticesGraph
首先实现并测试Vertex类。在这个类里面防止两个fields,一个存放当前顶点的名字
一个存放这个顶点发出边对应的顶点的label和权重信息,使用一个map来实现。因为两者都是私有的,并且在适当的位置使用了防御式拷贝,所以防止了表示泄露。表示不变性是所有边的权值是正数,即map的值为正值。
确定规约后,开始编写测试。测试方法详见测试文件。
在Vertex类中,需要返回当前顶点的邻接的顶点(使用防御式拷贝)。加入边,改变边的权重,获得边的权重,为实现ConcreteVerticesGraph做准备。
在该类中,重写了equals方法以表明只要Label相同的,便是同一个顶点对象。这个方法实现的源代码实现如图所示:

为了实现tostring方法,需要对边进行排序,然后再将结果加到一个result中。

接下来是编写ConcreteVerticesGraph类,重写接口中的方法。尤其需要注意的是set方法,可以分类讨论:
Source存在分两类:weight存在,weight不存在。在这时只调用Vertex类中的changeedge方法即可。如果source不存在且weight>0的情况注意首先必须把顶点加入List中。此时最微妙的地方是需要判断target是否存在,但不论如何target是否存在,都不阻碍我们用add方法将target加入list中。(target存在时,add不做任何事)。

tostring方法:利用顶点->顶点 权重的形式在每一行打印出相关信息。

最后运行测试文件,测试结果如图所示。

3.1.4Problem 3: Implement generic Graph
3.1.4.1Make the implementations generic
按照指示的要求:
首先将每个类名替换成泛型的形式,如图所示:

再替换所有的String为泛型L。例如,如图所示:

再修改test中的相关构造函数,例如,如图所示:

最后,测试成功的界面如图所示:

3.1.4.2Implement Graph.empty()
如图所示,随意地挑取一种实现方式返回即可。

为了测试标签是否满足,将泛型更换成Integer和Double类型,重新测试,测试通过。

3.1.5Problem 4: Poetic walks
3.1.5.1Test GraphPoet
首先查看类中的规约的要点:
以语料库初始化生成有向图。图中每一个顶点代表单词。单词被定义为不区分大小写,没有空格,没有新行的字符组合。在语料库中以空格、文件结束符、或新行作为区分。
图中的边代表两个邻接的单词出现的次数。W2跟在w1之后的次数,在图中用用一条从w1指向w2的权重为该次数的边所表示。
给定一个输入字符串,通过插入图中的单词作诗。在图中,如果w1->b->w2是一条具有两条边的路径,在作诗过程中,对于任意两个邻接的输入字符串中的单词w1,w2;如果w1至w2在原图中有两条边长度的路径,那么插入其中经过的顶点对应的单词到原字符串中,并且要保证这条路径的权重是最大的。
如果不存在这样的单词,那么不插入;
在输出的字符串中,输入的字符串保证他们原有的大小写形式,每个单词间以空格作为区分。

首先确定抽象函数:AF(graph) = 一个图,顶点表示单词,边表示相邻两个单词出现的次数。w1->w2的权重为a,当且仅当在语料库w2紧接着w1出现n次。可以利用此图根据规约的描述生成文章。
其次确定表示不变性:图中所有顶点的label为非空,不为null。
防止表示泄露的方式是:所有的字段都是私有的,final的。
为了方便测试,增加了两个observe.因为在graph类中天生这两个方法用了防御式拷贝,因此避免了表示泄露。
测试四种constructor。测试方式如下:

测试poem方法:测试方法如下:

3.1.5.2Implement GraphPoet
首先开始实现两种constructor。其中一种从文件读入语料库,另一种直接以字符串作为参数。需要解决的第一个问题是如何将该字符串按照一定的规则进行分隔。这里我们采用正则表达式的方式,关键源代码如图所示:

该正则表达式的含义是:使用\r\n或空格一次或重复多次的形式的字符串对文本进行分隔。但一个微妙的点是如果文本以这些字符开始,所生成的第一个字符串是空串,也就是我们所不想要的。于是,我们另申请了一个动态数组,里面存放了小写形式的,去掉a中空串的那些字符串,顺序存放。与此同时,将这些转换后的字符串转换为小写形式作为顶点存放入图中:

除此之外,便是根据b中的两两组合在图中添加边了。边如果原先存在,那么将权重增加1;如果不存在,那么加上这条边,同时将权重设置为1;
其次要实现poem方法。还是以上述方法将输入字符串分隔放入一个动态数组b中。然后两两拿出b中的元素。首先判断这两两拿出的元素转换为小写之后的形式在图中是否均存在。如果有一个不存在,那么直接continue
否则均存在,在原图中两次调用target方法寻找最大权重的路径。如果该路径不存在,那么直接continue;否则选择最大权重路径中间经过的那个点对应的字符串插入。继续循环。

最后将结果送入结果字符串中。根据规约,除了最后一个字符外,每个字符之后需要多打印一个空格。
全部写好后,运行测试,测试结果如图所示:

3.1.5.3Graph poetry slam
选取了一段莎士比亚语料。输入了一个句子,扩充后如图所示:

3.1.6Before you’re done
请按照http://web.mit.edu/6.031/www/sp17/psets/ps2/#before_youre_done的说明,检查你的程序。
如何通过Git提交当前版本到GitHub上你的Lab2仓库。

执行下列三条命令:
git add .
git commit -m”…”
git push
在这里给出你的项目的目录结构树状示意图。
如图所示:

3.2Re-implement the Social Network in Lab1
本任务通过使用上一任务编写的接口实现一个社交网络。
3.2.1FriendshipGraph类
首先撰写该类的规约:

再书写RI,AF,防止表示泄露的措施:

实现addvertex方法:
如果人物存在,那么输出一条信息,终止程序的运行。
如果人物不存在,则调用add方法加入图中。

实现addedge方法:
因为后面想要返回两个人之间的距离,所以权重为1.还要首先判断输入的两个人是否再图中存在。
判断是否存在后,调用set方法设置边即可。

检测表示不变性:无;(父类已经确保唯一性)
实现getdistance方法:首先还是特判人物不存在的情况和人物相等的情况。再通过和Lab1一致的广度优先搜索的方式进行。所不同的地方在于获取一个点发出的边和目标点可以调用父类的target方法来实现。

3.2.2Person类
因为要保证人物的唯一性,所以必须要重写person类的.equals方法。名字相同则认为两个人物是相同的。

其次,只含有一个不可变的,私有的,final的名字字符串防止了表示泄露。

同时还需要重写tostring方法保证对象能够得到正确表示。

3.2.3客户端main()
执行和lab1一致的客户端,输出结果与预期是一致的。

注释掉lab1中要求的第10行代码后,预期也是满足的。

最后,按照lab1的要求,将ross改为rachel结果与预期一致。

3.2.4测试用例
测试策略和lab1是一致的。
测试addvertex:添加三个顶点人物, 判断社交图的顶点是否包含这三个顶点人物。
测试addedge:添加三个顶点人物和几条边,判断每个顶点的邻接表中的顶点(集合)是否与预期的一致。
测试getdistance:建立如图所示的社交网络:分别测试每对顶点的距离是否符合预期,并测试相同顶点间的距离是否为0;

3.2.5提交至Git仓库
如何通过Git提交当前版本到GitHub上你的Lab3仓库。
在这里给出你的项目的目录结构树状示意图。
执行以下三条指令:
git add .
git commit -m”…”
git push
树状结构图如图所示:

4实验进度记录
请使用表格方式记录你的进度情况,以超过半小时的连续编程时间为一行。
每次结束编程时,请向该表格中增加一行。不要事后胡乱填写。
不要嫌烦,该表格可帮助你汇总你在每个任务上付出的时间和精力,发现自己不擅长的任务,后续有意识的弥补。
日期 时间段 计划任务 实际完成情况
6月1日 18:30-22:30 完成泛型图的测试 由于对于set方法的测试过于复杂,未按照完成
6月2日 19:00-21:00 完成泛型图的测试,撰写相关实验报告 完成
6月3日 18:00-22:30 完成第一个graph的具体类并完成测试 对于set方法屡次没有通过测试用例
6月4日 18:00-20:00 完成第一个graph的具体类并完成测试 完成
6月5日 14:00-18:00 完成第二个graph的具体类并完成测试 由于有实现上一个类的经验,这个具体类做起来快多了。
6月6日 18:30-22:30 完成poetwalks的测试 对规约的把握花费了较长的时间,但还是完成了
6月7日 11:00-14:00 完成poetwalks剩下部分并重新实现friendship类 完成
5实验过程中遇到的困难与解决途径
遇到的难点 解决途径
继承graph的子类friendshipgraph中写了checkrep方法。父类中也有checkrep方法。调试程序,出现堆栈溢出。

调试跟踪,即使子类进入了父类中的checkrep,但是父类中的checkrep调用的确是子类的checkrep,造成冲突!

改进的策略是将子类中的checkrep重命名;
由于不清楚IDEA的目录组织使得类的管理出现混乱;没有import相关的包名

分别将src和test设置为源码根和测试根。并在P1里的每个文件中添加包名。在P2中的每个文件中添加包名。并且import相关的包名;

对于AF,RI的把握非常不到位
花费6-7个小时复习ADT&OOP;
6实验过程中收获的经验、教训、感想
6.1实验过程中收获的经验和教训
本次实验收获丰满。主要针对ADT和OOP的设计积累了大量的经验。在第一阶段中,主要根据泛型Graph实现了两个特定的类是一步一步跟着网站上的要求进行抽象的过程。首先利用一个特定的不可边的类“字符串”的类型具体实现两个graph类,这与我们以往设计的思想类似。接着, 题目带领我们逐步的对这个ADT进行抽象,将字符串泛化成一般的不可变数据类型,这也让我们明白了从具体到抽象的一个过程。
其次,测试优先的软件构造确实是一个非常好的方法。就以前来说,我们都是先写程序然后再进行测试,颠倒了次序。但测试优先的方法会先让我们熟悉各种类,各种方法的规约,根据规约来编写测试用例,在此过程中也加强了自己对于规约的认识,更能够写出bug free的程序。同时,测试用例又反过来检查程序是否完美的实现了规约。这让我认识到了测试用例的重要性。
最后,有关AF和RI以及防止表示泄露的文档记载技巧也在本次实验的过程中,有了这些习惯,相信自己以后更能写出思路更加清晰明了的,错误少的程序。

6.2针对以下方面的感受
(1)面向ADT的编程和直接面向应用场景编程,你体会到二者有何差异:
面向ADT编程更像是先将模块设计好,再向里面填写方法,思路脉络较为清楚。而面向应用场景编程更强调程序处理问题的流程。
(2)使用泛型和不使用泛型的编程,对你来说有何差异?
使用泛型能够使得程序处理不同类型的数据,拥有很好的泛化能力,但是编写程序的抽象层次较高。而如果不适用泛型编程,则更为具体,但是程序能够应用的场景显然较少。
(3)在给出ADT的规约后就开始编写测试用例,优势是什么?你是否能够适应这种测试方式?
能够更加清晰地理解规约,更能够写出bugfree地代码。现在逐渐能够适应这种方式。
(4)P1设计的ADT在多个应用场景下使用,这种复用带来什么好处?
不需要每次编写同样的数据结构,节省了时间,提高了工作效率。
(5)为ADT撰写specification, invariants, RI, AF,时刻注意ADT是否有rep exposure,这些工作的意义是什么?你是否愿意在以后编程中坚持这么做?
使得程序的思路更加清晰准确。并能提高程序的健壮性,时刻保证程序和数据处于一个安全状态,并且我很愿意在今后
(6)关于本实验的工作量、难度、deadline。
本次实验相较于实验一工作量有了较大的提升,但符合实验二的量。难度适中,deadline合适。
(7)《软件构造》课程进展到目前,你对该课程有何体会和建议?
本课程教会我们的不仅仅是编程,更强调我们在程序过程中的一些设计理念,应用这些理念能够使我们的编程更加容易。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值