软件构造lab3总结

2021年春季学期
计算学部《软件构造》课程


Lab 3实验报告

目录

1 实验目标概述    1
2 实验环境配置    1
3 实验过程    1
3.1 待开发的三个应用场景    1
3.2 面向可复用性和可维护性的设计:IntervalSet<L>    1
3.2.1 IntervalSet<L>的共性操作    1
3.2.2 局部共性特征的设计方案    2
3.2.3 面向各应用的IntervalSet子类型设计(个性化特征的设计方案)    2
3.3 面向可复用性和可维护性的设计:MultiIntervalSet<L>    2
3.3.1 MultiIntervalSet<L>的共性操作    2
3.3.2 局部共性特征的设计方案    2
3.3.3 面向各应用的MultiIntervalSet子类型设计(个性化特征的设计方案)    2
3.4 面向复用的设计:L    2
3.5 可复用API设计    2
3.5.1 计算相似度    2
3.5.2 计算时间冲突比例    2
3.5.3 计算空闲时间比例    2
3.6 应用设计与开发    2
3.6.1 排班管理系统    3
3.6.2 操作系统的进程调度管理系统    3
3.6.3 课表管理系统    3
3.7 基于语法的数据读入    3
3.8 应对面临的新变化    3
3.8.1 变化1    3
3.8.2 变化2    3
3.9 Git仓库结构    3
4 实验进度记录    3
5 实验过程中遇到的困难与解决途径    4
6 实验过程中收获的经验、教训、感想    4
6.1 实验过程中收获的经验和教训    4
6.2 针对以下方面的感受    4

1实验目标概述
本次实验覆盖课程第前两次课的内容,目标是编写具有可复用性和可维护性的软件,主要使用以下软件构造技术:
子类型、泛型、多态、重写、重载
继承、代理、组合
常见的OO设计模式
语法驱动的编程、正则表达式
基于状态的编程
API设计、API复用
本次实验给定了三个具体应用(值班表管理、操作系统进程调度管理、大学课表管理),学生不是直接针对每个应用分别编程实现,而是通过ADT和泛型等抽象技术,开发一套可复用的ADT及其实现,充分考虑这些应用之间的相似性和差异性,使ADT有更大程度的复用(可复用性)和更容易面向各种变化(可维护性)。
2实验环境配置
实验环境设置请参见Lab-0 实验指南。
本次实验在GitHub Classroom中的URL地址为:
https://classroom.github.com/a/ZAJ8w2eC
请访问该URL,按照提示建立自己的Lab3仓库并关联至自己的学号。

本地开发时,本次实验只需建立一个项目,统一向GitHub仓库提交。实验包含的多个任务分别在不同的包内开发,具体目录组织方式参见各任务最后一部分的说明。请务必遵循目录结构,以便于教师/TA进行测试。

在这里给出你的GitHub Lab3仓库的URL地址(Lab3-学号)。
https://github.com/ComputerScienceHIT/HIT-Lab3-1190202407
3实验过程
3.1待开发的三个应用场景
1.值班表管理:一个单位有 n 个员工,在某个时间段内安排值班。每天只能安排一个员工且不能出现无人值班的情况;每个员工需要安排在连续的几天内。值班表内需要记录员工的名字、职位、手机号码,以便于外界联系值班员。
日期    值班人名字    职位    手机号码
2021-01-10    孙XX    书记    13900000000
2021-01-11    孙XX    书记    13900000000
2021-01-12    孙XX    书记    13900000000
2021-01-13    刘XX    主任    13811111111
2021-01-14    张XX    院长    18266666666
2021-01-15    张XX    院长    18266666666
2021-01-16    王XX    副院长    18677777777
2021-01-17    王XX    副院长    18677777777
2021-01-18    邬XX    副主任    15533333333
…            

2.操作系统进程调度管理:考虑计算机上有一个单核CPU,多个进程被操作系统创建出来,它们被调度在CPU上执行,由操作系统来调度决定在各个时段内执行哪个线程。操作系统可挂起某个正在执行的进程,在后续时刻可以恢复执行被挂起的进程。可知:每个时间只能有一个进程在执行,其他进程处于休眠状态;一个进程的执行被分为多个时间段;在特定时刻,CPU可以“闲置”,意即操作系统没有调度执行任何进程;操作系统对进程的调度无规律,可看作是随机调度。


3.大学课表管理:看一下你自己的课表,每一上午8:00-10:00和每周三上午8:00-10:00在正心楼13教室上“软件构造”课程。课程需要特定的教室和特定的教师。在本应用中,我们对实际的课表进行简化:针对某个班级,假设其各周的课表都是完全一样的(意即同样的课程安排将以“周”为单位进行周期性的重复,直到学期结束);一门课程每周可以出现1次,也可以安排多次(例如每周一和周三的“软件构造课”)且由同一位教师承担并在同样的教室进行;允许课表中有空白时间段(未安排任何课程);考虑到不同学生的选课情况不同,同一个时间段内可以安排不同的课程(例如周一上午1-2节的计算方法和软件构造);一位教师也可以承担课表中的多门课程;


异同点:
相同点:都包含了具有不同特征的“时间段集合”对象,每个时间段对应一个对象标签。
不同点:
1.是否允许时间轴上有空白。值班表中不能有空闲的时间,而进程和课表中可以有空闲的时间段。
2.是否允许不同的 interval 之间有重叠。值班表和进程在每个时间段只允许有一个老师,而课表中同一时间可以有多个课程,但每名老师只能上一门课。
3.是否包含周期性、连续性的时间段。值班表安排老师值班,若该老师需要值多天的班,则需要安排为连续值班;进程中,各个进程的运行时段可以不连续;课表安排中,同一个老师也可以不连续上课。
3.2面向可复用性和可维护性的设计:IntervalSet<L>
该节是本实验的核心部分。
3.2.1IntervalSet<L>的共性操作
empty():创建一个空对象。
void insert(long start, long end, L label):在当前对象中插入新的时间段和标签。
Set<L> labels():获得当前对象中的标签集合。
boolean remove(L label):从当前对象中移除某个标签所关联的时间段。
long start (L label):返回某个标签对应的时间段的开始时间。
long end (L label):返回某个标签对应的时间段的结束时间。
IntervalSet<L> copy():返回这个对象的副本。
3.2.2局部共性特征的设计方案
局部共性操作有初始化时间段集合,设置、删除时间段,更改该时间段的标签,获取时间段安排内容,例如获取值班老师的各种信息,如手机号等。
3.2.3面向各应用的IntervalSet子类型设计(个性化特征的设计方案)
使用decorator和delegation的思想,进行是否允许不同的 interval 之间有重叠以及周期性的维度的个性特征的设计。向装饰器中传入待装饰的IntervalSet,并在继承该装饰器的具体子类中实现相应的个性化功能。 DutyIntervalSet 类中定义了一个CommonIntervalSet,使用了委托,而且也给这个 CommonIntervalSet 添加了一个NoBlankDecorator 装饰器,添加判断方法 checkNoBlank 来检查其是否有空白,并且重新定义了值班表的 IntervalSet 的插入,删除等方法。
3.3面向可复用性和可维护性的设计:MultiIntervalSet<L>
3.3.1MultiIntervalSet<L>的共性操作
empty():创建一个空对象。
MultiIntervalSet(IntervalSet<L> initial):利用initial中包含的数据创建非空对象。
void insert(long start, long end, L label):在当前对象中插入新的时间段和标签。
Set<L> labels():获得当前对象中的标签集合。
boolean remove(L label):从当前对象中移除某个标签所关联的所有时间段。
IntervalSet<Integer> intervals(L label):从当前对象中获取与某个标签所关联的所有时间段。
3.3.2局部共性特征的设计方案
局部共性操作有初始化时间段集合,设置、删除时间段,更改该时间段的标签,获取时间段安排内容,例如获取进程中的最低开始时间等等。
一个标签可以有多个时间序列,一个标签的时间序列不可重叠,但对于方案三,不同标签的时间序列可以重叠,为了适应方案三的特性,定义了相关的装饰器。
3.3.3面向各应用的MultiIntervalSet子类型设计(个性化特征的设计方案)
使用decorator装饰器的方法进行是否允许不同的interval之间有重叠以及周期性的维度的个性特征的设计。向装饰器中传入待装饰的MultiIntervalSet,并在继承该装饰器的具体子类中实现相应的个性化功能。在 ProcessIntervalSet 类中定义了一个 MultiIntervalSet,标签为 ProcessItem,使用了委托。
3.4面向复用的设计:L
IntervalSet<L>和 MultiIntervalSet<L>中的泛型参数 L,可以是你所设计的任何 immutable 的类。
对三个应用来说,设计三个应用的不同标签,分别为“员工”、“进程”、“课程”。
Person的属性:姓名、职务、手机号码
Process的属性:进程 ID、进程名称、最长/短执行时间
Course的属性:课程 ID、课程名称、教师名、地点
            

3.5可复用API设计
3.5.1计算相似度
double Similarity(MultiIntervalSet<L> s1, MultiInterval Set<L> s2)
具体计算方法:按照时间轴从早到晚的次序,针对同一个时间段内两个对象里的interval,若它们标注的label等价,则二者相似度为1,否则为0;若同一时间段内只有一个对象有interval或二者都没有,则相似度为0。将各interval的相似度与interval的长度相乘后求和,除以总长度,即得到二者的整体相似度。


对于两个MultiIntervalSet:s1和s2,遍历s1中的标签,查看s2中是否存在相同的标签,如果不存在,则对相似度没有贡献;如果存在,那么这个标签在s1和s2中各有一个时间段的集合,计算这两个时间段集合的重合长度,将所有的重合长度加在一起除以MultiIntervalSet的时间跨度就是两个MultiIntervalSet的相似度。
3.5.2计算时间冲突比例
设计思路:因为时间轴是连续的,且起始时间与结束时间都为long类型的整数,所以考虑使用类似于桶排序(bucket sort)的方法,定义一个int类型的数组 bucket,作为时间轴,遍历IntervalSet中的每一条时间序列,对应在bucket数组中加一,若bucket数组中某个位置的值大于1,则说明这段时间序列重复了,以此来计算时间冲突的比例。
对于IntervalSet来说,由于它是一个特殊的MultiIntervalSet,因此可以把它转换成MultiIntervalSet后再调用针对MultiIntervalSet的计算时间冲突比例的函数。
3.5.3计算空闲时间比例
设计思路:类似于上一题的思路,因为时间轴是连续的,且起始时间与结束时间都为long类型的整数,所以考虑使用类似于桶排序(bucket sort)的方法,定义一个int类型的数组bucket,作为时间轴,遍历IntervalSet中的每一条时间序列,对应在bucket数组中加一,若bucket数组中某个位置的值为 0,则说明这段时间序列未使用,以此来计算空闲时间的比例。
3.6应用设计与开发
利用上述设计和实现的ADT,实现手册里要求的各项功能。
3.6.1排班管理系统
排班管理系统中,需要实现的功能为:
1. 设定排班日期。
2. 增加一组员工
3. 手工选择员工的排班
4. 随机生成排班表
5. 可视化排班表。
3.6.2操作系统的进程调度管理系统
操作系统进程调度管理系统需要实现的功能为:
1. 增加进程,使用addProcess方法,可以向ProcessScheduleApp中添加新的进程。
实现思路:在ProcessScheduleApp 类中定义了一个Map<ProcessItem, Long>processItems,其中键为ProcessItem对象,即进程对象,值为该进程的运行时间,初始设置为0。每次添加新的进程时,都需要往这个map里面put新的对象。
2. 随机调度:当前时刻(设定为0)启动模拟调度,随机选择某个尚未执行结束的进程在CPU上执行(执行过程中其他进程不能被执行),并在该进程最大时间之前的任意时刻停止执行,如果本次及其之前的累积执行时间已落到[最短执行时间,最长执行时间]的区间内,则该进程被设定为“执行结束”。重复上述过程,直到所有进程都达到“执行结束”状态。在每次选择时,也可“不执行任何进程”,并在后续随机选定的时间点再次进行进程选择。
实现思路:在ProcessScheduleApp中定义了operate方法,每次随机遍历processItems Map,若该进程为执行结束,则让它进行随机时间的运行,且运行总时间不会超过开始时设置的该进程的最大运行时间,依次循环,直到所有进程都运行结束。
3.6.3课表管理系统
使用CourseIntervalSet作为数据结构,同时维护一个Course的Map存储未被安排的课程以及对应的剩余学时数。
刚进入时APP,会提示初始化一些信息,包括:学期开始日期、总周数、以及一组课程信息。初始化结束后,打印一个菜单,告诉用户提供的一些功能。
3.7基于语法的数据读入
首先对文件的格式进行分析,文件总共包括三个部分,分别是Employee、Period和Roster,每个部分有自己独特的格式。在假设没有空格、缩进和空行的情况下,可以分别设计识别每个部分的正则表达式。

Employee{  //多个员工信息
ZhangSan{Manger,139-0451-0000} //{}前是员工姓名,大小写字母构成
LiSi{Secretary,151-0101-0000} //{}内第一个分量是职务,大小写字母和空格
WangWu{Associate Dean,177-2021-0301} 
//{}内第二个分量是中国境内合法的手机号码
//共11位,分为三段(3-4-4),用“-”相连
}
Period{2021-01-10,2021-03-06}  //表示排班的开始日期和结束日期
Roster{                      //表示排班计划
ZhangSan{2021-01-10,2021-01-31}//三个分量分别为姓名、开始日期、结束日期
LiSi{2021-02-01,2021-02-28}     //上述所有日期的格式为yyyy-MM-dd
WangWu{2021-03-01,2021-03-10}
}
Employee部分的正则表达式:
pattern_Employee = r'\w+{[\w\s]+\,\d\d\d\-\d\d\d\d\-\d\d\d\d}'
Period部分的正则表达式:
pattern_Period = r'Period{\d\d\d\d\-\d\d\-\d\d,\d\d\d\d\-\d\d\-\d\d}'
Roster部分的正则表达式:
pattern_Roster = r'\w+{\d\d\d\d\-\d\d\-\d\d,\d\d\d\d\-\d\d\-\d\d}'
对于整个文件,由于三个部分出现的顺序是不定的,即共有六种情况,因此识别整个文件的正则表达式为:

首先利用这个正则表达式判断文件的格式是否正确,如果正确就抽取出每个部分。
根据抽取出的信息,就可以构造出一个排班表了,在构造的同时判断一些错误,如:员工信息重复、员工未定义、时间重叠等。
在APP中,读入用户指定的文件,并去除所有的空格、缩进、空行,之后调用Parser解析信息,在出现错误时提示给用户相应错误信息。
3.8应对面临的新变化
3.8.1变化1
修改之前的DutyIntervalSet实现的是IntervalSet接口,但是本次变化要求每个标签可以对应多个时间段,因此要求DutyIntervalSet实现MulitIntervalSet接口,同时还要保持不允许重叠的特征。因此只需要修改DutyIntervalSet继承的装饰器类型以及实现的接口类型即可。
由于DutyIntervalSet从实现IntervalSet接口变为实现MulitIntervalSet接口,因此一些方法会发生变化,例如不再支持start()和end()方法,同时新增加了intervals()方法。因此,在DutyRosterApp需要修改相应的实现方法。
3.8.2变化2
之前的设计可以应对变化,代价很小。
设计思想:将 CourseScheduleAPP 的底层实现的 MultiIntervalSet 的插入方法由可以重叠的 freeInsert 更改为无法重叠的 insert,每次插入重叠的内容会抛出异常。
3.9Git仓库结构
请在完成全部实验要求之后,利用Git log指令或Git图形化客户端或GitHub上项目仓库的Insight页面,给出你的仓库到目前为止的Object Graph,尤其是区分清楚change分支和master分支所指向的位置。

4实验进度记录
请使用表格方式记录你的进度情况,以超过半小时的连续编程时间为一行。
每次结束编程时,请向该表格中增加一行。不要事后胡乱填写。
不要嫌烦,该表格可帮助你汇总你在每个任务上付出的时间和精力,发现自己不擅长的任务,后续有意识的弥补。
日期    时间段    计划任务    实际完成情况
2021.6.28    14:00-16:00    搭建环境平台    完成
2021.6.28    18:30-22:00    设计 Interval 接口,并完成CommonIntervalSet 类    完成
2021.6.29    13:30-15:30    实现 MultiIntervalSet 类     完成
2021.6.29    16:00-19:00    ADT框架设计    完成
2021.6.30    9:00-11:30    设计实现API,编写测试    完成
2021.6.30    14:00-17:00    实现DutyRoster 应用并实现正则表达
式    完成
2021.7.1    13:30-17:30    实现CourseSchedule、ProcessSchedule    完成
5实验过程中遇到的困难与解决途径
遇到的难点    解决途径
不知如何设计正则表达式来抽取大量的文件信息。    在学习正则表达式语法的同时进行一些小的测试,从小的正则表达式开始,逐渐累积成复杂的正则表达式,最终实现对文件的解析。
编写APP时遇到很多的健壮性问题。    仔细分析每个步骤中用户可能的所有输入,针对任何非法情况都作出提示。虽然很耗费时间,但效果很好。
6实验过程中收获的经验、教训、感想
6.1实验过程中收获的经验和教训
实验难度较大,耗时较长,在代码编写的过程中,学习与运用了课上的知识,提升个人的代码编写能力。
6.2针对以下方面的感受
(1)重新思考Lab2中的问题:面向ADT的编程和直接面向应用场景编程,你体会到二者有何差异?本实验设计的ADT在三个不同的应用场景下使用,你是否体会到复用的好处?

面向ADT编程,复用性高;面向应用编程,复用性低。

(2)重新思考Lab2中的问题:为ADT撰写复杂的specification, invariants, RI, AF,时刻注意ADT是否有rep exposure,这些工作的意义是什么?你是否愿意在以后的编程中坚持这么做?

它使客户端了解各方法的功能但无法得知内部具体实现,可以防止内部变量被客户端恶意修改,时刻检查表示不变量,保证安全性。提高了代码的可读性与可维护性。

(3)之前你将别人提供的API用于自己的程序开发中,本次实验你尝试着开发给别人使用的API,是否能够体会到其中的难处和乐趣?

(4)你之前在使用其他软件时,应该体会过输入各种命令向系统发出指令。本次实验你开发了一个解析器,使用语法和正则表达式去解析输入文件并据此构造对象。你对语法驱动编程有何感受?

语法驱动编程为实际应用提供了很大的便利,用户只需提供一个文件以及一定的语法规则即可,便于高效的自动化处理。

(5)Lab1和Lab2的大部分工作都不是从0开始,而是基于他人给出的设计方案和初始代码。本次实验是你完全从0开始进行ADT的设计并用OOP实现,经过五周之后,你感觉“设计ADT”的难度主要体现在哪些地方?你是如何克服的?

ADT的难度主要体现在抽象上。一个好的ADT既不能过于具体,也不能过于抽象。合理设计ADT的结构,不断测试与改进。

(6)“抽象”是计算机科学的核心概念之一,也是ADT和OOP的精髓所在。本实验的五个应用既不能完全抽象为同一个ADT,也不是完全个性化,如何利用“接口、抽象类、类”三层体系以及接口的组合、类的继承、设计模式等技术完成最大程度的抽象和复用,你有什么经验教训?

接口、抽象类、类的抽象程度一定是逐渐降低的,将所有应用的共性抽象为接口,然后在抽象类以及类中添加新的特性。

(7)关于本实验的工作量、难度、deadline。
时间太紧了,建议宽限几周

(8)到目前为止你对《软件构造》课程的评价。

提高了个人代码编写能力,学到了软件构造的方法。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值