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.2 Implement GraphPoet· 2
根据实验手册简要撰写。
本次实验训练抽象数据类型(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并据此设计测试用例。
简要陈述你配置本次实验所需环境的过程,必要时可以给出屏幕截图。
特别是要记录配置过程中遇到的问题和困难,以及如何解决的。
有了第一次实验的配置之后,第二次就是轻车熟路了。按照链接下载对应的实验文件,新建对应的github库并修改其名称使其符合规定。之后用Github Desktop软件创造其本地副本,就大功告成了。
在这里给出你的GitHub Lab2仓库的URL地址(Lab2-学号)。
https://github.com/ComputerScienceHIT/HIT-Lab2-120L021816
请仔细对照实验手册,针对两个问题中的每一项任务,在下面各节中记录你的实验过程、阐述你的设计思路和问题求解思路,可辅之以示意图或关键源代码加以说明(但千万不要把你的源代码全部粘贴过来!)。
在这里简要概述你对该任务的理解。
该任务从我们熟悉的有向图构建出发,先后进行了编写测试类,编写实现类,编写Poet相关信息散三个步骤。时间紧任务重。让参与这次实验的学生真正了解到了实际情况下编程的实感。同时,这个实验的难度却说不上非常大,在底层实现部分并没有新的内容,这也是为了学生的能力进行的考虑:在保证任务量的情况下适当调整任务的难度,从而到达最佳效果。
如何从GitHub获取该任务的代码、在本地创建git仓库、使用git管理本地开发。
按照图中所示的网络连接来到网页,以压缩文件形式下载代码,下载完成后就获得了该任务的代码。在接受任务之后,就会自动新建一个库。此时打开Github Desktop就会自动出现是否在本地建库的提示,点击确定红柱子后就可以在本地建库了。使用git管理本地开发也是完完全全借助Github Desktop这个在64位win10系统下的外国软件完成的。
-
-
- Problem 1: Test Graph <String>
-
以下各部分,请按照MIT页面上相应部分的要求,逐项列出你的设计和实现思路/过程/结果。
按照MIT页面上相应部分的要求,本部分是要编写代码的GraphStaticTest和GraphInstanceTest两个类,让它们在结合Problem2中两种具体的图的代码之后,能够产生正确的测试结果。其中需要编写的一共有7个函数:add,set,remove,vertices,source,target以及empty函数。同时要注意添加初始测试函数testAssertionAbled。本阶段难度较简单,只需要根据函数上方的规约来编写函数即可。需要注意的是由于我们只编写了测试函数而没有编写Graph类的函数本身,所以尚不能通过系统运行的方法检测编写代码的正确性。
对其中每个函数的分析留到3.1.3节进行,而empty函数留在3.1.4.2节
-
-
- Problem 2: Implement Graph <String>
-
以下各部分,请按照MIT页面上相应部分的要求,逐项列出你的设计和实现思路/过程/结果。
这一部分是整个P1甚至于整个实验2的主要内容,虽然难度不是特别大,但对每一个函数甚至于每一个规约都是需要经过一些思考的。而同时,这个作业分解需要编写的代码量和需要书写的规约量都是相当之大。总体来说是十分具有挑战性的一次作业。
-
-
-
- Implement ConcreteEdgesGraph
-
-
ConcreteEdgesGraph函数:本节的构建函数之一,在初始的代码文件中已经给出。其中vertices用于存放String类型的顶点,而edges用于存放边,边的具体方法由编写者自己确定。
checkRep函数:由题意,本实验所编写的图可以由环、可以有自环,但是不能有相同名称的顶点,因此针对这点在函数中我们需要比较所有的顶点对查看它们的名称是否相等。
add函数:由于图中不能有相同名称的顶点,因此在加入顶点时应当首先考量图中是不是已经有了这个顶点,如果有不需要再添加,并且按照要求返回false,反之则返回true。
set函数:set函数无论是在ConcreteEdgesGraph里还是在ConcreteVerticesGraph里都是大量代码的重头戏,其中涉及到对边权值为负的排除,对顶点集中没有添加边的起点和终点情况的处理,对根据顶点名称寻找顶点集中对应顶点,并考察对应顶点是否有边连接的方法的处理。以及如何能满足所有条件地改变边的权值。测试类需要对所有情况都进行测试。
Remove函数:Remove函数和add函数一样,也需要考察待删除的顶点到底在不在顶点集中,但是Remove函数的内容却不仅仅只有这些:Remove函数还需要删除边集里所有和被删除顶点关联的边,这就需要遍历边集,并且对每一条边都进行考察。
vertices函数:直接将顶点列表返回即可。注意防御式拷贝。
sources函数/targets函数:这两个函数原理其实相同,给定一个顶点,求出所有以这个顶点为起点的所有边的终点/以这个顶点为终点的所有边的起点为键,对应边的权值为值的Map型键值对。其难度在于对于Map类下方法的掌握。同时注意防御式拷贝。
toString函数:将图以某种字符串的形式进行表示,这个问题在以边集为核心的图中较为简单,只需要打印边集即可。
Edge函数:本节的构建函数之二。通过分析得出这个模型中的每一条边都应该有三个构成元素:这条边的起点(源点),这条边的终点(汇点),以及这条边的权重。
checkRep函数:这个函数是对Edge类及其下述方法的一个返回值检测。根据题意,每条边的起点和终点都必须非空,边的权值也必须要大于零。然而,在之后的set函数中,规约明确提到了边权值为0时的特殊处理,因此本函数需要涵盖边权值为0的特殊情况。
getSource/getTarget/getWeight函数:这三个函数都是用来获得Edge类变量的内部信息的。由于之前将Edge的三个下属量都设置成了private类型,我们无法在Edge之外的类里直接获取它们,所以要额外设计函数。这些函数结构极其简单,但是也不要忘了加上checkRep。由于Edge被作业题干要求设定为不变量,因此不用防御式拷贝。
toString函数:这个函数是将每条边所含的全部信息转化成字符串进行输出的函数,由于Edge这个量本身结构简单,不像后面将要提到的Vertex一样有图,所以在思维结构上相对简单,只需要用字符串将所有下属量连接起来就可以了。同时注意要有@override标志。
-
-
-
- Implement ConcreteVerticesGraph
-
-
ConcreteVerticesGraph函数:与ConcreteEdgesGraph函数相比,ConcreteVerticesGraph的数据结构相对简单,不过对应的结果就是函数书写较为复杂。
checkRep函数:这一部分与前一节相同。
add函数:由于本子类的顶点并不直接是字符串,而是顶点下的一个子类是用于区分顶点的字符串,因此在考察时应该从顶点那里取出字符串进行比较。
set函数:在上一节set函数的每一部分判定中,都要用到顶点的名称,这也造成了这个函数代码量的巨幅膨胀。
remove函数:由于边和顶点相关,因此我们在删除顶点时,就可以顺带删拿出一部分边,然而,其他顶点类下的边仍需要遍历一遍来删除。
vertices函数:直接返回由顶点名称组成的集合即可(需要自己创建),注意防御式拷贝。
sources/targets函数:与前一节不同,以顶点集为核心的图只需要找到对应顶点,就可以拿到所有以这个顶点为起点/终点的边了。注意防御式拷贝。
toString函数:同样也可以用边来表示,不过这次要在sources函数和targets函数里二选一,否则会有重复。
Vertex:生成算法,需要用户提供要生成的顶点名称,以判断是否和已有的顶点名称重复。同时,其他函数也会多次用到顶点名称。
checkRep函数:相比于边的checkRep,顶点的checkRep竟然相对简单一些,只需要检查名称是否为空就可以了。
getName/getSource/getTarget函数:与Edge类的函数不同,这些函数需要防御式拷贝,这是由于Vertex是mutable的,而Edge是immutable的。
addSource/addTarget/removeSource/removeTarget:由于防御式拷贝,通过get系列函数得到的其实只是起点集和终点集的副本,因此就需要额外的函数对起点集终点集进行增加和删改工作。
toString函数:直接以字符串形式输出顶点即可。
这一步较为简单,只需要将之前除了toString函数返回值的所有String都替换成L即可,完全可以用Ctrl+F的查找替换功能完成。
-
-
-
- Implement Graph.empty()
-
-
这一步需要考察GraphStaticTest,并将里面的testEmptyVerticesEmpty方法测试成功,同时不改变这个函数本身的内容。这就需要我们找到Graph接口里的empty函数,并且返回一个没有顶点但形式合适的图。
-
-
- Problem 4: Poetic walks
- Test GraphPoet
- Problem 4: Poetic walks
-
和之前的问题一样,这一部分也是首先编写测试类,其次再编写实现函数。在编写测试类时应该注意到重复单词、单词大小写、重复词组的情况,并注意到按照题干所述,真实过程是不区分标点符号的。
-
-
-
- Implement GraphPoet
-
-
实现GraphPoet具体分为两部分:一部分是由文件里的句子生成诗人图,另一部分是根据输入的句子补全诗句。其中第一部分是一个在特定情况下有向图的建立,需要从前向后遍历每个词汇并且进行创建边/增加边的权值的操作。而第二各部分是要在从前向后遍历的过程中对每一个单词判断是否有中间单词可以插入。同时注意题干要求,是忽视大小写差别的比较。
这其实就是完成主函数,相当于换一种形式写测试类。
请按照Problem Set 2: Poetic Walks的说明,检查你的程序。
如何通过Git提交当前版本到GitHub上你的Lab2仓库。
在这里给出你的项目的目录结构树状示意图。
在这里简要概述你对该任务的理解。
这其实是P1的简化版,难度不高,所耗时间也不长。但要是在Lab1中没有好好去写这部分的话会消耗相对较长的时间。
给出你的设计和实现思路/过程/结果。
原来的框架基本不变,只是按照ConcreteVerticesGraph的形式复制黏贴一下,其中addVertex函数和addEdge函数甚至只需要<L>类型到<Person>类型的转换。而getDistance函数只需要按照Lab1的结构稍加改动即可。
给出你的设计和实现思路/过程/结果。
如果Lab1做得够好的话,这部分根本不用修改。
-
-
- 客户端main()
-
给出你的设计和实现思路/过程/结果。
如果Lab1做得够好的话,这部分根本不用修改。
给出你的设计和实现思路/过程/结果,覆盖度报告。
需要考虑到相同顶点、无friendship关系的顶点,间接连接的顶点,直接连接的顶点。
其中Friendship类中没有覆盖的部分均为main函数,在测试类中根本不能覆盖。因此可以说本次测试是百分百覆盖了。
-
-
- 提交至Git仓库
-
如何通过Git提交当前版本到GitHub上你的Lab2仓库。
在这里给出你的项目的目录结构树状示意图。
请使用表格方式记录你的进度情况,以超过半小时的连续编程时间为一行。
每次结束编程时,请向该表格中增加一行。不要事后胡乱填写。
不要嫌烦,该表格可帮助你汇总你在每个任务上付出的时间和精力,发现自己不擅长的任务,后续有意识的弥补。
日期 | 时间段 | 计划任务 | 实际完成情况 |
2022.5.10 | 14:31-15:06 | 下载相关文件,阅读大致要求,填写实验环境、实验目的等基础信息 | 按时完成 |
2022.5.10 | 15:07-15:13 | 尝试编写Problem2 | 等到一切都准备好的时候发现Problem2需要Problem1的内容,遂改变目标 |
2022.5.10 | 15:13-15:48 | 尝试编写Problem1 | 仔细阅读内容Overview,Get the code,并下载相关文件,编写顶点增加类 |
2022.5.12 | 16:38-18:38 | 尝试编写ConcreteEdgesGraph | 完成主要任务,但并未编写规约、防御式编程部分。 |
2022.5.12 | 18:39-22:11 | 尝试编写ConcreteVerticesGraph | 连主要任务都没有完成 |
2022.5.13 | 22:12-23:47 | 完成concreteEdgesGraphTest | 正常完成 |
2022.5.14 | 9:45-10:56 | 完成P1的第二个问题的边部分(即把5.12号工作收尾) | 正常完成,但还是没有写防御式编程。 |
2022.5.14 | 11:00-12:01 | 完成P1的3.1 | 正常完成 |
2022.5.14 | 14:12-17:04 | 完成P2 | 正常完成 |
2022.5.14 | 17:04-17:52 | 解决所有现在涉及到的文件里的警告问题 | 正常完成 |
2022.5.14 | 21:58-22:14 | 解决所有现在涉及到的文件的防御式编程问题 | 正常完成 |
2022.5.14 | 22:14-23:46 | 解决P1的4.1 | 正常完成 |
2022.5.14 | 23:46-0:42 | 解决P1的4.2,4.3 | 正常完成 |
2022.5.15 | 8:15-10:16 | 写报告 | 正常完成 |
遇到的难点 | 解决途径 |
不知道该如何写规约 | 查看老师上课的ppt,复习相关知识。 |
出现过多警告,很难达到完全消除警告的目的 | 和同学交流,在同学给出大致方法之后,自己成功消除了绝大部分警告 |
对于L类型的防御式拷贝不知道如何书写 | 和老师进行长时间的交流,最终发现并不用书写L类型的防御式拷贝,因为L是不可变类型。 |
应该好好听课,遇到的问题老师课上都讲过。
应该好好听课,遇到的问题老师课上都讲过。
应该好好听课,遇到的问题老师课上都讲过。
- 面向ADT的编程和直接面向应用场景编程,你体会到二者有何差异?
目的是不一样的,所以具体使用的方法,具体的侧重点也不一样。比如我在这次实验里就编出了应用型极差的,面向ADT的编程
- 使用泛型和不使用泛型的编程,对你来说有何差异?
使用泛型的更抽象一些,不好理解。不过只要编写好了还是比较轻松的。但是还是看着不习惯。下次我估计还是会自发地不使用泛型。
- 在给出ADT的规约后就开始编写测试用例,优势是什么?你是否能够适应这种测试方式?
先进行规约在编写测试用例,最后再写具体的函数,能够增加具体实现函数的正确性,减少出错的可能。但从我个人角度来说,我还是更愿意先写实现函数、后写测试类。
- P1设计的ADT在多个应用场景下使用,这种复用带来什么好处?
方便,不用再额外编写代码了。
- 为ADT撰写specification, invariants, RI, AF,时刻注意ADT是否有rep exposure,这些工作的意义是什么?你是否愿意在以后编程中坚持这么做?
这些操作最大的目的,就是为了以后某天再次使用这些代码时,能快速看懂这些代码的含义。同时对其他编程者和用户也是一个不小的帮助。这确实是一个应该坚持的好习惯。
- 关于本实验的工作量、难度、deadline。
工作量偏大,但是可以接受。难度适中,deadline完全可以。没什么意见。
- 《软件构造》课程进展到目前,你对该课程有何体会和建议?
应该好好听课。应该好好听课。应该好好听课。