软件构造LAB3总结
1.实验概述
本次实验要求编写可复用性和可维护性的软件。涉及到子类型、泛型、多态、重写、重载、继承、代理、组合语法驱动的编程 、 正则表达式API设计 、 API复用等基本内容。通过 ADT和 泛型等抽象技术,开发一套可复用的 ADT及其实现,完成三个具体应用(值班表管理、 操作系统进程 调度管理 、大学课表管理)。
2.设计模式
在这个实验中,选择了委托的设计模式。通过接口组合实现局部共性特征的复用。每个维度分别定义自己的接口,针对每个维度不同特征取值,分别实现针对该维度接口的不同实现类,实现特殊逻辑操作。
通过接口组合,将局部共性行为组合在一起,形成满足每个应用要求的特殊接口。
对于应用2,使用MultiIntervalSet作为父类建立ProcessIntervalSet子类。
首先建立IProcessIntervalSet接口,这个接口继承NonOverlapIntervalSet<L>,BlankIntervalSet<L>, NonPeriodicIntervalSet<L>三个接口,即实现时间段无重叠,允许空闲时间以及没有周期性时间段三个特殊的要求。然后建立ProcessIntervalSet类继承MultiIntervalSet,并实现IProcessIntervalSet接口。接下来建立委托关系:
private NonOverlapIntervalSet<L> nois;
private BlankIntervalSet<L> bis;
private NonPeriodicIntervalSet<L> npis;
public ProcessIntervalSet(NonOverlapIntervalSet<L> nois,BlankIntervalSet<L> bis,NonPeriodicIntervalSet<L> npis)
{
this.bis = bis;
this.nois = nois;
this.npis = npis;
}
然后委托调用,完成特殊功能的实现:
public boolean insert(MultiIntervalSet<L> multiintervalset, long start, long end, L label) {
try{
return nois.insert(multiintervalset, start, end, label);
}catch(OverlapException e){
System.out.println("同一时间不允许允许多个进程!");
return false;}
对于应用3,使用MultiIntervalSet作为父类建立CourseIntervalSet<L>子类。
首先建立ICourseIntervalSet接口,这个接口继承OverlapIntervalSet<L>, BlankIntervalSet<L>, PeriodicIntervalSet<L>三个接口,即实现时间允许重叠,允许空闲时间以及有周期性时间段三个特殊的要求。建立如下的委托关系:
public CourseIntervalSet(OverlapIntervalSet<L> ois,BlankIntervalSet<L> bis,PeriodicIntervalSet<L>pis){
this.ois = ois;
this.bis = bis;
this.pis = pis;
}
3.局部共性实现
对于三个层次的不同,主要主要包含六个方面。这里采用的是方案5,所以设计了6个接口分别对应三个层次6中情况。
1.无空闲时间
为此定义接口NoBlankIntervalSet,接口中定义了方法
public List<interval> checkNoBlanck(IntervalSet<L> intervalset,long allstart,long allend);
这个方法检查intervalset中是否有空白,然后返回空白段。若时间轴设置了开始和技术时间,空白段的计算将会考虑这两个参数。若时间轴没有设置开始和结束时间,将这两个参数设置为-1可以忽略。
检查空白段的方法为,使用getintervalset得到所有时间段的集合。将所有开始时间(start)从小到大放入一个List,将所有结束时间(end)从小到大放入一个List。根据这两个集合,计算空白块。判断的方法为,若第i+1个start大于第i个end则存在空白快,将这个空白块加入要返回的空白块集合。此外,考虑是否给定了时间轴的开始时间和结束时间,否则以第一个开始时间作为时间轴的起始。
Collections.sort(starts);
Collections.sort(ends);//排序时间
for(int i =0;i<ends.size()-1;i++){//遍历
if(starts.get(i+1).longValue()>ends.get(i).longValue())
blank.add(new interval(ends.get(i).longValue(),starts.get(i+1).longValue());}
原理:若有空白块存在,则必然是前一个时间段的end与后一个时间段的start之间存在距离,且这一段时间轴上没有其他的时间段。当第i个end出现的时候,前面必然有i个start已经出现,也就是从end(i)往前有i个时间段。start(i+1)比end(i)大,说明start(i+1)也就是第i+1个时间段出现在前i个时间段之后,且与前i个时间段没有交集。
此外,NoBlankIntervalSetImpl中还提供了获得时间轴的总开始时间和结束时间的方法。
2.允许有空闲时间
为此定义接口BlankIntervalSet。
有空闲时间就是对时间段是否空白不加约束,所以这个类不对IntervalSet进行约束。
3.不允许时间段重叠
为此定义NonOverlapIntervalSet接口并定义:
public boolean insert(IntervalSet<L> intervalset,long start,long end,L label)throws OverlapException
这个方法是插入前的一个检查方法。当要插入一个时间段时,判断这个时间段是否和时间轴上的时间段重合。若时间段重合,则返回false,并且不执行插入操作;若时间段不重合,则返回true并执行insert操作。
检查是否有重叠的方法为,调用getintervalset得到所有时间段的集合。遍历这个集合,若待插入时间段的开始时间落在已有时间段的开始和结束时间之间,则说明待插入时间段和已有的时间段有重合,返回false,否则返回true并执行插入操作。
for(interval in:iset)
{
if(in.getStart()<=start&&in.getEnd()>start)
return false;//有重叠
}
4.允许时间段重叠
为此定义接口OverlapIntervalSet。允许时间重叠但是不允许相同标签有重叠的时间段。但是在IntervalSet的设计中已经考虑到这一点,IntervalSet一个标签只有一个时间段,所以不存在上述情况。这个仅仅是简单的执行insert方法。
对于MultiIntervalSet定义接口OverlapIntervalSet。允许时间重叠但是不允许相同标签有重叠的时间段。所以需要检查要插入的时间段是否和已有的标签对应的时间段有重叠。若有重叠,则返回false且不执行插入操作,否则执行插入操作并返回true。
5.有周期性时间段
为此定义接口PeriodicIntervalSet,接口中对于IntervalSet不定义方法。因为IntervalSet每个标签只有一个时间段,所以无法实现周期性的时间。需要使用MultiIntervalSet。
为此定义接口PeriodicIntervalSet,接口中定义方法public IntervalSet<L> extension(IntervalSet<L> intervalset, long Time, int n)这个方法对有周期性时间段的对象进行周期性的扩展。其中,Time时周期,n是周期数,返回拓展后的对象。
具体的方法是遍历待扩展对象的标签元素以及对应的时间段。对每个时间段进行周期性扩展,即将时间段的开始时间和结束时间增加n个周期,然后再写入到时间轴上。
6.无周周期性时间段
为此定义接口NonPeriodicIntervalSet,但是不定义实现方法。
4.API中计算冲突时间比例方法
4.1问题描述
发现 一个 IntervalSet L 或 MultiIntervalSet < 对象 中的时间冲突比例。所谓的“冲突”,是指
同一个时间段内安排了两个不同的 interval 对象。 用发生冲突的时间段总长度除于总长度,得到冲突比例,是一个 0-1 之间的值。
4.2问题分析
需要两个值,一个是冲突时间长度,一个是时间轴的总长度。时间轴的总长度很好确定,需要解决冲突时间长度问题。因为是可重叠的时间段,所以可能有三个以上时间段重叠在一起。这样,找到冲突的时间段是容易的,但是冲突的时间段之间仍然会冲突,所以就需要计算冲突时间段的有效长度,这个是解决问题的关键。
4.3方法实现
首先使用getintervalset得到所有时间段,然后按照与上一节相似的方法,遍历这个时间段集合,得到这个时间段集合的冲突的时间段。将这些时间段放入一个interval的集合中。这个集合中的时间段仍然有可能是冲突的,所以要进行区间合并,去除重叠的时间段,得到真正有效的冲突时间集合。
区间合并的方法是,将时间段集合按照开始时间排列,然后检查前一个时间段和后一个时间段的冲突情况,前一个时间段与后一个时间段没有冲突,则将这两个时间段加入一个新的interval的集合nlist中;若两者产生了冲突,则计算这两个区间合并后的长度,插入到新的集合中。然后将这个新的时间段作为基准,再去检查后面的时间段,直到所有的时间段遍历完毕。
long min = conlist.get(0).getStart();
long max = conlist.get(0).getEnd();
for(int i=1;i<conlist.size();i++)
{
if(conlist.get(i).getStart()<=max)
{
max = conlist.get(i).getEnd()>max?conlist.get(i).getEnd():max;
}else {
nlist.add(new interval(min,max));
min = conlist.get(i).getStart();
max = conlist.get(i).getEnd();
}
}
nlist.add(new interval(min,max));
最后在nlist集合中计算有效冲突时间段的总长度,这个长度除以时间轴的总长度得到冲突比例。
5.APP细节
5.1时间点和时间段
App1的问题,App1给出的排班要求是按天排班,如果给A分配了1月1日,则B不能在1月1日值班,那么如何界定这个时间点。
假如给A安排了1月1日到1月2日,A的时间段不是从1月1日的开始到1月2日的开始,而是到其结束,应该安排到1月3号的开始前一秒。所以使用Date转换为long后,再增加一天时间,作为结束时间。
5.2非法格式识别
如何识别文本中的格式错误如名字中有数字,电话位数不对。
采用的办法是匹配所有合法内容,当某一行不能匹配的时候,就说明出现了非法格式,类型白名单。
6.总结感想
通过本次实验,了解了子类型、泛型、多态、重写、重载、继承、代理、组合、语法驱动的编程 、 正则表达式、API设计 、 API复用等内容。
ADT的设计良好与否,的的确确关系到后面对3个APP的开发。尤其是使用change改变。由于比较号的ADT及其子类的实现,change对于代码的改动非常少,只有几行。面向ADT的编程要考虑共性和差异,复用性和可维护性。比直接面向应用场景要困难。但是一个良好的ADT,能适用于多个场景。
下一篇: lab3可复用性和可维护性(1)