实验目的:
根据实验手册简要撰写。
是编写具有可复用性和可维护性的 软件,主要使用以下软件构造技术:
⚫ 子类型、泛型、多态、重写、重载
⚫ 继承、代理、组合
⚫ 语法驱动的编程、正则表达式
⚫ API 设计、API 复用 本次实验给定了三个具体应用(值班表管理、操作系统进程调度管理、大学 课表管理),学生不是直接针对每个应用分别编程实现,而是通过 ADT 和泛型等 抽象技术,开发一套可复用的 ADT 及其实现,充分考虑这些应用之间的相似性 和差异性,使 ADT 有更大程度的复用(可复用性)和更容易面向各种变化(可 维护性)。
简要陈述你配置本次实验所需环境的过程,必要时可以给出屏幕截图。
特别是要记录配置过程中遇到的问题和困难,以及如何解决的。
建立一个项目,统一向 GitHub 仓库提交。
在这里给出你的GitHub Lab3仓库的URL地址(Lab3-学号)。
https://github.com/ComputerScienceHIT/lab3-reusability-and-maintainability-programming-2021-1190202109.git
请仔细对照实验手册,针对每一项任务,在下面各节中记录你的实验过程、阐述你的设计思路和问题求解思路,可辅之以示意图或关键源代码加以说明(但千万不要把你的源代码全部粘贴过来!)。
简要介绍三个应用。
分析三个应用场景的异同,理解需求:它们在哪些方面有共性、哪些方面有差异。
值班表管理:一个单位有 n 个员工,在某个时间段内每天安排唯一一个员工在单位 值班,且不能出现某天无人值班的情况;每个员工若被安排值班 m 天 (m>1),那么需要安排在连续的 m 天内。值班表内需要记录员工的名字、职位、手机号码。
操作系统管理:计算机上有一个单核 CPU,多个进程被操作系统创建出来,它们被调度在 CPU 上执行,由操作系统决定在各个时段内执行哪个线程。操作系统可挂起某个正在执行 的进程,在后续时刻可以恢复执行被挂起的进程。可知:每个时间只能 有一个进程在执行,其他进程处于休眠状态;一个进程的执行被分为多 个时间段;在特定时刻,CPU 可以“闲置”,意即操作系统没有调度执行任何进程;
大学课表管理::针对某个班级,假设其各周的课表都是完全一样的(意即 同样的课程安排将以“周”为单位进行周期性的重复,直到学期结束); 一门课程每周可以出现 1 次,也可以安排多次(例如每周一和周三的“软 件构造”课)且由同一位教师承担并在同样的教室进行;允许课表中有 空白时间段(未安排任何课程);考虑到不同学生的选课情况不同,同一个时间段内可以安排不同的课程(例如周一上午 1-2 节的计算方法和软 件构造);一位教师也可以承担课表中的多门课程。
共性:了具有不同特征的“时间段集合”对象,都是进行对时间段的分配
差异:安排时间的格式不同 安排的对象不同 安排时间的规则不同
在应用 1 中,不允许有空白;在应用 2 和应用 3 中,允许有 空白
在应用 1 和应用 2 中,不允许有重 叠;在应用 3 中,允许有重叠
应用 3 中以“一周”为单位重复某个课程,但应 用 1 和应用 2 中不存在这种情况
-
- 面向可复用性和可维护性的设计:IntervalSet<L>
该节是本实验的核心部分。
-
-
- IntervalSet<L>的共性操作
-
1.public static <L> IntervalSet<L> empty()
创建一个空白的IntervalSet<L>;
2.public void checkRep()
对于我们实现IntervalSet<L>的两种方法有不同的形式化方法
RepListIntervalSet<L>
private final List<L> labelList = new ArrayList<>();
private final List<Long> valueList = new ArrayList<>();
在RepListIntervalSet<L>中我们通过创建上述的List来记录label和其开始与结束时间
在chepRep()中我们需要保证labelList.size()*2 == valueList.size()
同样的我们需要保证对于每一段时间内其终止时间的值要大于其初始时间
RepMapIntervalSet<L>
private final Map<L, Long> startMap = new HashMap<>();
private final Map<L, Long> endMap = new HashMap<>();
在RepMapIntervalSet<L>中我们通过创建上述的Map来记录label和其开始与结束时间
在chepRep()中我们需要保证startMap.size()==endMap.size()
同样的我们需要保证对于每一段时间内其终止时间的值要大于其初始时间
3.public void insert(long start, long end, L label) throws IntervalConflictException;
分别将时间段与相应的label传入对应的List和Map中
4.public Set<L> labels();
Set<L> labels = new HashSet<L>();
遍历labellist并将其添加到labels中并返回labels
5.public boolean remove(L label)
若labellist并没有label则返回false;
否则 则分别从labellist和valuelist中移除label和其对应的时间段
6.public long start(L label) throws NoSuchElementException
通过遍历找出label对应的i并返回valueList.get(2*i)
7.public long end(L label) throws NoSuchElementException
通过遍历找出label对应的i并返回valueList.get(2*i+1)
8.public String toString()
遍历labels获得相应的i并将其通过格式加入到str中
str = str + labelList.get(i) + "=" + "[" + Long.toString(valueList.get(2*i)) + "," + Long.toString(valueList.get(2*i+1)) + "]" + "\n";
CommonIntervalSet<L> 局部共性的特征值
我们需要将共性的特性填入到CommonIntervalSet<L>中
我们添加到CommonIntervalSet<L>的特性包括:
public void checkRep()
public void insert(long start, long end, L label) throws IntervalConflictException
public Set<L> labels()
public boolean remove(L label)
public long start(L label) throws NoSuchElementException
public long end(L label) throws NoSuchElementException
我们将上述的共性的特性提取到CommonIntervalSet<L>中
-
-
- 面向各应用的IntervalSet子类型设计(个性化特征的设计方案)
-
关于个性化特征的设计我选择了第六种方法——使用了装饰器来解决问题
以下为各个装饰器:
NoBlankIntervalSet<L> 无空白
对于不允许有空白的特性我们需要在进行完所有的输入后检查
public boolean Isblank(long start,long end)
我们遍历labellist找到以start为初始时间的对象,并将start赋值为该对象的终止时间且flag=1;
若遍历完并未找到此对象则返回true;
我们会一直循环直到start = end;
NonOverlapIntervalSet<L> 无重叠
需要在checkRap()中添加对于重叠的检测
public void checkRep()
在原有的interval.checkRap的基础上需要添加新的功能
补充部分:
遍历labellist 并记下当前对象a的start和end值
再次遍历labelist 当前对象的start值需要大于等于a的end值
当前对象的end值需要小于等于a的start值
NonPeriodicIntervalSet<L> 无重复
需要在checkRap()中添加对于重复的检测
public void checkRep()
在原有的interval.checkRap的基础上需要添加新的功能
补充部分:
遍历labellist 并记下当前对象a的start和end值
再次遍历labelist 当前对象的Start值不能等于start值
End值不能等于end
面向应用的个性化处理
public class DutyIntervalSet
public class ProcessIntervalSet
public class CourseIntervalSet
-
- 面向可复用性和可维护性的设计:MultiIntervalSet<L>
- MultiIntervalSet<L>的共性操作
- 面向可复用性和可维护性的设计:MultiIntervalSet<L>
private ArrayList<IntervalSet<L>> intervalSetList;
将所有的interval填入到上述的list中
public MultiIntervalSet()
返回空白的MultiIntervalSet<L>
public MultiIntervalSet(IntervalSet<L> initial)
将以建立的intervalset填入到MultiIntervalSet<L>
MultiIntervalSet<L>可以继承intervalset内的内容
共性操作:
1.public void insert(long start, long end, L label) throws IntervalConflictException
IntervalSet<L> n = IntervalSet.empty();
申请一个新的intervalset 并使用interlstet的insert操作将所得的信息填入到n中
将n加入到intervalSetList中
2.public boolean removeAll(L label)
移除所有以label作为标志的interval
遍历intervalsetlist 找出以label为标志的intervalset并通过remove()进行移除
3.public boolean removeAll(L label)
Set<L> labels = new HashSet<L>()
将所有的Intervlaset填入到labels中
4.public IntervalSet<Integer> intervals(L label) throws NoSuchElementException, IntervalConflictException
IntervalSet<Integer> interval = IntervalSet.empty();
将新建立的interval添加到上述的IntervalSet<Integer>中
遍历intervlasetlist找到所有以label为标志的interval并将其start和end值填入到上述的IntervalSet<Integer>中
其标志应该是从0开始的整数依次增加一
5.public String toString()
输出intervalsetlist的所有的信息
CommonIntervalSet<L> 局部共性的特征值
我们需要将共性的特性填入到CommonIntervalSet<L>中
我们添加到CommonIntervalSet<L>的特性包括:
public MultiIntervalSet()
public MultiIntervalSet(IntervalSet<L> initial)
public void insert(long start, long end, L label) throws IntervalConflictException
public boolean removeAll(L label)
public Set<L> labels()
public IntervalSet<Integer> intervals(L label) throws NoSuchElementException, IntervalConflictException
-
-
- 面向各应用的MultiIntervalSet子类型设计(个性化特征的设计方案)
-
关于个性化特征的设计我选择了第六种方法——使用了装饰器来解决问题
以下为各个装饰器:
NoBlankIntervalSet<L> 无空白
对于不允许有空白的特性我们需要在进行完所有的输入后检查
public boolean Isblank(long start,long end)
我们遍历labelsetlist找到以start为初始时间的inetval,并将start赋值为该对象的终止时间且flag=1;
若遍历完并未找到此对象则返回true;
我们会一直循环直到start = end;
NonOverlapIntervalSet<L> 无重叠
需要在checkRap()中添加对于重叠的检测
public void checkRep()
在原有的interval.checkRap的基础上需要添加新的功能
补充部分:
遍历labelsetlist 并记下当前intervla a的start和end值
再次遍历labelist 当前对象的start值需要大于等于a的end值
当前对象的end值需要小于等于a的start值
NonPeriodicIntervalSet<L> 无重复
需要在checkRap()中添加对于重复的检测
public void checkRep()
在原有的interval.checkRap的基础上需要添加新的功能
补充部分:
遍历labelsetlist 并记下当前intervla a的start和end值
再次遍历labelsetist 当前interval的Start值不能等于start值
End值不能等于end
IntervalSet和 MultiIntervalSet中的泛型参数 L,可以是任何 immutable 的类。
以下为我所设计的class类:
Employee
private String name; 员工的姓名
private String job; 员工的工作
private String phonenumber; 员工的电话号码
实现的功能:
public String getname() 获取相应的name
public String getjob() 获取相应的工作职位名称
public String getphonenumber() 获取相应的电话号码
Process
private String ID; 进程的ID
private String name; 进程的名称
private long shortest; 进程的最短运行时间
private long longest; 进程的最长运行时间
实现的功能:
public String getname() 获取相应的名称
public String getID() 获取相应的进程ID
public long shortest() 获取相应的最短运行时间
public long longest() 获取相应的最长是时间
Course
private String ID;
private String name;
private String teachername;
private String position;
private long sumtimes;
实现的功能:
public String getID() 获取相应的课程ID
public String getname() 获取相应的课程名称
public String getteachername() 获取相应的教师名称
public String getposition() 获取相应的位置
public long getsumtimes() 获取相应的周学时
public double similarity(MultiIntervalSet<L> a, MultiIntervalSet<L> b) throws IntervalConflictException, IOException
Set<L> labelListA = a.labels();
Set<L> labelListB = b.labels();
Triple构造如下:
class Triple<L> {
private L start;
private L end;
private Double value;
public Triple(L splitLine,L splitLine2,Double value) {
this.setStart(splitLine);
this.setEnd(splitLine2);
this.setValue(value);
}
public L getStart() {
return start;
}
public void setStart(L start) {
this.start = start;
}
public L getEnd() {
return end;
}
public void setEnd(L splitLine2) {
this.end = splitLine2;
}
public Double getValue() {
return value;
}
public void setValue(Double value) {
this.value = value;
}
1.遍历labelListA并将其所有的label填入到Triple<L>
Triple的start与end值为label 其value值为1.0
2.需要分别遍历labelListA和labelListB找出最小的start值和最大的end值
3.当labelListA和labelListB均拥有某一个label时我们需要计算其重合部分的值
4.在我们计算重合部分时会出现以下几种情况:A被B包含 B被A包含 或互相不包含 我们可以分别计算这几种情况下的重合量
5.最终返回return ansDouble/(maxDouble-minDouble);
public double calcConflictRatio(IntervalSet<L> set)
Set<L> labelList = set.labels();
- 通过遍历labellist找出最小的start值与end值
- 遍历labellist记录相应的start和end值
min = set.start(a);
max = set.end(a);
再次遍历labellist找出start值等于min并且end值等于max的对象
- 返回return ansDouble/(maxDouble-minDouble);
public double calcFreeTimeRatio(IntervalSet<L> set)
Set<L> labelList = set.labels();
1.通过遍历labellist找出最小的start值与end值
2.遍历labellist记录相应的start和end值
min = set.start(a);
max = set.end(a);
通过max-min算出已经能加入的时间段的长度
3.返回return 1-ansDouble/(maxDouble-minDouble);
利用上述设计和实现的ADT,实现手册里要求的各项功能。
public class DutyRosterApp
public static long start_time;
public static long end_time;
static IntervalSet<String> Duty=DutyIntervalSet.Create();
static Set<Employee> employees=new HashSet<>();
static Free<String> free = new Free<String>();
实现的功能:
public static void SetStart_End(long start,long end)
设置start_time,end_time;
public static void addEmployee(String name, String job, String phonenumber)
Employee employee=new Employee(name, job, phonenumber);
创建一个新的Employee 将Employee填入到employees中
public static void deleteEmployee(String name)
遍历值班表删除名称为name的职工的日程安排
再遍历employees从中删除名称为name的Employee
public static boolean addDuty(Employee employee,long start,long end) throws IntervalConflictException
若日程表中无空闲位置则直接返回false;
若在日程表中已进行该职员的安排则直接返回false;
否则 填入到日程表中 并且展示空白的日期与空白比例
返回true;
public static void RandomDuty(long start_time,long end_time) throws IntervalConflictException
遍历employees向其中加入随机设定的日期安排
long rangeLong = start + (((long) (new Random().nextDouble() * (end - start))));
Duty.insert(start, rangeLong, e.getname());
start = rangeLong;
public static void tostring()
显示已经进行安排的日程表的信息
public class ProcessScheduleApp
static IntervalSet<String> process=ProcessIntervalSet.Create();
static List<Process> processes =new ArrayList<>();
static List<Process> finished =new ArrayList<>();
static Free<String> free = new Free<String>();
static MultiIntervalSet<String> ProcessSchedule = new MultiIntervalSet<String>(process);
public static void addProcess(String ID, String name, long shortest, long longest)
Process process =new Process(ID,name,shortest,longest)
创建新的process
将process添加到processes中
Process process =new Process(ID,name,shortest,longest)
int i = new Random().nextInt(processes.size());
通过随机数生成的方法来确定将要运行的进程
若将要运行的进程已经运行完毕则直接返回false
long longest = processes.get(i).longest();
long j = 0 + (((long) (new Random().nextDouble() * (longest - 0))));
同样通过随机数生成的方法来确定运行的时间(以最长运行时间作为界限)
若该进程已经完成则直接添加到finished行列中
返回true
public static boolean runshortProcess()
通过遍历processes来选择距离其最大执行时间差距最小的进程
若将要运行的进程已经运行完毕则直接返回false
long longest = processes.get(i).longest();
long j = 0 + (((long) (new Random().nextDouble() * (longest - 0))));
同样通过随机数生成的方法来确定运行的时间(以最长运行时间作为界限)
若该进程已经完成则直接添加到finished行列中
返回true
public class CourseScheduleApp
static MultiIntervalSet<String> CourseSchedule = new MultiIntervalSet<String>();
public static long start_time;
public static long weeks;
static Set<Course> courses =new HashSet<>();
static Free<String> free = new Free<String>();
static conflict<String> conflict = new conflict<String>();
实现的功能:
public static void addCourse(String ID, String name, String teachername,String position,long sumtimes)
Course course =new Course(ID,name, teachername, position,sumtimes);
建立一个新的Course
将course填入到cuorses中
public static boolean addSchedule(Course course,long start,long end) throws IntervalConflictException
若课程的周学时已经结束则不能继续添加并返回false;
想课程表中添加新的课程安排
返回true;
public static void printstate() throws IntervalConflictException
输出当前每周的空闲时间比例和重复时间比例
public static void check(long data) throws NoSuchElementException, IntervalConflictException
通过遍历课程表找出在相应日期并且符合课时标准的所有的课程
以标准化的方法输出结果
修改“排班管理”应用以扩展该功能。
public static void readfile(String filepath) throws IntervalConflictException
通过InputStream来读取文件
String phonenumber="[1][0-9]{2}(-)?[0-9]{4}(-)?[0-9]{4}";//电话号码
String date="[0-9]{4}[-][0-9]{1,2}[-][0-9]{1,2}";//日期
String period="Period\\{"+date+","+date+"\\}"; //Period
String EmployeeInfo="[a-zA-Z]+\\{[a-zA-Z\\s]+,"+phonenumber+"\\}";//Employee
String DutyInfo="[a-zA-Z]+\\{"+date+","+date+"\\}";//Roster
以上为用来进行匹配的正则表达式
通过Matcher匹配器来进行符合标准的正则表达式的字符串
通过获取的信息来完成开始与终止时间的,员工信息,值班表的录入
评估之前的设计是否可应对变化、代价如何 可以进行变化 代价较小
如何修改设计以应对变化
先修改在添加值班表部分的判断
其次要将完成的Interval添加到MultiIntervalSet中
-
-
- 变化2
-
评估之前的设计是否可应对变化、代价如何 可以进行变化 代价较小
如何修改设计以应对变化
我们可以利用我们构造的装饰器来进行装饰
-
- Git仓库结构
请在完成全部实验要求之后,利用Git log指令或Git图形化客户端或GitHub上项目仓库的Insight页面,给出你的仓库到目前为止的Object Graph,尤其是区分清楚change分支和master分支所指向的位置。