软件构造——Lab2实验总结

一. Poetic Walks

本问题给出了一个Graph接口,要求我们秉承测试驱动开发的思想,分别建立继承自graph接口的ConcreteEdgesGraph类、ConcreteVerticesGraph类,并且在类中设计规约并具体实现操作graph的一系列方法。另外,我们需要在类中具体实现Edge和Vertex数据型,并设计其spec、RI、AF以及Safety from rep exposure,在完成之后,用我们设计的图的抽象数据型完成poetic walks的工作。

1. Test Graph < String >

测试策略按等价类划分即可

2. Implement Graph < String >

2.1 Implement ConcreteEdgesGraph

ConcreteEdgesGraph的AF、RI和Safety from rep exposure
在这里插入图片描述
checkRep方法,以RI为判断准则
在这里插入图片描述
ConcreteEdgesGraph方法实现
add方法:
判断加入的点是否已存在,若存在,返回false,若不存在,加入vertices中,且返回true
set方法:
先判断权重是否为0,若不为0,则加入两个点
在这里插入图片描述

遍历所有边,若找到一条边的起始点和终止点分别与source和target对应,则进行权重判断,若权重为0,移除该边,若权重不为0,则更改该边权重
若没有找对应的边且要添加的权重不为0,则添加该边
remove方法:
如果vertices中不包含要移除的点,返回false
若包含,则新建一个边集,遍历原边集,若与边相关的两个点不包含要移除的点,则加入新边集,遍历过后,移除原边集的所有边,加入新边集
vertices方法:
在这里插入图片描述
sources方法:
新建一个Map<String, Integer>,遍历边集,如果边的终点与传入的参数点相等,则将该边的起点和权重加入新Map中
target方法:
新建一个Map<String, Integer>,遍历边集,如果边的起点与传入的参数点相等,则将该边的终点和权重加入新Map中
toString方法:
在这里插入图片描述

edge类的field
在这里插入图片描述

edge类的AF、RI和Safety from rep exposure
在这里插入图片描述
checkRep:
在这里插入图片描述
edge方法的实现:
三个获取属性的方法,不做赘述
equals方法:
若两个边的顶点、终点、权值均相等,则两个边相等
toString方法:
在这里插入图片描述

2.2 Implement ConcreteVerticesGraph

concreteVerticesGraph的AF、RI和Safety from rep exposure
在这里插入图片描述
checkRep方法:
在这里插入图片描述
concreteVerticesGraph方法实现:
add方法:
遍历点集,若点集中存在与要加入的点名字相同的点,返回false,若不存在,则新建一个vertex,加入vertices中
set方法:
如果权重为0,遍历点集,若存在一个或两个参数点不在点集中,则返回0;若两个点均在点集中,则查看source的targets,若包含target,则移除,同理,查看target的sources,若包含source,则移出。
若权重不为0,则先向点集添加两个参数点,然后遍历vertices,找到这两个点,分别进行addSource和addTarget操作
remove方法:
遍历点集,若未找到参数点,则返回false,若找到,则在点集中移除该点,随后遍历点集,对每个点进行removeSource(vertex)和removeTarget(vertex)操作
vertices方法:
遍历点集,将每一个点的name加入要返回的set中
sources方法:
遍历点集,找到参数点,返回其sources属性(采用防御式拷贝的方式返回)
targets方法:
遍历点集,找到参数点,返回其sources属性(采用防御式拷贝的方式返回)
toString方法:
在这里插入图片描述
Vertex类的field
在这里插入图片描述
Vertex类的AF、RI和Safety from rep exposure
在这里插入图片描述
checkRep:
在这里插入图片描述
Vertex的方法实现:
属性的getter方法:
在这里插入图片描述
addSource方法:
在这里插入图片描述
如果权重为0,遍历sources属性,若sources中包含参数点source,则移除该点,返回原权值,若sources中不包含,则返回0
如果权重不为0,遍历sources属性,若sources中包含参数点sources,则重新put该点和新权值,返回原权值,若sources中不包含,则新添该点和新权值,返回0
addTarget方法,道理同上,不再赘述
removeSources方法:
在这里插入图片描述
若sources中包含source参数点,则移除该点,返回原权值,若不包含,则返回0
removeTargets方法,道理同上,不再赘述
equals方法:若参数点的name与该点相等,则视为两点相等
toString方法:
遍历targets,打印出每一个target及对应边的权值

3. Implement generic Graph

3.1 Make the implementations generic

将代码中涉及到的类及属性改为泛型表示,测试用例全部通过为成功

3.2 Implement Graph.empty()

使Graph.empty()方法返回ConcreteEdgesGraph()
在这里插入图片描述
在GraphStaticTest中添加测试用例,以创建和使用 Graph 具有几种不同类型的(不可变)标签的实例

4. Poetic walks

4.1 Test GraphPoet

test for GraphPoet(使用toString方法来测试是否构造出正确的图)
在这里插入图片描述
test for poem
在这里插入图片描述

4.2 Implement GraphPoet

AF、RI、Safety from rep exposure
在这里插入图片描述
GraphPoet方法的实现:
GraphPoet方法:
首先,读取文件内容,并将文件内容按空格分隔存入列表
定义一个Map型变量,键存储图中的一个点,值存储该点指向的目标点和权值组成的键值对的Map集合

Map<String, Map<String, Integer>> wordAdjacency = new HashMap<>();

遍历words列表的单词,建立相邻两单词间的关联,若已建立过关联,则将权值加1

for (int i = 0; i < words.size()-1; i++) {
    String word1 = words.get(i).toLowerCase();
    String word2 = words.get(i+1).toLowerCase();
    wordAdjacency.putIfAbsent(word1, new HashMap<>());
    wordAdjacency.get(word1).put(word2, 			wordAdjacency.get(word1).getOrDefault(word2, 0) + 1);
}

最后将统计结果加入图中

for (Map.Entry<String, Map<String, Integer>> entry : wordAdjacency.entrySet()) {
    String word1 = entry.getKey();
    for (Map.Entry<String, Integer> adj : entry.getValue().entrySet()) {
        String word2 = adj.getKey();
        int weight = adj.getValue();
        graph.add(word1);
        graph.add(word2);
        graph.set(word1, word2, weight);
    }
}

poem方法的实现:
首先还是处理输入,将输入的字符串转换成以空格为分割形式的列表
遍历单词列表,依次处理相邻单词对,在图中找首单词,若找不到,则不添加,跳到下一个循环,若能找到,则获取该点的targets,提取出targets中的点值,继续找每一个点的targets,保留targets中包含尾单词的点,并累加其权值,最后,找出权值最大的中间节点,加入单词对之间。
toString方法的实现:
直接返回graph.toString

4.3 Graph poetry slam

beaytifulPoet.txt

I offer you lean streets, desperate sunsets, the moon of the jagged suburbs.

输出结果
在这里插入图片描述

二. Re-implement the Social Network in Lab1

该问题要求我基于在 3.1 节 Poetic Walks 中定义的 Graph<L>及其两种 实现,重新实现 Lab1 中 3.3 节的 FriendshipGraph 类。 针对 addVertex() 和 addEdge() ,我需要尽可能复用ConcreteEdgesGraph<L>中已经实现的 add() 和 set()方法;针对 getDistance()方法,我需要基于选定的 ConcreteEdgesGraph<L>的 rep 来实现,而不能修改其 rep。 最后,我需要保证 Lab1 的 3.3 节给出的客户端代码仍可运行并且在 Lab1 里所写的 JUnit 测试用例仍然表现正常。

1. FriendshipGraph类

我令FriendshipGraph类继承自ConcreteEdgesGraph类
因为ConcreteEdgesGraph中的rep均标注为private final,所以不能被子类继承,但子类可以通过继承来的方法间接访问父类属性,所以FriendshipGraph的AF、RI、Safety from rep exposure与父类相同
在这里插入图片描述
FriendshipGraph方法实现:
addVertex方法:添加Person时需判断图中是否已存在
addEdge方法:添加边时需要判断图中是否存在输入的两个顶点
getDistance方法:
计算两点间距离时使用广度优先算法,找起始点targets,并存入队列中,循环取队头,重复操作,直到找到目标点

public int getDistance(Person person1, Person person2) {
                // 用Set存储已访问过的顶点,避免重复访问
                Set<Person> visited = new HashSet<>();
                // 用队列存储待访问的顶点和已走过距离组成的键值对
                Queue<Pair<Person, Integer>> queue = new LinkedList<>();
                queue.add(new Pair<>(person1, 0));

                while (!queue.isEmpty()) {
                        // 取出队列中的第一个元素
                        Pair<Person, Integer> current = queue.poll();
                        // 当前顶点
                        Person currentPerson = current.getKey();
                        int dist = current.getValue();
                        visited.add(currentPerson);
                        if (currentPerson == person2) {
                                return dist;
                        }
                        // 将当前顶点的邻接表中的顶点加入队列
                       for (Person friend : targets(currentPerson).keySet()) {
                               if (!visited.contains(friend)) {
                                       queue.add(new Pair<>(friend, dist + 1));
                                }
                        }
                }
                // 如果两个人之间没有路径,则返回-1
                return -1;
        }

存入队列的数据结构为自定义的Pair内部静态类,以键值对的形式构建,键为顶点,值为已走的距离

2. Person类

在构造器中,检测输入的字符串是否为空,检测输入的名字是否已存在,使用一个Set类来存储已存在的名字

private String name;
private static final Set<String> existingNames = new HashSet<>();

 		public Person(String name) {

                // 检查输入是否为空字符串
                if (name.trim().isEmpty()) {
                        throw new IllegalArgumentException("Name cannot be empty or contain only whitespace.");
                }
                // 检查名字是否已经存在
                if (existingNames.contains(name)) {
                        throw new IllegalArgumentException("Person with name " + name + " already exists.");
                }
                this.name = name;
                // 将新建的名字加入到已存在的名字集合中
                existingNames.add(name);
        }

3. 客户端main()

main()运行结果为:
在这里插入图片描述

4. 测试用例

addVertex测试

Test Strategy:

  • 按输入的顶点是否已存在于图中划分:在图中、不在图中
  • 按输入的字符串长度划分:0,1,n(>1)
  • 按输入的字符串内容划分:空字符串、非空字符串

addEdge测试

Test Strategy:

  • 按输入的顶点是否已存在于图中划分:两个顶点都在图中、两个顶点都不在图中、一个顶点在图中一个不在图中

getDistance测试

Test Strategy:

  • 按输入的两个顶点之间是否有路径划分:有路径、无路径
  • 按输入的两个顶点是否是同一个顶点划分:是同一个顶点、不是同一个顶点

总结

面向ADT编程更加抽象,更关注定义数据类型的抽象结构、操作和行为,并且面向ADT的编程注重可重用,其定义的数据类型可以被多个应用场景共享和复用;而直接面向应用场景编程更加具体,关注于解决特定的应用问题,会有更多定制化的实现,但可能会产生很多冗余代码,缺少可重用性。 本实验的工作量很大,难度较高,综合考验了ADT和OOP的全方面能力,对于我来说是个较大的考验,也是一个极大的提升。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值