本次实验训练抽象数据类型(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下载和环境配置
在官网:Java Downloads | Oracle 中国下载
环境配置:
第二步,下载IDEA,并配置环境
第三步,下载并配置JUnit
第四步,下载Git,并学习相关操作
配置过程中遇到的问题和困难:
1..配置JDK时,在17和11同时切换使用存在问题,后来调查了资料使用JAVA_HOME随时切换。
2.Git安装时的具体操作,在网上搜集资料后成功安装。
在这里给出你的GitHub Lab2仓库的URL地址(HIT-Lab2-学号)。
https://github.com/ComputerScienceHIT/HIT-Lab2-2022111787.git
请仔细对照实验手册,针对三个问题中的每一项任务,在下面各节中记录你的实验过程、阐述你的设计思路和问题求解思路,可辅之以示意图或关键源代码加以说明(但千万不要把你的源代码全部粘贴过来!)。
在这里简要概述你对该任务的理解。
Java集合框架提供了许多有用的数据结构来处理对象集合:列表、映射、队列、集合等。它不提供图形数据结构。让我们实现图形,然后使用它们来创造无与伦比的光彩的永恒艺术。
如何从GitHub获取该任务的代码、在本地创建Git仓库、使用Git管理本地开发。
对项目右键打开open Git Bash here,用git clone从远程仓库导到本地仓库
修改文件格式后上传到远程仓库
1. git init
2. git add .(文件name)
3. git commit -m "first commit"
4. git remote add origin 远程仓库地址
5. git pull origin master --allow-unrelated-histories
6. git push -u origin master
-
-
- Problem 1: Test Graph <String>
-
以下各部分,请按照MIT页面上相应部分的要求,逐项列出你的设计和实现思路/过程/结果。
这一步是测试带有String类型顶点标签的图。
- 对于静态方法GraphStaticTest.java,只有一个实现,只需要运行一次这些测试,已经被提供,所以可以保持原样。
- 在GraphInstanceTest.java中,使用emptyInstance()方法创建一个空图,并对Graph接口中每个方法使用等价类划分的方法。
- 将图划分为空图与非空图
- 根据顶点分为“已经存在于图中”,“未存在于图中”两组。
- 根据边分为“weight为0”和“weight为正整数”两组。
- 测试方法的实现调用Graph接口提供的add, set, remove, vertices, sources, targets等方法充分覆盖对输入集的划分。
Problem 2: Implement Graph <String>
以下各部分,请按照MIT页面上相应部分的要求,逐项列出你的设计和实现思路/过程/结果。
-
-
-
- Implement ConcreteEdgesGraph
-
-
对于ConcreteEdgesGraph我们必须使用的rep是
对于AF,ConcreteEdgesGraph为一个带权有向图,其中每个顶点的标签为非空字符串且两两不等;每条有向边两个端点不等;每条有向边具有正整数权值。
AF,Ri如下图:
对于需要实现的函数如下:
- add函数:
如果点集中含有这个点,返回false,没有就将该点加入点集
- set函数:
若weight不为0,当存在(source, target)这条边时,将其边权修改为weight,返回值为之前的权值;否则当这条边不存在,加入这条边,边权为weight,返回0。若weight为0,当存在这条边时,删除这条边;否则什么也不做。
- remove函数:
删除点集中的该点及其对应的边
- set函数:
返回vertices的副本,防止泄露
- sources函数:
创建Map,遍历边集,将所有能到达target的点及其权值加入Map
- targets函数:
创建Map,遍历边集,将所有source能到达的点及其权值加入Map
- toString函数:
打印出图
-
-
-
- Implement ConcreteVerticesGraph
-
-
对于ConcreteEdgesGraph我们必须使用的rep是
对于AF,Ri如下图:
在Vertex类中加入label作为邻接表的表头,targets作为出边集合
对于需要实现的函数如下:
- add函数:
如果点集中含有这个点,返回false,没有就将该点加入点集
- set函数:
若weight不为0,当存在(source, target)这条边时,将其边权修改为weight,返回值为之前的权值;否则当这条边不存在,加入这条边,边权为weight,返回0。若weight为0,当存在这条边时,删除这条边;否则什么也不做。
- remove函数:
删除点集中的该点及其对应的边
- set函数:
返回vertices的副本,防止泄露
- sources函数:
创建Map,遍历点集,找到所有出边集合中含有target的表头,并连同weight加入map
- targets函数:
创建Map,遍历点集,找到与source相等的对应表头,使得map=ver.getTargets()
- toString函数:
打印出图
对于两个图的实现方法的测试除toString外为公共测试,在GraphInstanceTest中实现,testtoString在两个函数中分别实现
只需要将作为图标签的String替换为<L>,.Edge和Vertex也应该替换为泛型Edge<L>和Vertex<L>
-
-
-
- Implement Graph.empty()
-
-
对empty函数进行修改:
-
-
- Problem 4: Poetic walks
- Test GraphPoet
- Problem 4: Poetic walks
-
Testing strategy如图所
-
-
-
- Implement GraphPoet
-
-
对应的AF,Ri如图
在GraphPoet中使用BufferedReader的readline方法循环读取文本,并用split函数分割用“ ”相隔开的单词,将其保存到words数组中,保存到图,并计算出weight.
在poem函数中将输入的语句用相同的办法分割,并求出weight最大的bridge.
重写的main如图:
输出结果为:
-
-
- 使用Eclemma/Code Coverage for Java检查测试的代码覆盖
-
请按照Problem Set 2: Poetic Walks的说明,检查你的程序。
如何通过Git提交当前版本到GitHub上你的Lab2仓库。
在这里给出你的项目的目录结构树状示意图。
基于在 3.1 节 Poetic Walks 中定义的 Graph<L>及其两种实现,重新实现 Lab1 中 3.3 节的 FriendshipGraph 类。
使用3.1中的两个类来建图
- addVertex函数:
将点加入graph中
- addEdge函数:
判断图中是否有两个点。若有,将边加入图,反之给出错误提示并退出
- getDistance函数:
使用广度优先算法
与实验1的设计一致
-
-
- 客户端main()
-
与实验1的设计一致
给出你的设计和实现思路/过程/结果。
分别对addVertex,addEdge,getDistance进行测试,使用assertEquals断言来进行测试,和实验1一致。
-
-
- 提交至Git仓库
-
如何通过Git提交当前版本到GitHub上你的Lab3仓库。
在这里给出你的项目的目录结构树状示意图。
使用表格方式记录你的进度情况,以超过半小时的连续编程时间为一行。
每次结束编程时,请向该表格中增加一行。不要事后胡乱填写。
不要嫌烦,该表格可帮助你汇总你在每个任务上付出的时间和精力,发现自己不擅长的任务,后续有意识的弥补。
日期 | 时间段 | 计划任务 | 实际完成情况 |
2024.4.13 | 20:00-20:45 | 简单了解实验要求,并对对应的知识点进行了充分的复习 | 按时完成 |
2024.4.14 | 10:00-12:00 | 完成3.1的Problem1及对应report | 按时完成 |
2024.4.15 | 18:00-24:00 | 完成3.1的Problem2及对应report | 按时完成 |
2024.4.16 | 18:00-22:00 | 完成3.1的Problem3, Problem4及对应report | 按时完成 |
2024.4.17 | 18:00-24:00 | 完成3.2及对应report | 按时完成 |
2024.4.18 | 17:30-20:00 | 完善report | 按时完成 |
遇到的难点 | 解决途径 |
对checkRep的概念不理解 | 在CSDN查阅资料学会 |
对图的empty调用报错 | 发现读题理解有误,应使用emptyInstance |
在ConcreteEdgesGraph中设计toString时,使用TreeSet,让其有序,但是在ConcreteVerticesGraph中未果,同时程序报错StringIndexOutOfBoundsException | 查阅资料后发现对StringBuilder类型的长度length()和删除最后一个字符放在一个语句,产生歧义。 |
在FriendshipGraph中使用广度优先算法求最小距离时,程序出现死循环 | 发现是对Set类型的判断为空语句有误 |
学会了ADT的设计规约、测试,并使用面向对象编程(OOP)技术实现 ADT。了解到了一些编程语言技巧。
-
- 针对以下方面的感受(必答)
- 面向ADT的编程和直接面向应用场景编程,你体会到二者有何差异?
面向ADT的编程:这种方法更加抽象和通用,关注于数据的结构和操作,而不是具体的应用场景。由于ADT是通用的,所以可以被多个应用或场景重用。可维护性更好
直接面向应用场景的编程:这种方法更具体和实际,你会直接解决特定的问题或实现特定的功能。可能会导致代码紧密耦合于特定的应用场景,降低了代码的重用性和灵活性。
- 用泛型和不使用泛型的编程,对你来说有何差异?
使用泛型提高了代码的复用性,类型安全性、清晰度和可读性,同时也能帮助在编译时捕获错误。而不使用泛型可能会导致代码冗余、类型不安全、难以维护和调试等问题。
- 在给出ADT的规约后就开始编写测试用例,优势是什么?你是否能够适应这种测试方式?
规约定义了ADT的预期行为和属性,这为测试提供了明确的目标。编写测试用例可以帮助我好地设计ADT的实现。通过考虑如何测试ADT,我更清楚地了解哪些功能需要实现,以及如何实现它们。适应起来不如想象中的容易,但了解了他的好处后,我意识到应该努力适应,
- P1设计的ADT在多个应用场景下使用,这种复用带来什么好处?
减少了代码的冗杂,提高了代码的重用性,降低了代码的维护成本
- 为ADT撰写specification, invariants, RI, AF,时刻注意ADT是否有rep exposure,这些工作的意义是什么?你是否愿意在以后编程中坚持这么做?
确保ADT的正确性:规约:定义了ADT的操作和行为,为实现提供了明指导。不变式:描述了在ADT的生命周期内始终保持不变的属性或条件,帮助确保ADT的正确性。表示不变量(RI):指定了表示(rep)必须满足的条件,确保内部表示的有效性。抽象函数(AF):描述了如何从ADT的表示到其抽象值的映射,确保ADT与其抽象值之间的关系清晰明确。
防止rep exposure:通过控制对表示的直接访问,可以避免rep exposure,从而防止不合法或不一致的状态。这有助于保护ADT的内部表示,确保它不被外部代码意外或恶意地修改。
为了以上的有利点,我愿意在以后编程中坚持这么做。
- 关于本实验的工作量、难度、deadline。
本次实验工作量适中,有些难度,我的deadline是在4.18
- 《软件构造》课程进展到目前,你对该课程有何收获和建议?
本课程教我们如何规避一些不必要的麻烦,开发出高质量的软件,也让我们与其他程序员之间建立起规约,可以与其他人进行更好地交流