2022年春季学期
计算学部《软件构造》课程
Lab 3实验报告
姓名 秦易
学号 120L020102
班号 2003003
电子邮件 1060982414@qq.com
手机号码 17300298293
目录
1 实验目标概述 1
2 实验环境配置 1
3 实验过程 1
3.1 待开发的三个应用场景 1
3.2 ADT识别与设计 1
3.2.1 任务1:投票类型VoteType 1
3.2.2 任务2:投票项VoteItem 1
3.2.3 任务3:选票Vote 2
3.2.4 任务4:投票活动Poll的测试 2
3.2.5 任务5:投票活动Poll的实现类GeneralPollImpl 2
3.2.6 任务6:投票活动Poll的子类型 2
3.3 ADT行为的设计与实现 2
3.3.1 任务7:合法性检查 2
3.3.2 任务8:采用Strategy设计模式实现灵活的计票规则 2
3.3.3 任务9:采用Strategy设计模式实现灵活的遴选规则 2
3.3.4 任务10:处理匿名和实名投票 2
3.3.5 任务11:采用Visitor设计模式实现功能扩展 2
3.3.6 任务12:基于语法的数据读入 2
3.4 任务13:应用设计与开发 2
3.4.1 商业表决系统 2
3.4.2 代表选举系统 2
3.4.3 聚餐点菜系统 2
3.5 任务14:应对面临的新变化 2
3.5.1 商业表决应用:可以一次表决多个商业提案 2
3.5.2 代表选举应用:遴选规则变化 3
3.5.3 聚餐点菜应用:取消权重设置、只计算“喜欢”的票数 3
3.6 Git仓库结构 3
4 实验进度记录 3
5 实验过程中遇到的困难与解决途径 3
6 实验过程中收获的经验、教训、感想 4
6.1 实验过程中收获的经验和教训(必答) 4
6.2 针对以下方面的感受(必答) 4
1 实验目标概述
本次实验覆盖课程第 2、3 章的内容,目标是编写具有可复用性和可维护性的软件,主要使用以下软件构造技术:
子类型、泛型、多态、重写、重载
继承、委派、CRP
语法驱动的编程、正则表达式
设计模式
本次实验给定了多个具体应用,学生不是直接针对每个应用分别编程实现,而是通过 ADT 和泛型等抽象技术,开发一套可复用的 ADT 及其实现,充分考虑这些应用之间的相似性和差异性,使 ADT 有更大程度的复用(可复用性)和更容易面向各种变化(可维护性).
2 实验环境配置
Windows 10 ; IDEA; JDK11; Junit4;Git
在这里给出你的GitHub Lab3仓库的URL地址(Lab3-学号)
https://github.com/ComputerScienceHIT/HIT-Lab3-120L020102.git
3 实验过程
请仔细对照实验手册,针对每一项任务,在下面各节中记录你的实验过程、阐述你的设计思路和问题求解思路,可辅之以示意图或关键源代码加以说明(但千万不要把你的源代码全部粘贴过来!)。
3.1 待开发的三个应用场景
简要介绍四个应用。
分析四个应用场景的异同,理解需求:它们在哪些方面有共性、哪些方面有差异。
3.2 ADT识别与设计
该节是本实验的核心部分。
3.2.1 任务1:投票类型VoteType
对于投票类型,采用Map来存储每一种选项及其对应的分数。对于其构造函数,通过传入的参数来确定最基本的投票类型,后续任务12会采用更加高级的正则表达式来构造。对于checkLegality而言,直接判断该option在不在Map中即可。要查询选项所对应的分值,首先要判断它是否合法,不合法则抛出异常,合法就返回分值。
3.2.2 任务2:投票项VoteItem
对于投票项,rep为被投票候选人和选项,在构造实例时,需要检查选项是否合法,不合法需抛出异常。通过get方法得到该投票项的候选人和选项,因为该ADT是不可变的,所以需要用private和final来修饰其中的rep防止泄露。
3.2.3 任务3:选票Vote
缺省为匿名投票,包含了一个投票人对所有候选对象的投票项集合以及投票时间,此外新增了一个temp来标记该选票是否合法。构造函数仅是创建了一个空的投票,需要通过set方法来加入投票项,在加入投票项时会对投票项进行检查,同一候选人只能有一个投票项。在get方法中,会将rep进行防御式拷贝过后在返回。
3.2.4 任务4:投票活动Poll的测试
① 对于Poll的测试,首先要构造一个新的活动以及投票人和候选人。检测其构造方法。
② 然后再创建投票人的Map,value代表其权值,并加入该活动中。检测addVoters方法。
③ 创建候选人的List并加入该活动中。检测addCandidates方法。
④ 创建投票并加入活动中。检测addVotes方法。
⑤ 创建一个计票方案,并使用其来对活动进行计票。检测statistics方法。
⑥ 创建一个遴选方案,并使用其来对活动进行遴选。检测selections方法。
⑦ 最后使用断言检测result方法。
每一步都需要用断言检测方法的正确性。
3.2.5 任务5:投票活动Poll的实现类GeneralPollImpl
对于基本的投票活动GeneralPollImpl,有以下rep:
投票活动名称;活动发起时间;候选对象List集合;投票人集合(包含对应权值);遴选最大数量;投票采用的投票类型;所有选票集合(包含不合法的);计票结果Map(包含其等分);遴选结果(包含其位次)。
构造方法依然是构造一个空的实例,通过set方法来为其填写相应的信息。
通过add方法来添加候选人、投票人和他们的权值以及所有投票。
计票和遴选都是通过委托pattern里面接口中的方法来进行的。
最后的result方法因为未确定C的类型,所以就用了java自带的toString。
3.2.6 任务6:投票活动Poll的子类型
在父类GeneralPollImpl的基础上,子类主要有变化的地方在于投票人权值的不同,以及应用1和3是采用的实名投票,因此后续计票和遴选的规则也会有相应的变化,因此需要重写其中一部分的方法。
对于商业投票而言,每个人的权值是不同的,在加入voters时就应该确定好权值。并且投票类型是仅统计支持票,没有最大遴选数量,只要同意率超过三分之二就可以通过。对于计票规则和遴选规则则在pattern中实现。最后的result也需要重写来表达正确的含义:
对于代表选举,每个人权值相同,为确保权值相同,可以在addVoters方法中把所有投票人的权值重新设置为1,以防客户端错误输入。类似地,可以将该应用的计票和遴选规则放在patter包中,在方法中委托即可。最后的result也需要重写来表达正确的含义:
再看应用3点餐,不同人的权值不同,可以在addVoters方法中修改“爷爷”、“儿子”等投票人的权值,也可以直接在客户端进行设置。计票和遴选规则同上。最后的result类似:
3.3 ADT行为的设计与实现
3.3.1 任务7:合法性检查
Poll对象在接收外部客户端传递进来的一张选票时,要对其进行合法性检查,具体在 addVote()方法实现。以下是合法性检查时应检查的非法内容:
① 一张选票中没有包含本次投票活动中的所有候选人
② 一张选票中包含了不在本次投票活动中的候选人
③ 一张选票中出现了本次投票不允许的选项值
④ 一张选票中有对同一个候选对象的多次投票
⑤ (仅应用 2)一张选票中对所有候选对象的支持票数量大k。
有关合法性检查,其中①~④是三个应用所共通的,直接在GeneralPollImpl中的addVote方法内实现即可。⑤则需要在应用2中重写该方法。
我在做前面任务时,已将③实现在VoteItem中,将④实现在Vote中,所以在该方法不用再做实现。
检查①和②的实现如下:
这里在vote类中新增了方法notL来标记这个vote是不合法的。
⑥ 的实现:
新增一个局部变量s来记录这个vote中支持的数量,若超出quantity就将投票记录为不合法。
3.3.2 任务8:采用Strategy设计模式实现灵活的计票规则
不同的活动类型有不同的计票规则,考虑到今后可能会添加新的计票方法,因此采用Strategy设计模式来实现灵活的计票规则。
在 Poll的代码中,statistics()方法有一个参数 ss,其类型为 StatisticsStrategy,这是一个接口,在这个接口下生成了一个类rule,里面有各种不同的计票方法。
在活动类的方法中传入ss并调用其rule里的计票方法,则可以实现不同的计票规则,这是一种委托机制。
3.3.3 任务9:采用Strategy设计模式实现灵活的遴选规则
不同的活动类型有不同的遴选规则,考虑到今后可能会添加新的遴选方法,因此采用Strategy设计模式来实现灵活的遴选规则。
在 Poll 的代码中,selection()方法有一个参数 es,其类型为SelectionStrategy,这也是一个接口。我建的rule类里面同样包含了各种不同的遴选方法。
在活动类的方法中传入ss并调用其rule里的遴选方法,则可以实现不同的遴选规则,这是一种委托机制。
3.3.4 任务10:处理匿名和实名投票
应用1和应用3是实名投票,而应用2是匿名投票。前面给出的 Vote 的 rep中没有出现投票人信息,因此它是支持匿名投票的 ADT。为了支持实名投票,需要在选票 ADT 中额外记录投票人信息。具体方法有二:
① 构造Vote的子类型RealNameVote;
② 使用 Decorator 设计模式,在 Vote 基础上进行扩展。
3.3.5 任务11:采用Visitor设计模式实现功能扩展
考虑到将来的投票应用可能出现更多的对投票结果的处理,请在ADT 设计中引入 Visitor 设计模式,预留好接口扩展新功能。基于该设计模式,实现一个“计算合法选票在所有选票中所占比例”的扩展功能。
3.3.6 任务12:基于语法的数据读入
在3.3.3节中已经实现了 VoteType 的构造方法。在这里补充完成另一个构造方法 public VoteType(String regex),它输入一个遵循特定语法的字符串,构造函数解析该字符串,进而构造出 VoteType 对象。
对该方法的测试:
3.4 任务13:应用设计与开发
3.4.1 商业表决系统
① 构造活动
② 创建并加入Voters
③ 创建并加入candidates
④ 创建并加入投票
⑤ 计票遴选最后输出结果
3.4.2 代表选举系统
① 构造活动
② 创建并加入Voters
③ 创建并加入candidates
④ 创建并加入投票
⑤ 计票遴选最后输出结果
3.4.3 聚餐点菜系统
① 构造活动
② 创建并加入Voters
③ 创建并加入candidates
④ 创建并加入投票
⑤ 计票遴选最后输出结果
3.5 任务14:应对面临的新变化
3.5.1 商业表决应用:可以一次表决多个商业提案
之前我设计的商业表决关于quantity是可以设置为大于一的整数的,所以也可以一次表决多个商业提案,最终会输出通过表决的提案。
在构造实例时就可以将quantity设置得很大,这样可以任意添加提案进去投票。
最后输出时会将所有通过的提案打印出来。
3.5.2 代表选举应用:遴选规则变化
之前的设计采用了Strategy设计模式,将计票和遴选的方法放在SelectionStrategy和StatisticsStrategy的接口中,在活动的计票和遴选仅需调用接口中的方法即可,这是一种委托机制。
所以当有新的遴选规则时,仅需在接口中完成新的方法即可。
3.5.3 聚餐点菜应用:取消权重设置、只计算“喜欢”的票数
之前的设计是让客户端传进来每个Voter的权值,仅对特殊Voter作权值的修改。当取消权值设置时,需要将每个Voter的权值都设置为1。通过重写或重载addVoters方法,将每个加入进来的Voter权值设为1即可。
同样,新的计票规则只需要在StatisticsStrategy接口中实现即可。
3.6 Git仓库结构
请在完成全部实验要求之后,利用Git log指令或Git图形化客户端或GitHub上项目仓库的Insight页面,给出你的仓库到目前为止的Object Graph,尤其是区分清楚change分支和master分支所指向的位置。
在做任务14的时候出了些问题。我在按照指导书的步骤做:
…最初在 master 上工作…
git checkout -b change 创建新分支
…按上面的要求进行代码修改…
git add *
git commit -m "change"在该分支上提交
git checkout master 切换回 master分支。当做到这一步时,给我报错了:error: pathspec ‘master’ did not match any file(s) known to git。
然后我在看所有分支:
发现我的master不见了。。。最后重新建了一次master,然后就成这样了
在CSDN上找的恢复分支的方法试了也没有用。只得硬着头皮这样子交上来了。
4 实验进度记录
请使用表格方式记录你的进度情况,以超过半小时的连续编程时间为一行。
每次结束编程时,请向该表格中增加一行。不要事后胡乱填写。
不要嫌烦,该表格可帮助你汇总你在每个任务上付出的时间和精力,发现自己不擅长的任务,后续有意识的弥补。
日期 时间段 计划任务 实际完成情况
6.22 20:00-21:00 阅读指导书并完成任务1 按计划完成
6.23 13:00-14:00 完成任务2 按计划完成
6.23 14:00-15:00 完成任务3 按计划完成
6.23 15:00-16:00 完成任务4的测试用例 按计划完成
6.24 13:00-13:30 完成任务4 按计划完成
6.24 14:00-16:00 完成GeneralPollImpl的实现 未能跑通测试
6.24 16:00-17:00 实现计票和遴选方法的接口 按计划完成
6.24 20:00-21:00 完成三个子类的测试用例 按计划完成
6.24 21:00-22:00 完成商业投票的类的实现 未能跑通测试
6.24 22:00-23:00 完成选举和点餐的类的实现 未能跑通测试
6.25 10:00-12:00 修复三个子类的bug 成功跑通测试
6.25 13:00-14:00 完成任务7 按计划完成
6.25 14:00-15:00 完成任务8和9 按计划完成
6.25 15:00-15:30 完成匿名投票 按计划完成
6.25 15:30-16:00 完成任务11 按计划完成
6.25 16:00-17:00 实现正则表达式的输入 按计划完成
6.25 17:00-18:00 完成任务13 按计划完成
6.25 20:00-21:00 完成任务14 按计划完成
6.26 10:00- 写报告 按计划完成
5 实验过程中遇到的困难与解决途径
遇到的难点 解决途径
这次实验项目相比起之前的要大,有很多ADT需要自己去设计实现,关联好每一个ADT需要费不少的时间。 反复阅读指导书上的表,通过查看每一个ADT内部老师给好的代码,将每个ADT的实现方案都记录下来,方便后续实现。
在实现poll的测试和子类时,由于还没有实现计票和遴选的接口,导致后续实现方法时遇到了障碍,没能用上这两个接口,测试也没能跑通。 将计票和遴选的方法暂时写在poll子类的内部方法中,后续完成计票和遴选的接口时再将代码搬移至pattern里。
有关正则表达式的拆分等还不是很了解使用的方法。 在CSDN上查阅相关资料,借鉴前辈的正则表达式的拆分方案。
在使用Git时,checkout change 并commit后,master分支没了。 在CSDN上查阅解决方案,多次fetch后依然没有master分支,最后只得新建master分支并checkout。
6 实验过程中收获的经验、教训、感想
6.1 实验过程中收获的经验和教训(必答)
这次实验的工作量相比起前面两次要大得多,让我体会到了做大型项目时的辛苦。在实现计票和遴选方法时,采用的Strategy设计模式让我感受到这个委托机制的妙处,确实是方便了不少。由于期末考试,这次实验时间也比较短暂,还有很多地方和细节做得不够还也没能完善,特别是程序健壮性这一块,自己写的程序健壮性不够好,那我的测试类就越难做。包括很多需要抛出异常的地方,可见在企业完成一个大的项目时,考虑的方面是非常多且全面的,要把程序每个地方都做到极致,让用户用得舒服,是十分具有挑战性的。这次实验还用到了正则表达式,让我有机会去浅浅学习了一下java中正则表达式的用法。
6.2 针对以下方面的感受(必答)
(1) 重新思考Lab2中的问题:面向ADT的编程和直接面向应用场景编程,你体会到二者有何差异?本实验设计的ADT在三个不同的应用场景下使用,你是否体会到复用的好处?
答:面向ADT的编程我仅需要考虑这一个ADT,面向应用场景编程我需要考虑到所有要使用到的ADT,后者难度更大。在实现GeneralPollIpml的子类时,我仅需重新实现有所差异的地方,复用让程序员节省了不少的时间和经理。
(2) 重新思考Lab2中的问题:为ADT撰写复杂的specification, invariants, RI, AF,时刻注意ADT是否有rep exposure,这些工作的意义是什么?你是否愿意在以后的编程中坚持这么做?
答:这些工作有利于程序员在实现ADT的方法时,时刻注意并检查RI,更好地实现程序,检查泄露加强ADT安全性和健壮性。我当然愿意这么做。
(3) 之前你将别人提供的ADT/API用于自己的程序开发中,本次实验你尝试着开发给别人使用的ADT/API,是否能够体会到其中的难处和乐趣?
答:难处就在于设计ADT时要考虑的方面真的很多,要考虑ADT之间的联动,它们的泄露与否,遇到异常时的处理又如何,这些都需要花一定的时间和精力去完善这些细节。乐趣在于当我们真正做好一个ADT并使用它时,能够体会到这些对于用户而言带来了很大的便利,非常有成就感。
(4) 你之前在使用其他软件时,应该体会过输入各种命令向系统发出指令。本次实验你开发了一个简单的解析器,使用语法和正则表达式去解析一个遵循特定规则的字符串并据此构造对象。你对语法驱动编程有何感受?
答:语法驱动编程难度蛮大的,要考虑好用户所有可能的输入。
(5) Lab1和Lab2的工作都不是从0开始,而是基于他人给出的设计方案和初始代码。本次实验中也提供了一部分基础代码。假如本实验要求你完全从0开始进行ADT的设计并用OOP实现,你觉得自己是否能够完全搞定?你认为“设计ADT”的难度主要体现在哪些地方?
答:如果完全从零开始的话,我觉得我依然能够搞定,但是花费的时间和精力就要多一些了。我认为设计ADT的难度主要体现在每个ADT之间的联动,如何做好相互之间的接口复用等等。
(6) “抽象”是计算机科学的核心概念之一,也是ADT和OOP的精髓所在。本实验的三个应用既不能完全抽象为同一个ADT,也不是完全个性化,如何利用“接口、抽象类、类”三层体系以及接口的组合、类的继承、委派、设计模式等技术完成最大程度的抽象和复用,你有什么经验教训?
答:共性的地方在父类中完成,不同的地方在子类中重写,对于有变化的方法,可以利用委托机制来完成,用对设计模式可以让程序变得更加简易。
(7) 关于本实验的工作量、难度、deadline。
答:工作量蛮大的,特别是各个细节都要做到位的话确实是很难,加上期末,时间还挺赶的。
(8) 课程结束了,你对《软件构造》课程内容和任课教师的评价如何?
答:课程内容非常实用且紧凑,学到了很多如何更好地写程序。刘老师人很好,讲课也很不错。