decorator 设计模式
在OOP的学习中,有种软件设计模式是 decorator 设计模式,而且在之后的实验中用到了。当时感觉不是很好理解,后来通过查阅资料有了一点感悟,将其记录如下。
decorator 设计模式能够向一个现有的对象添加修饰的设计和新的功能,同时又可以不改变对象原本的结构。这种类型的设计模式属于结构型模式,它是作为现有的类的一个包装。这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。
decorator 模式的结构与实现
- 模式的结构
装饰器模式结构主要包含以下部分。
抽象构件:定义一个抽象接口以规范准备接收附加责任的对象。
具体构件:实现抽象构件,通过装饰角色为其添加一些职责。
抽象装饰:继承抽象构件,并包含具体构件的实例,可以通过其子类扩展具体构件的功能。
具体装饰:实现抽象装饰的相关方法,并给具体构件对象添加附加的责任。
如上图所示,最顶层的是一个抽象构件Component,是一个规范准备接收附加责任对象的抽象接口。而ConcreteComponent是实现上面抽象接口的部件,实现了我们的基本功能。Decorator就是抽像装饰,它相当于继承了Component,包含具体构件的实例,内部完全调用了父类进行实现,并没有增加装饰。因为实现抽象装饰的相关方法要在Decorator的子类ConcreteDecotator中实现。
下面通过实验中一个实例具体讲解。
实验要完成一个 mutable 的 ADT,描述了一组在时间轴上分布的“时间段”(interval),每个时间段附着一个特定的标签,且标签不重复。
例如:下图的时间段集合可表示为{ A=[0,5], B=[10,30], C=[30,35] }。在该例子中,标签是简单的字符串(“A”、“B”、“C”),但实际应用中标签可以是任何 immutable 类型的 ADT,也就是 IntervalSet中的 L。
这是抽象接口的代码相当于抽象构件(为了方便说明,以下代码有很多简化)
public interface IntervalSet<L> {
public static <L> IntervalSet<L> empty() {
return new CommonIntervalSet<L>();
}
/**
* 插入标签和时间段
*/
public void insert(long start, long end, L label);
/**
*
* @return 标签集
*/
public Set<L> labels();
/**
* 对对应标签进行删除
* @param 标签
* @return 成功删除返回true,否则返回false
*/
public boolean remove(L label);
/**
*
* @param 标签
* @return 对应标签开始时间
*/
public long start (L label);
/**
*
* @param 标签
* @return 对应标签结束时间
*/
public long end (L label);
}
抽象构件由具体构件来实现。
public class CommonIntervalSet<L> implements IntervalSet<L> {
private final Map<L, Time> itmap = new HashMap<>();
/**
*
*/
public CommonIntervalSet() {
// TODO 自动生成的构造函数存根
}
/**
* 插入标签和时间段
*/
@Override
public void insert(long start, long end, L label) {
if (start < 0 || start > end) {
return;
}
if (itmap.containsKey(label)) {
System.out.println("已存在");
} else {
Time time = new Time(start, end);
itmap.put(label, time);
}
}
@Override
public Set<L> labels() {
return itmap.keySet();
}
@Override
public boolean remove(L label) {
// TODO 自动生成的方法存根
if (itmap.containsKey(label)) {
itmap.remove(label);
return true;
}
return false;
}
@Override
public long start(L label) {
if (itmap.containsKey(label)) {
return itmap.get(label).getstart();
}
return -1;
}
@Override
public long end(L label) {
if (itmap.containsKey(label)) {
return itmap.get(label).getend();
}
return -1;
}
}
}
但是这个具体构件是允许时间上有重叠的,像实现时间上无重叠就不行了。这时候想不改变原来的实现,就要用到装饰器。
定义一个抽象装饰继承抽象构件,完全用抽象构件的实现方法。
public abstract class IntervalSetDecorator<L> implements IntervalSet<L> {
protected final IntervalSet<L> intervalSet;
public IntervalSetDecorator(IntervalSet<L> in) {
this.intervalSet=in;
}
/**
* 插入标签和时间段
*/
@Override
public void insert(long start, long end, L label) {
intervalSet.insert(start, end, label);
}
@Override
public Set<L> labels() {
return intervalSet.labels();
}
@Override
public boolean remove(L label) {
return intervalSet.remove(label);
}
@Override
public long start(L label) {
return intervalSet.start(label);
}
@Override
public long end(L label) {
return intervalSet.end(label);
}
定义具体装饰继承抽象装饰来实现我们的新增功能。
可以看到,通过一个HashMap在insert中增加了判断时间是否有覆盖的功能,如果覆盖就进行提示,不再插入。删除时记HashMAp中对应的标签也要删除,否则再添加时可能会发生时间冲突的错误。
public class NonOverlapIntervalSet<L> extends IntervalSetDecorator<L> implements IntervalSet<L> {
private final Map<L, Time> itMap = new HashMap<>();
public NonOverlapIntervalSet(IntervalSet<L> in) {
super(in);
// TODO 自动生成的构造函数存根
}
/**
* 插入标签和时间段
*/
@Override
public void insert(long start, long end, L label) {
for (L lab : itMap.keySet()) {
Time time = itMap.get(lab);
if (start >= time.getend() || end <= time.getstart())
continue;
else {
System.out.println("时间冲突");
System.out.println(lab.toString() + time.toString());
System.out.println(label.toString());
return;
}
}
super.insert(start, end, label);
itMap.put(label, new Time(start, end));
}
@Override
public boolean remove(L label) {
// TODO 自动生成的方法存根
if (itMap.containsKey(label)) {
super.remove(label);
itMap.remove(label);
return true;
}
return false;
}
}
这样装饰器模式的结构就实现了。
具体用法
IntervalSet<L> duty = new CommonIntervalSet<>();
duty = new NonOverlapIntervalSet<>(duty);
duty = new BlankIntervalSet<>(start, end, duty);
使用方法就是先用抽象构件定义ADT用具体构件实现IntervalSet duty = new CommonIntervalSet<>();此时duty实现的就是允许时间重叠。加上修饰duty = new NonOverlapIntervalSet<>(duty);,此时duty就变成不允许时间重叠了。如果还有其他修饰也可继续添加,如上所示。
装饰器优缺点
1.优点
装饰器是继承的有力补充,比继承灵活,在不改变原有对象的情况下,动态的给一个对象扩展功能,即插即用
通过使用不用装饰类及这些装饰类的排列组合,可以实现不同效果。
2.缺点
装饰器模式会增加许多子类,过度使用会增加程序得复杂性。