装饰器模式
解决问题
为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。
方案
动态地给一个对象添加一些额外的职责, 通过创建一个包装对象,也就是装饰来包裹真实的对象。
结构
抽象组件角色(Component):定义一个对象接口,以规范准备接受附加责任的对象,即可以给这些对象动态地添加职责。
具体组件角色(ConcreteComponent) :被装饰者,定义一个将要被装饰增加功能的类。可以给这个类的对象添加一些职责
抽象装饰器(Decorator):维持一个指向构件Component对象的实例,并定义一个与抽象组件角色Component接口一致的接口
具体装饰器角色(ConcreteDecorator):向组件添加职责。
public abstract class Component{
// 抽象地方法
public abstract void cost();
}
public class ConcreteComponent extends Component{
@Override
public void cost(){
// do something ...
}
}
public abstract class Decorator extends Component{
private Component component = null;
public Decorator(Component component){
this.component = component;
}
@Override
public void cost(){
this.component.cost();
}
}
public class ConcreteDecorator extends Decorator{
public ConcreteDecorator(Component component){
super(component);
}
// 定义自己的修饰逻辑
private void decorateMethod(){
// do somethind ...
}
// 重写父类的方法
public void cost(){
this.decorateMethod();
super.cost();
}
}
public class DecoratorDemo{
public static void main(String[] args){
Component component = new ConcreteComponent();
// 第一次修饰,比如,加鸡蛋,加1块
component = new ConcreteDecorator(component);
// 第二次修饰,比如,加烤肠,加2块
component = new ConcreteDecorator(component);
// 修饰后运行,将钱加在一起
component.cost();
}
}
适用性
侧重点在于灵活的增加需要的属性.装饰顾名思义,可以凭借自己的喜好,随意添加自己想要的装饰.我可以装饰一个物件,可以一个接一个的装饰上多个物件.
- 在不影响其他对象的情况下,以动态、透明的方式给单个对象添加职责。
- 处理那些可以撤消的职责。
- 当不能采用生成子类的方法进行扩充时。一种情况是,可能有大量独立的扩展.
优缺点
- 装饰对象和真实对象有相同的接口。这样客户端对象就可以以和真实对象相同的方式和装饰对象交互。
- 装饰对象包含一个真实对象的索引(reference)
- 装饰对象接受所有的来自客户端的请求。它把这些请求转发给真实的对象。
- 装饰对象可以在转发这些请求以前或以后增加一些附加功能。这样就确保了在运行时,不用修改给定对象的结构就可以在外部增加附加的功能。在面向对象的设计中,通常是通过继承来实现对给定类的功能扩展。
优点
- 比静态继承更灵活
- 避免在层次结构高层的类有太多的特征
- 装饰类和被装饰类可以独立发展,不会相互耦合
缺点
多层装饰比较复杂。
经典实现
框架中的实现
i/o系统中的inputStream
InputStream作为抽象构件,其下面大约有如下几种具体基础构件,从不同的数据源产生输入:
- ByteArrayInputStream,从字节数组产生输入;
- FileInputStream,从文件产生输入;
- StringBufferInputStream,从String对象产生输入;
- PipedInputStream,从管道产生输入;
- SequenceInputStream,可将其他流收集合并到一个流内;
FilterInputStream作为装饰器在JDK中是一个普通类,其下面有多个具体装饰器比如BufferedInputStream、DataInputStream等。我们以BufferedInputStream为例,使用它就是避免每次读取时都进行实际的写操作,起着缓冲作用。