哈工大软件构造 第三次实验报告

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

 

 

 

 

Lab 3实验报告

 

 

 

 

 

 

 

 

姓名

郭子正

学号

1190201117

班号

1936601

电子邮件

1190201117@stu.hit.edu.cn

手机号码

18102152791

 

 

 

目录

 

1 实验目标概述

2 实验环境配置

3 实验过程

3.1 待开发的三个应用场景

3.2 面向可复用性和可维护性的设计:IntervalSet<L>

3.2.1 IntervalSet<L>的共性操作

3.2.2 局部共性特征的设计方案

3.2.3 面向各应用的IntervalSet子类型设计(个性化特征的设计方案)

3.3 面向可复用性和可维护性的设计:MultiIntervalSet<L>

3.3.1 MultiIntervalSet<L>的共性操作

3.3.2 局部共性特征的设计方案

3.3.3 面向各应用的MultiIntervalSet子类型设计(个性化特征的设计方案)

3.4 面向复用的设计:L

3.5 可复用API设计

3.5.1 计算相似度

3.5.2 计算时间冲突比例

3.5.3 计算空闲时间比例

3.6 应用设计与开发

3.6.1 排班管理系统

3.6.2 操作系统的进程调度管理系统

3.6.3 课表管理系统

3.7 基于语法的数据读入

3.8 应对面临的新变化

3.8.1 变化1

3.8.2 变化2

3.9 Git仓库结构

4 实验进度记录

5 实验过程中遇到的困难与解决途径

6 实验过程中收获的经验、教训、感想

6.1 实验过程中收获的经验和教训

6.2 针对以下方面的感受

 

 

 

1 实验目标概述

本次实验覆盖课程第 2、3 章的内容,目标是编写具有可复用性和可维护性 的软件,主要使用以下软件构造技术:

⚫ 子类型、泛型、多态、重写、重载
⚫ 继承、代理、组合
⚫ 语法驱动的编程、正则表达式
⚫ API设计、API复用

本次实验给定了三个具体应用(值班表管理、操作系统进程调度管理、大学课表管理),学生不是直接针对每个应用分别编程实现,而是通过 ADT 和泛型等抽象技术,开发一套可复用的 ADT 及其实现,充分考虑这些应用之间的相似性和差异性,使ADT有更大程度的复用(可复用性)和更容易面向各种变化(可维护性)。

2 实验环境配置

简要陈述你配置本次实验所需环境的过程,必要时可以给出屏幕截图。

特别是要记录配置过程中遇到的问题和困难,以及如何解决的。

 

点击实验指导中的链接,创建仓库,克隆到本地进行开发即可。

 

在这里给出你的GitHub Lab3仓库的URL地址(Lab3-1190201117)。

 

https://github.com/ComputerScienceHIT/HIT-Lab3-1190201117

3 实验过程

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

3.1 待开发的三个应用场景

DutyRosterApp:设计一个排班表。排班表不允许重叠。理论上也不允许有空白,但是这需要等到全部插入结束之后之后才能判断。

ProcessSchedule:设计一个进程调度器,可以将进程执行时间分割并重新排列,模拟处理器的进程调度。进程之间不可以重叠。

CourseSchedule:设计一个课程表,可以添加课程并选课,课程之间可以重叠。由于每周课程都相同,于是只需要关注一周的课程情况即可。

 

分析三个应用场景的异同,理解需求:它们在哪些方面有共性、哪些方面有差异。

 

共性:都需要一个管理时间区间的数据结构,可以向结构内插入新区间。每个区间都有一个标识。

差异:有些情况下(排班表、进程调度)区间不允许重叠,而另外一些情况下(课表管理)区间可以重叠。

3.2 面向可复用性和可维护性的设计:IntervalSet<L>

该节是本实验的核心部分。

3.2.1 IntervalSet<L>的共性操作

  1. 插入新区间
  2. 获取结构内所有区间的标志
  3. 删除区间
  4. 获取给定标志的区间的开始/结束时间(由于IntervalSet中不允许多个区间共享一个标记,所以这个操作的结果是唯一的)
  5. 将区间按开始时间排序,返回排好序的列表
  6. 计算冲突时间的比例
  7. 计算空闲时间的比例
  8. 获取/设置该数据结构存放区间的最早开始时间/最晚结束时间

3.2.2 局部共性特征的设计方案

CommonIntervalSet实现局部共性特征,个性特征使用装饰器模式拓展。CommonIntervalSet描述了最基本的区间集合。该ADT使用HashMap将区间的标志和区间信息(开始时间,结束时间,区间标志)联系起来,并且保存区间的最早开始时间/最晚结束时间。

  1. 插入新区间/删除区间:直接修改HashMap即可。
  2. 获取区间标志:直接访问HashMap中keySet即可。
  3. 获取给定标志的区间的开始/结束时间:直接访问对应区间信息即可。
  4. 将区间按开始时间排序,返回排好序的列表:重写比较器,利用标准库的算法。
  5. 计算冲突/空闲时间的比例:扫描全部时间点,并查看相邻两个时间点之间是否有区间,再进行计算即可。
  6. 获取/设置该数据结构存放区间的最早开始时间/最晚结束时间:直接访问ADT内保存的 信息即可。

3.2.3 面向各应用的IntervalSet子类型设计(个性化特征的设计方案)

添加一个装饰器NonOverlappableIntervalSet,可以禁止重叠区间。装饰器内只需要重写插入方法。每次插入时,扫描待插入区间对应的时间段,查看是否有其他区间已经占用了此时间段。

      

面向排班表的类型设计DutyIntervalSet:增加了管理员工的功能以及随机生成排班表的功能。

3.3 面向可复用性和可维护性的设计:MultiIntervalSet<L>

3.3.1 MultiIntervalSet<L>的共性操作

  1. 插入新区间
  2. 获取结构内所有区间的标志
  3. 删除区间
  4. 获取给定标志的所有区间
  5. 将区间按开始时间排序,返回排好序的列表
  6. 计算冲突时间的比例
  7. 计算空闲时间的比例
  8. 获取/设置该数据结构存放区间的最早开始时间/最晚结束时间
  9. 计算两个MultiIntervalSet的相似性

 

3.3.2 局部共性特征的设计方案

计算相似性:枚举所有时间点,查看在任意两个相邻时间点间两个ADT是否存储了具有相同标记的区间信息,若有则项结果中添加相应的值,否则继续查看下一个时间段。

利用IntervalSet作为其rep,将标签标号作为IntervalSet内区间的标签进行操作。其他方法和IntervalSet中实现方案相同。

3.3.3 面向各应用的MultiIntervalSet子类型设计(个性化特征的设计方案)

进程调度ProcessIntervalSet:增加随机调度和最短进程优先的调度方法.

模拟课表CourseIntervalSet : 增加添加课程、选课和获取课表的方法

3.4 面向复用的设计:L

将IntervalSet和MultiIntervalSet中标签的类型泛化,以适应不同场景。

3.5 可复用API设计

API设计在各ADT内部。

3.5.1 计算相似度

public double Similarity(MultiIntervalSet<L> set)

枚举所有时间点,查看在任意两个相邻时间点间两个ADT是否存储了具有相同标记的区间信息,若有则项结果中添加相应的值,否则继续查看下一个时间段。

3.5.2 计算时间冲突比例

public double calcConflictRatio()

扫描全部时间点,并查看相邻两个时间点之间是否有区间,再进行计算。

3.5.3 计算空闲时间比例

public double calcFreeTimeRatio()

扫描全部时间点,并查看相邻两个时间点之间是否有区间,再进行计算。

3.6 应用设计与开发

利用上述设计和实现的ADT,实现手册里要求的各项功能。

3.6.1 排班管理系统

使用Calendar类管理日期,将日期转化成长整型进行存储,输出时再将长整数转化回日期进行输出。

存储员工姓名和对应Employee信息的对照表,方便进行查找。由于不允许重名,所以这个方法是可行的。

剩下的功能可以直接调用DutyIntervalSet中的方法直接实现。

3.6.2 操作系统的进程调度管理系统

将进程信息打包成Process类,作为ProcessIntervalSet的标签。所有功能均可以使用ProcessIntervalSet中的方法实现。注意在这个应用中区间的总长度是不断变长的。

3.6.3 课表管理系统

只需要管理一周的课程安排即可。将一周分成35个时间段,对应35个可能的上课时间。对这些时间分别编号并存进CourseIntervalSet即可。其余功能可以借助CourseIntervalSet中的方法完成。

3.7 基于语法的数据读入

修改“排班管理”应用以扩展该功能。

只需利用正则表达式分析文本文件。具体如下:

String pEmployee = "([a-zA-Z]+)\\{([a-zA-Z]+),(\\d{3}-\\d{4}-\\d{4})\\}";

        String pPeriod = "Period\\{(\\d{4})-(\\d{2})-(\\d{2}),(\\d{4})-(\\d{2})-(\\d{2})\\}";

        String pRoster = "([a-zA-Z]+)\\{(\\d{4})-(\\d{2})-(\\d{2}),(\\d{4})-(\\d{2})-(\\d{2})\\}";

       

        Pattern patternEmployee = Pattern.compile(pEmployee);

        Pattern patternPeriod = Pattern.compile(pPeriod);

        Pattern patternRoster = Pattern.compile(pRoster);

       

        Matcher matcherEmployee = patternEmployee.matcher(buf);

        Matcher matcherPeriod = patternPeriod.matcher(buf);

        Matcher matcherRoster = patternRoster.matcher(buf);

之后分别读取各组中的字符串即可。其中需要特殊判断Roster部分和Period部分可能的重合情况。

3.8 应对面临的新变化

3.8.1 变化1

评估之前的设计是否可应对变化、代价如何、如何修改设计以应对变化

 

可以应对变化,将排班表中的rep改为MultiIntervalSet即可。代价很小(两行代码)。

3.8.2 变化2

评估之前的设计是否可应对变化、代价如何、如何修改设计以应对变化

可以应对变化。将MultiIntervalSet中rep的IntervalSet添加一层装饰器NonOverlappableIntervalSet即可。代价很小(三行代码)。

3.9 Git仓库结构

请在完成全部实验要求之后,利用Git log指令或Git图形化客户端或GitHub上项目仓库的Insight页面,给出你的仓库到目前为止的Object Graph,尤其是区分清楚change分支和master分支所指向的位置。

4 实验进度记录

请使用表格方式记录你的进度情况,以超过半小时的连续编程时间为一行。

每次结束编程时,请向该表格中增加一行。不要事后胡乱填写。

不要嫌烦,该表格可帮助你汇总你在每个任务上付出的时间和精力,发现自己不擅长的任务,后续有意识的弥补。

日期

时间段

计划任务

实际完成情况

2021-06-28

15:00-17:00

设计装饰器结构,写IntervalSet相关测试用例

完成,顺便实现了CommonIntervalSet

2021-06-28

19:00-20:00

设计IntervalSet相关装饰器

完成

2021-06-30

8:00-12:00

设计CommonIntervalSet的接口,并完成测试和实现

完成

2021-07-01

8:00-24:00

完成DutyRoster

发现代码需要重构,未完成

2021-07-02

13:00-17:00

实现并调试DutyRoster

完成

2021-07-02

19:00-21:00

实现并调试Process

完成

2021-07-03

9:00-14:30

实现并调试Course

完成

5 实验过程中遇到的困难与解决途径

遇到的难点

解决途径

计算日期/处理日期相关数据

使用Calendar类

正则表达式匹配

百度相关内容,请教同学

 

 

6 实验过程中收获的经验、教训、感想

6.1 实验过程中收获的经验和教训

(1)  过早优化是万恶之源,会把代码结构弄乱,并且会不断重构。

(2)  为了避免重构,需要在开发初期仔细设计好接口以及规范。尽量降低代码耦合度。

6.2 针对以下方面的感受

(1)  重新思考Lab2中的问题:面向ADT的编程和直接面向应用场景编程,你体会到二者有何差异?本实验设计的ADT在三个不同的应用场景下使用,你是否体会到复用的好处?

应用场景变化较多,需要考虑的情形较之于面向ADT编程要复杂。复用的好处在于可以减少代码量,提高开发效率和维护性。

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

意义:防止在复杂的应用场景中出现复杂且难以调试的错误。

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

难处在于需要精心设计API才可以使适用范围尽可能扩大。

乐趣在于API可以带来效率提升。

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

语法驱动编程比起传统方式较为高效,可以快速准确地读入数据。

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

难度:设计规约。设计欠周的规约在后期会带来不便,且经常需要重新设计规约,将已有设计推翻重来。

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

经验:尽可能将共用方法放在上层,个性方法放在下层。

教训:1.使用恰当的设计模式,不可滥用设计模式 2. 设计接口时考虑后期拓展的可能性,尽量避免不得不重构代码的情况。

(7)  关于本实验的工作量、难度、deadline。

如果是在平常情况下,工作量和难度均适中,但是实验所在的时间段临近期末,综合考虑来说时间非常不够用。

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

这门课讲了一些实用的编程技巧以及指导思想,对以后编程开发有帮助。但是课时被压缩之后内容不完整。

上一篇:软件构造——不变量相关知识点


下一篇:关于Java ADT的学习总结