3.1.1 Get the code and prepare Git repository
3.1.2 Problem 1: Test Graph <String>
3.1.3 Problem 2: Implement Graph <String>
3.1.3.1 Implement ConcreteEdgesGraph
3.1.3.2 Implement ConcreteVerticesGraph
3.1.4 Problem 3: Implement generic Graph<L>
3.1.4.1 Make the implementations generic
3.1.4.2 Implement Graph.empty()
3.2 Re-implement the Social Network in Lab1
1实验目标概述
本次实验训练抽象数据类型(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 并据此设计测试用例。
2实验环境配置
使用的IDE为IntelliJ IDEA,JDK为版本11.0.22
Build system 是Intellij.
3实验过程
3.1Poetic Walks
在该问题中,将实现抽象数据类型Graph,用于带有标记顶点的可变加权有向图。完成Graph接口的两实现个类ConcreteEdgesGraph,ConcreteVerticesGraph ,实现Graph数据类型。两个类都实现Graph接口中的功能,但是实现的方法不同,ConcreteVerticesGraph保存点集和与点集中元素相连的边及和权值,ConcreteEdgesGraph保存点对,起点和终点及对应权值。各自实现功能,并设计两个方法的测试。
最后应用该ADT。
3.1.1Get the code and prepare Git repository
链接仓库,在Git中选择Manage Remotes...中添加url,同时pull内容,就会将老师仓库中的内容拉取到本地中,整理放到相应的目录下面。
3.1.2Problem 1: Test Graph <String>
首先要明确,各个接口中函数的功能,再根据具体的功能,划分出客户端可能的输入,对各种情况进行结果的设计和测试类的设计。
add:加入一个点,返回布尔值(加入成功->True,不成功->False)
set:添加、更改、删除图中的加权有向边。(源点,目标,权重)
两个点都在图中,新权值不为零,将这个两点之间的边置为传入权值
两个点有不在图中的,先将不在图中的添加进图中。再更改权值。
两个点都在图中,新权值为零,删去两点之间的边。
两个点有不在图中的,新权值为零,仅加入不在的点。
remove:去除图中一个点,以及所有与该点相连的边。(该点在图中->True,不在->False)
vertices:得到图中所有点。
sources:选定源点,得到它和它相邻所有边的权重。(返回键值对)
target:从源点到某点的边及权重。
在进行具体设计时,要尽量保证各种输入情况都照应到。
3.1.3Problem 2: Implement Graph <String>
3.1.3.1Implement ConcreteEdgesGraph
Edge类:
一条边包含起点、终点以及边权。
构造方法,能返回起点、终点以及边权。同时设计toString。
checkRep(),source和target均不为空,满足边权要非负。
ConcreteEdgesGraph:
CheckRep(),集合内的点不能有重名,判断地址不同时,name一不一样。
add方法:把点加入进来,直接在点集中添加该点。(点已经存在时返回错误)
set方法:在edge中加入(源点,终点,权值)的对。权值为零时可表示不加入边/仅加入点/删除存在的边。
remove方法:删除点集中的点,和edge表中source和target与删除点匹配上的所有的边。
vertices方法:直接返回点集。
sources方法:寻找入边,对应边表中target集与传入target一致的边,写入map。
targets方法:寻找出边,对应边表中source集与传入target一致的边,写入map。
3.1.3.2Implement ConcreteVerticesGraph
Vertex类:
点和与点相连的终点与权值的键值对。
构造方法,能返回起点、终点以及边权,能加入出边。同时设计toString。
checkRep();方法,满足点的标签不空。
ConcreteVerticesGraph:
checkRep(),集合内的点不能有重名,判断地址不同时,label一不一样。
add方法:把点加入进来,在链表中新建一个点,并未标签命名。(点已经存在时返回错误)
set方法:在Vertex中加入(终点,权值)的对。权值为零时可表示不加入边/仅加入点/删除存在的边。
remove方法:删除链表中label匹配的的Vertex(包括表示出边的map),和遍历其他点的出边图与label匹配上的所有label的入边。
vertices方法:直接返回点Vertex集的label集和。
sources方法:寻找入边,循环在全部出边表的终点中找到target,并对应权值,写入map中,即为target的入边。
targets方法:寻找出边,直接对应Vertex集的label与传入source一致的点对,写入map。
3.1.4Problem 3: Implement generic Graph<L>
对具体实现代码的进行泛化,得到使用抽象类<L>的实现。只需要将作为图标签的String替换为<L>.Edge和Vertex也应该替换为泛型。
3.1.4.1Make the implementations generic
泛化后的代码,再次运行测试类。仍旧能够通过测试。
测试成功。
3.1.4.2Implement Graph.empty()
empty()使用ConcreteEdgesGraph类
测试:
3.1.5Problem 4: Poetic walks
3.1.5.1Test GraphPoet
Testing strategy
GraphPoet:
训练文本和输入文本,然后根据训练文本和设计文本的内容来推断我们的诗歌扩写之后应该是什么样子,之后再输出。
1.输入文件为空/不为空
poem:
1. 输入的字符串为null/不为null
2.只有一个字符/不止一个字符
toString:
1.输入的graph不为空,但没有边
2. 输入的graph有点又有边
3. 输入的graph为空
GraphPoet:
空文件做语料库,会导致原来的句子不能扩写。
单个input或空白input,都会导致不能扩写。
形成的图,会因为输入文件的内容出现,有点有边、有点无边、无点无边的情况。
3.1.5.2Implement GraphPoet
GraphPoet:通过语料库中的句子、短语,形成一张有向带权图,用于扩写句子。
利用ADT中的功能,把逐句分割的文章转化成图,不断地添加边进入。
getCountBetween:得到两个点之间的边权。(辅助函数)
getBridgeWord:得到连个点之间的中间点。(辅助函数)
Poem:根据输入,逐一检查每两个词之间是否有中间词,想实现扩写。
3.1.5.3Graph poetry slam
实现扩写。
3.1.6使用Eclemma/Code Coverage for Java检查测试的代码覆盖度
graph部分:
GraphPoet部分:
Main函数部分只实现操作一个句子扩写,故测试不包含。
3.1.7Before you’re done
直接使用IDEA中的git工具栏,将commit全部push。
3.2Re-implement the Social Network in Lab1
设计实现一张社交关系网络图,并编写一个计算人机关系“距离”的函数。网络图基于两个类,分别是FriendshipGraph类和Person类。训练对于图的理解和使用,要注意关注有向还是无向FriendshipGraph类。
较于实验一,着重对于ADT的应用,尽量对graph功能的复用。实现简化。
3.2.1Person类
满足使用Person的各种要求,存储人名。在判断人是否出现过时,需要getName和isSameName。一个获得要添加的Person的名字,一个和原有的作比较。
重写tostring.
3.2.2客户端main()
测试重名现象。
3.2.3测试用例
除了重复人名Person的添加是在mian()中进行测试,其他,两人之间没有路、两人直接有联系,两人间接联系、自己与自己的联系,都在测试类中进行测试。
3.2.4提交至Git仓库
直接使用IDEA中的git工具栏,将commit全部push。
4实验进度记录
请使用表格方式记录你的进度情况,以超过半小时的连续编程时间为一行。
日期 | 时间段 | 计划任务 | 实际完成情况 |
2024-4.14 | 22:00-1:00 | 了解任务一的具体要求,尝试进行求解.了解接口的功能和测试的要求,对GraphInstanceTest中的测试方法进行部分编写 | 完成 |
2024-4.16 | 0:00-3:00 | 完成测试类的编写 | 完成部分 |
2024-4.17 | 20:00-3:00(次日) | 完成两种具体实现 | 延迟次日5:00完成部分,仍有bug |
2024-4.18 | 18:00-20:30 | 解决实现的bug | 按时完成 |
2024-4.18 | 21:30-23:00 | 实现泛化、和类的测试 | 按时完成 |
2024-4.18 | 23:00-00:30(次日) | 实现poet部分 | 完成部分,测试覆盖仍有问题 |
2024-4.19 | 19:30-23:00 | 实现person部分问题 | 按时完成 |
2024-4.20 | 13:00-15:30 | 实现报告的编写与发现解决bug | 按时完成 |
5实验过程中遇到的困难与解决途径
遇到的难点 | 解决途径 |
在测试类中构建的图,每次进行单个功能的测试时,还要重复之前的部分测试功能,以建立一个便于测试的图 | 可以直接调用方法,建立测试用的图,不需要每次使用assertEquals。 |
6实验过程中收获的经验、教训、感想
6.1实验过程中收获的经验和教训(必答)
- 全面的进行测试,在测试中进行代码的完善、迭代。覆盖各种场景和使用情况,确保代码的稳定性和可靠性。在测试过程中,可能会发现之前未考虑到的问题或边界情况,这时需要对代码进行完善和迭代。这种持续的测试、修复和迭代循环有助于提高代码的质量。
- 全面的思考。这包括对需求的深入理解、系统设计的全局考虑、以及对潜在风险和挑战的预见性。比如对与泛型化要考虑全面,不能仅仅只是在形式上改成抽象类型,而实现却只能对一种类型有效。
- 重写tostring的重要性。
6.2针对以下方面的感受(必答)
(1)面向ADT的编程和直接面向应用场景编程,你体会到二者有何差异?
抽象级别: ADT更关注数据和操作的抽象,而直接面向应用的编程更关注具 体应用的实现。
重用性与灵活性: ADT提供更高的代码重用性和灵活性,而直接面向应用的 编程可能更专注于特定应用场景。
实现细节: ADT需要关注数据结构和操作的底层实现,而直接面向应用的编程则可能更关注功能实现。
(2)使用泛型和不使用泛型的编程,对你来说有何差异?
使用泛型进行编程,要考虑的内容比不使用泛型要多很多,一不小心思考不 到位就出现泛型后的功能在面对特定数据结构的情况下不能实现。就向这次 在person具体实现中出现的问题。
(3)在给出ADT的规约后就开始编写测试用例,优势是什么?你是否能够适应这种测试方式?
能更好的了解即将要实现的功能,在具体编写中也可以随时测试看看是否完 善功能,方便规划功能的完成,同时减少遗漏。
能适应。
(4)P1设计的ADT在多个应用场景下使用,这种复用带来什么好处?
减少了代码的重复率,更具简洁性,同时也方便类似场景功能的实现,减少了程序员的工作量。
(5)为ADT撰写specification, invariants, RI, AF,时刻注意ADT是否有rep exposure,这些工作的意义是什么?你是否愿意在以后编程中坚持这么做?
Specification(规约)
意义: 定义ADT的行为和功能,为其他开发者提供了使用ADT的明确指导。
注意事项: 确保规格清晰、准确,描述ADT的主要功能和操作。
Invariants (不变式):
意义: 描述ADT在其生命周期中必须始终保持的属性或条件。
注意事项: 确保不变式在ADT的所有操作中都得到维护,避免破坏ADT的 一致性。
Representation Invariant (RI, 表示不变式):
意义: 定义了数据表示的约束和条件,保证ADT内部数据结构的有效性。
注意事项: 避免rep exposure,即避免直接暴露内部表示给外部,确保数据的 封装性和安全性。
Abstraction Function (AF, 抽象函数):
意义: 描述了ADT的抽象值与其表示的关系,帮助理解ADT的意图和目的。
注意事项: 确保抽象函数清晰、准确,为开发者提供ADT的直观理解。
(6)关于本实验的工作量、难度、deadline。
工作量较大,难度适中,deadline适中。
(7)《软件构造》课程进展到目前,你对该课程有何收获和建议?
收获很大,在练习中逐渐能切身体会一些规范的重要性,以及,规范操作对编写代码、修改bug的帮助。