在Lab3中,我们需要实现可复用的类的设计,并实现三个具体的APP,这里指导书中给出了6种解决方案,我选择了装饰器模式进行解决,在本篇博客中,我将对装饰器模式的原理与应用进行简单的说明,希望能对其他刚了解装饰器模式的人有帮助。
装饰器模式原理
首先介绍一下什么是装饰器模式,首先,对于一个父类,我们可能需要很多具有不同功能的子类,而这些子类之间可能有许多个维度方面的不同,以我们Lab3中的内容为例,在Lab3中我们需要实现一个类似时间表的类,每个时间段有其对应的标签,而我们要实现的APP应用的具体子类之间会有三个维度的不同,比如有的子类需要时间轴上没有空的区间,有的子类要求时间轴上没有重叠的时段,有的子类则需要时间轴具有周期性,如果针对这三个维度实现所有可能出现的子类,我们共需要写2*2*2=8个子类,这样就出现了组合爆炸的问题。
此时我们可以使用装饰器模式,顾名思义,我们子类的特性通过一次一次的“修饰”增加到我们的基类上,那么如何实现修饰呢,我们首先要有一个基类实现接口,之后还需要一个用于装饰的基础类实现接口,这个装饰的基础类一般定义为抽象类,而其构造函数通过传入接口类型的变量初始化,这里传入接口类型是为了在装饰时,不仅可以装饰基础类,还可以装饰一个已经被装饰的类,这就导致了我们可以同时添加多个装饰。其他方法都通过委托给基础类实现即可。
之后我们就可以写具体的装饰类了,首先,装饰类要继承装饰的基础类,之后其构造函数要继承其父类的构造函数,再之后我们在装饰类中增加需要增加的函数,并重写需要增加功能的函数即可。注意重写时一般是先调用父类的函数,再在其基础上增加功能,这样能保证每次装饰增加的功能都能一层一层的套在最开始的函数上,而不是把之前的函数完全覆盖掉。
装饰模式的具体结构代码如下图所示
interface Stack {//定义接口
...
}
public class ArrayStack implements Stack {//接口的实现基类
...
}
public abstract class StackDecorator implements Stack {//定义用于装饰的基础类
protected final Stack stack;
public StackDecorator(Stack stack) {
this.stack = stack;
}
...//其中的方法通过委托给基类实现
}
public class UndoStack extends StackDecorator {
public UndoStack(Stack stack) {
super(stack);
}
}
装饰器模式应用
那么对于我们的Lab3,如何使用装饰类实现呢?由于根据实验指导书,我们需要实现两个接口,两个基类,在前一节中提到我们的APP有三个维度,即是否允许时间轴有空白,是否允许时间段重叠,是否具有周期性。然而对于其中一个接口IntervalSet,其要求每个标签只对应一个时间段,不可能具有周期性,则对于这个接口我们只需要实现两个实现类,而对于另一个接口我们需要实现三个实现类。那么我们通过现构造装饰基础类,再构造其子类作为装饰类,在子类中将其功能进行实现即可,这样通过构造类时传入不同的子类,完成不同的装饰组合。我们以IntervalSet在不可重叠方面的装饰类为例,其全部代码如下:
package IntervalSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.TreeSet;
public class NonOverlapIntervalSetImpl<L> extends IntervalSetImplDecorator<L> {
// Abstraction function:
// intervalSet是被装饰的时间表
// Representation invariant:
// intervalSet中的时间段不允许有重叠部分
// Safety from rep exposure:
// 从父类继承变量,为protected类型且是不可变的
/**
* 装饰器的构造函数
*
* @param intervalSet 用于初始化的IntervalSet
*/
public NonOverlapIntervalSetImpl(IntervalSet<L> intervalSet) {
super(intervalSet);
}
/**
* 检查表示不变性
*/
private void checkRep()
{
long nowtime=0;
int count=0;
HashSet<Interval> time = new HashSet<Interval>();
for (L label : intervalSet.labels()) {
Interval add = new Interval(intervalSet.start(label), intervalSet.end(label));
time.add(add);
}
TreeSet<Interval> timeLine = new TreeSet<Interval>(time);//得到排序后的时间轴
for (Interval now: timeLine){
if(count==0)
{
nowtime=now.getEnd();
count++;
continue;
}
assert nowtime<=now.getStart();
nowtime=now.getEnd();
count++;
}
}
/**
* 在当前的对象中增加新的时间段和标签
* 时间段之间不允许有重叠
*
* @param start 增加时间段的起始时间
* @param end 增加时间段的结束时间
* @param label 增加时间段的标签,不能和已有的标签重复
*/
@Override
public void insert(long start, long end, L label) {
for (L label0:this.intervalSet.labels())
{
if((start>=intervalSet.start(label0)&&start<intervalSet.end(label0))||(end>intervalSet.start(label0)&&end<=intervalSet.end(label0)))
throw new IllegalArgumentException("时间段不允许有重叠");
}
super.insert(start,end,label);
checkRep();
}
@Override
public String toString() {
return this.intervalSet.toString();
}
}
最后,在我们实现具体APP的类时,我们根据需求使用不同的构造方式即可,以第一个APP为例,其为IntervalSet(每个标签对应一个标签),且时间轴非空,不允许重叠,则其构造时代码如下即可:
private final IntervalSet<L> dutyTable = new NoBlankIntervalSetImpl<L>(new NonOverlapIntervalSetImpl<L>(new CommonIntervalSet<L>()));
这样其既具有非空的特性,又有不重叠的特性,相比直接构造子类大大减少了代码量,对于其他APP需要的类构造方法同理,之后我们利用其实现具体功能即可。