哈工大软件构造Lab2实验总结(2023春)

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.实验环境配置

环境:IntelliJ IDEA,jdk-1.8.0,Junit4

3.实验过程

请仔细对照实验手册,针对三个问题中的每一项任务,在下面各节中记录你的实验过程、阐述你的设计思路和问题求解思路,可辅之以示意图或关键源代码加以说明(但千万不要把你的源代码全部粘贴过来!)。

3.1Poetic Walks

完成Graph接口的两实现个类ConcreteEdgesGraph,ConcreteVerticesGraph ,实现Graph数据类型,同时可实现泛型化Graph<L>。

Graph接口要求实现add(添加新节点),set(添加新边),remove(移除节点),vertices(获得所有的节点集合),sources(target)获得以target为目标节点的边的起始节点,targes(source)获得以source为起始节点的边的目标节点。

在两个具体的实现类中也有不同于接口中的toString(转换成字符串)方法。

GraphPoet类是给定一组单词(文件输入),对于两个相邻的单词a和b,认为存在一条由a到b的有向边,通过Graph接口构造有向图。再给定一段由单词组成的句子,如果句子中两个相邻单词之间在Graph图中有一个中间单词则将中间单词插入到两单词之间(如果有多个则插入权重最大的那个)。

3.1.1Get the code and prepare Git repository

打开实验要求的代码提取GitHub仓库。直接在本地GitHub仓库用git clone 获取文件,截取其中的P1。

如图:

 

3.1.2Problem 1: Test Graph <String>

以下各部分,请按照MIT页面上相应部分的要求,逐项列出你的设计和实现思路/过程/结果。

静态方法测试:直接使用了他提供的两个测试方法,未作修改。Graph有两种实现方式,在测试时Graph接口中的empty()方法可以返回ConcreteEdgesGraph或者ConcreteVerticesGraph中的任意一个新对象。这里选择ConcreteEdgesGraph。

 

静态测试结果:

 

Graph中各个接口以及给出了规约,应首先在GraphInstanceTest类中编写测试策略如下:

 

按照对应测试策略可以写出各种测试方法

      1. Problem 2: Implement Graph <String>

以下各部分,请按照MIT页面上相应部分的要求,逐项列出你的设计和实现思路/过程/结果。

该部分要求用两种实现方式实现图的类。ConcreteEdgesGraph中需要编写两部分代码:ConcreteEdgesGraph类和Edge类。首先需要在ConcreteEdgesGraph类中按照Graph中的规约实现Graph接口,并重写toString类。再者是编写Edge类,需要考虑安全性,所以类中的属性需设为private final,类中的不提供set方法,get方法采用防御性拷贝。ConcreteVerticesGraph同理,具体实现在下述问题中再具体描述。

此外,因为考虑到该问题中需要修改为泛型,所以防御性拷贝不能直接用具体类实现,应采用序列化方式进行拷贝。具体为在P1的包中增加deepclone类,内部只设置一个静态方法deepclone,代码如下:

 

只需要将想要拷贝的对象传入方法,返回值即为拷贝得到的新对象。此外,使用该方法时会产生编译时错误,故需要在使用处用try/catch捕获异常来使程序可以正常运行。

另外,P1中获取的原本代码不符合实验要求,应该对内部加以调整,调整后如下:

 

        1. Implement ConcreteEdgesGraph

Edge:在该问题中,需要先编写Edge类,内部属性如下:

 

AF、RI和Safety from rep exposure如下:

 

所以根据RI产生的checkRep函数如下:

 

任何可能改变值的位置都应用checkRep检查。

Edge类中,构造方法必须接收source、target和weight的初始值,getSource、getTarget、getWeight三个方法只需直接利用防御性拷贝返回对应的值即可。toString方法可以按照自己喜欢的方式输出边:

 

ConcreteEdgesGraph类:类中属性已经给出,AF、RI和Safety from rep exposure如下:

 

根据RI产生的checkRep如下:

 

add方法需要先检测输入点是否为空,再检测图中是否已经有待添加点,否则将点vertex直接加入点集vertices:

 

set方法需要检测几项标准:输入点不能为空、weight小于0,数据必然非法、source和target必须同时存在、起始点和终点之间是否有边、有边或无边情况下weight为0还是大于0,合法的标准组合为(起点和终点都在图中&&(起点和终点有边且weight>=0 || 起点和终点之间无边且weight>0)),不符合该条件的均为非法,返回-1,其余情况按spec规约编写即可,关键代码如下:

 

remove方法需要检测要删除的点是否在图中,如果在图中则删除该点,以及在边集edges中与该点相连的边。需要注意的是,如果用增强for的方法遍历删除边,会产生错误,应该采用迭代器的方法遍历edges删除边,利用Iterator中的remove方法删除,否则会产生空访问错误;而我这里采用了List的removelf方法,删除所有符合条件的边,而且不会产生上述问题。关键代码如下:

 

vertices方法需要检测是否为空图,如果为空,直接返回新建的空Set即可,若不为空,利用防御性拷贝vertices返回即可:

 

sources方法和targets方法是一对功能相似的方法,所以两者的实现是几乎相同的。具体只需寻找以输入点为终点或起点的边,将相应数据存入Map返回即可,以sources方法为例:

toString方法用自己喜欢的方式输出图即可,如下:

 

补充:Edge类中方法的测试类和ConcreteEdgesGraph类中toString的测试方法需要写在ConcreteEdgesGraphTest类中,Edge类中方法测试策略如下:

 

按照测试策略编写测试方法即可。

        1. Implement ConcreteVerticesGraph

Vertex类:编写此问题时需要先实现Vertex类,AF、RI和Safety from rep exposure如下:

 

由RI可得checkRep如下:

 

getName、getOutEdg、getInEdge方法都是直接进行防御性拷贝并返回。

而类中设置了setOutEdge、setInEdge方法,与graph接口中set方法的逻辑类似,就是检查合法性后,如果有边就返回原来的权值,无边就返回0,替换边的操作也如出一辙:

 

toString方法还是依照自己的想法来输出:

 

ConcreteVerticesGraph类:

类中的属性只有点集,所以需要借助Vetex中对边的操作来完成各个方法的编写。AF、RI和Safety from rep exposure如下:

 

由RI得checkRep:

 

add方法仍然是检测输入点不能为空、检测图中是否已经有此点,如果没有,即可加入。

set方法中检测起点和终点不能相等、起点和终点必须都在图中,若合法则直接调用setOutEdge、setInEdge方法加边,出入边都需要添加,所以应该搜索起始点和终点,关键代码如下:

 

remove需要先检测删除点是否在图中,如果在图中则直接删除点,然后再调用setOutEdge和setInEdge函数删除所有和vertex相连的边:

 

vertices方法直接将所有点加入集合中即可,然后再次防御性拷贝返回。

source与target只需要找到对应的点,然后调用getInEdge或getOutEdge返回即可。查找如下:

 

toString还是按照自己的格式输出即可:

 

补充:不在Graph接口中的方法和类需要在ConcreteVerticesGraphTest类中额外补充,Vertex的测试策略如下:

 

按照测试策略编写即可。

      1. Problem 3: Implement generic Graph<L>

在该问题中,需要更新两个实现以支持任何类型的顶点标签,使用占位符L而不是String。完成转换后,所有实例方法测试都应该通过。

        1. Make the implementations generic

为了使图适用于泛型而不仅仅是String,需要对上述完成的代码做出一定的修改,首先就是修改几个类的声明:

 

 

 

 声明修改之后,要把这四个类中一大批以String声明改为以L声明。

之后还要修改接口和测试类:

 

 

 此外还需要修改、添加<L>这样的标签。

修改后进行测试的结果如下:

 

 

因为防御性拷贝的问题,测试类中没有测试大量的try/catch,导致覆盖率下降,但总体覆盖率在预期内,测试到位。  

        1. Implement Graph.empty()

如下:

 

两个实现任选一个即可。

      1. Problem 4: Poetic walks

该问题中给定一个语料库,根据语料库中的文本生成一个单词图,然后给定一条语句输入,在图中搜索词之间的关系,自动补全语句中可能可以完善的部分。在语料库中,对每一个单词看作一个顶点,相邻的单词之间,建立一条有向边,相邻单词对出现的次数,作为这条有向边的权值。在输入信息补全时,对相邻单词A和B做检查,如果存在一个单词C,在图中可以由前一个单词A通过这个单词C到达单词B,那么就在A和B之间补全C,补全的优先级按照权值越大者优先。

因为在GraphPoet的描述中以单词为内容的点是可以有指向自己的边的,所以应对Graph的两个实现进行适当的修改,解除起点和终点不能相同的限制,具体到代码就是修改一部分checkRep和set方法的判断条件,经测试修改后Graph依旧可以正常运行。

        1. Test GraphPoet

测试策略:

 

根据该策略编写结果即可

        1. Implement GraphPoet

GraphPoet的AF、RI和Safety from rep exposure如下:

 

GraphPoet方法比较简单,只需要逐行读取语料库中的句子,然后再用根据空格分割句子存入字符数组即可,之后根据字符数组,利用Graph接口的方法依次保存在图中。字符数组中的前一个单词应在图中添加指向后一个单词的边,如果边已经存在,就将权值加一。部分代码如下:

 

Poem是要利用上一个方法利用语料库形成的图为输入的字符串添加桥词,如果输入字符串中的两个词在图中只相隔了一个点(即桥词),就找到那个可以使两个词在只途径桥词的前提下距离最大的桥词,如果没有就不添加。部分代码如下:

 

        1. Graph poetry slam

除了Main函数中已经给出的用例,个人可以提出全新的语料库mycurpos.txt进行诗歌的生成,代码如下:

 

结果如下:

 

      1. 使用IDEA自带工具检查测试的代码覆盖度

完成上述部分,各个部分的代码覆盖度如下:

 

      1. Before you’re done

利用git add和git commit指令将项目添加到本地,最后再利用git push上传到指定仓库。

idea生成的项目的目录结构树状示意图如下:

 

    1. Re-implement the Social Network in Lab1

基于Graph<L>的两种实现ConcreteEdgeGraph<L>或者ConcreteVerticesGraph<L>,重新实现Lab1中的社交网络,并对其进行测试。

可以在Lab1的基础上稍加修改,将代码中相似的部分替换为适用Graph中的方法即可。

      1. FriendshipGraph

这里继承了Lab1中的代码思想,但是结合了Graph接口。

AddVertex方法直接调用Graph中的add方法即可:

 

addEdge同理,调用set方法,加入少量判断条件即可:

 

直接继承了Lab1中的做法,先将图中点和边按照Lab1中的形式存入friends,再加入部分判断条件即可。部分代码如下:

 

值得注意的是,因为Graph的两个实现中都使用了防御性拷贝,所以friends中的点、边和原本Person点可识别性各不相同,判断是否包含便不能直接利用cointains,应该使用遍历结合equals方法来判断。

      1. Person类

给出你的设计和实现思路/过程/结果。

Person继承了Lab1中的Person类,只需要存储名字和获取名字即可。但是因为Graph的实现中判断相等用的是equals方法,所以Person类中应该重写equals方法,代码如下:

 

此外,P1中使用的序列化拷贝方法,被拷贝的类型需要实现Serializable接口,否则会报错,所以应将类的声明改成:

 

      1. 客户端main()

Main方法直接使用Lab1中的即可,在此处将main放入FriendshipGraph类中:

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值