JDK设计模式(八)装饰模式

1、定义

动态地将责任附加到对象上,若要扩展功能,装饰者提供了比继承更有弹性的替代方案。

2、解决的问题

在不必改变原类文件和使用继承的情况下,动态的扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象,防止类继承带来的爆炸式增长。

3、模式中的角色

1、 抽象构件(Component)角色:给出一个抽象接口,以规范准备接收附加责任的对象。

2、具体构件(ConcreteComponent)角色:定义一个将要接收附加责任的类。

3、装饰(Decorator)角色:持有一个构件(Component)对象的实例,并定义一个与抽象构件接口一致的接口。

4、具体装饰(ConcreteDecorator)角色:负责给构件对象“贴上”附加的责任。

4、模式解读

装饰模式的类图如下所示

 

Decorator通过operation(),调用ConcreteComponentoperation方法。

具体ConcreteDecoratorComponent添加额外的行为,在operation方法中,先调用decoratoroperation方法,然后添加新的行为addedBehavior2

5JDK涉及到的设计模式

装饰模式在Java语言中的最著名的应用莫过于Java I/O标准库的设计了。由于Java I/O库需要很多性能的各种组合,如果这些性能都是用继承的方法实现的,那么每一种组合都需要一个类,这样就会造成大量性能重复的类出现。而如果采用装饰模式,那么类的数目就会大大减少,性能的重复也可以减至最少。因此装饰模式是Java I/O库的基本模式。Java I/O库的对象结构图如下,由于Java I/O的对象众多,因此只画出InputStream的部分。

 

抽象构件(Component)角色,由InputStream扮演,为各种子类型提供统一的接口。具体构件(ConcreteComponent)角色:由ByteArrayInputStreamFileInputStreamPipedInputStreamStringBufferInputStream等类扮演,实现了抽象构件角色所规定的接口。抽象装饰(Decorator)角色,由FilterInputStream扮演,实现了InputStream所规定的接口。具体装饰(ConcreteDecorator)角色,由几个类扮演,分别是BufferedInputStreamDataInputStream以及两个不常用到的类LineNumberInputStreamPushbackInputStream

6、模式总结

半透明的装饰模式

装饰模式和适配器模式都是“包装模式(Wrapper Pattern)”,它们都是通过封装其他对象达到设计的目的的,但是它们的形态有很大区别。

理想的装饰模式在对被装饰对象进行功能增强的同时,要求具体构件角色、装饰角色的接口与抽象构件角色的接口完全一致。而适配器模式则不然,一般而言,适配器模式并不要求对源对象的功能进行增强,但是会改变源对象的接口,以便和目标接口相符合。

装饰模式有透明和半透明两种,这两种的区别就在于装饰角色的接口与抽象构件角色的接口是否完全一致。透明的装饰模式也就是理想的装饰模式,要求具体构件角色、装饰角色的接口与抽象构件角色的接口完全一致。相反,如果装饰角色的接口与抽象构件角色接口不一致,也就是说装饰角色的接口比抽象构件角色的接口宽的话,装饰角色实际上已经成了一个适配器角色,这种装饰模式也是可以接受的,称为“半透明”的装饰模式,如下图所示。

 

优点

1、 装饰模式与继承关系的目的都是要扩展对象的功能,但是装饰模式可以提供比继承更多的灵活性。装饰模式允许系统动态决定“贴上”一个需要的“装饰”,或者除掉一个不需要的“装饰”。继承关系则不同,继承关系是静态的,它在系统运行前就决定了。

2、通过使用不同的具体装饰类以及这些装饰类的排列组合,设计师可以创造出很多不同行为的组合。

缺点

    由于使用装饰模式,可以比使用继承关系需要较少数目的类。使用较少的类,当然使设计比较易于进行。但是,在另一方面,使用装饰模式会产生比使用继承关系更多的对象。更多的对象会使得查错变得困难,特别是这些对象看上去都很相像。

适用场景

1、 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。

2、处理那些可以撤消的职责。

3、当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展,为支持每一种组合将产生大量的子类,使得子类数目呈爆炸性增长。另一种情况可能是因为类定义被隐藏,或类定义不能用于生成子类。