通常,我们使用继承或组合来扩展一个类的行为,但这些都是在编译过程中完成的。它适用于所有类的例子。随着扩展功能的增加,子类将非常膨胀。我们不能在操作过程中添加或删除任何现有行为以达到重用的目的。为了解决这个问题,有一个装饰模式。
比如我们熟悉的java IO类中就有大量装饰器模式的使用。例如FilterInputStream、FilterOutputStream、FilterReader、BufferedWriter等。
-
定义
装饰器(Decorator)结构设计模式是一种替代继承的技术,是指在不改变现有对象结构的情况下,动态地向当前对象添加一些额外的功能。
装饰模式结构:
1)抽象构件(Component)角色:为了规范子类对象的行为,定义抽象接口,所有包装(装饰对象)和包装(真实对象)都继承了该接口。io流中InputStream、OutPutStream、Reader、Writer;
2)具体构件(ConcreteComponent)角色:指包装类的实现类(真实对象)。io流中FileInputStream、FileOutputStream、ObjectInputStream等;
3)装饰(Decorator)角色:所有包装类别都继承自Decorator(抽象)类可以通过其子类扩展特定部件的功能。io流中的FilterInputStream、FilterOutputStream;
4)具体装饰(ConcreteDecorator)角色:实现抽象装饰的相关方法,用于扩展包装功能。io流中的BufferedInputStream、DataInputStream、BufferedOutputStream;
对于java io对于流中的输入流,其系统结构如上图所示。
Component角色:InputStream声明输入流实现规范;
ConcreteComponent角色:FileInputStream,ObjectInputStream、ByteArrayInputStream、PipedInputStream为不同场景提供输入流;
Decorator角色:FilterInputStream抽象装饰是为了增强ConcreteComponent人物抽象;
ConcreteDecorator角色:BufferedInputStream、DataInputStream是装饰的实现,是增强的具体方法。
以前我们用io流去阅读文件时,是否经常添加缓冲流来提高数据读取效率。
InputStream in = new FileInputStream("D:\\bigTest.txt"); InputStream bin = new BufferedInputStream(in); byte[] data = new byte[1024]; while (bin.read(data) != -1) { //... }
那为什么不直接给呢?FileInputStream访问缓存功能或添加缓存功能FileInputStream的子类呢?从上图可以看出,如果InputStream只有一个实现类的话,这样做完全没有问题,可是InputStream有许多实现类。如果每个子类都添加了增强功能的子类,可以想象此时的子类数量非常大。而我们的装饰模式是用来代替继承和增强对象的。
1.2.装饰模式的优缺点
-
优点
1)装饰模式是继承的替代模式,可以动态扩展实现功能;
2)多种行为可以通过将一个对象包装到多个装饰器中来组合;
3)大可变整体也可分为小类,更符合单一职责原则;
4)装饰模式完全遵循开关原则。
-
缺点
装饰模式会增加很多子类,过度使用会增加程序的复杂性。
1.3、创建方式
1)根据装饰模式的结构依次创建
/** * Component-抽象构件 */ public interface ICar { void assemble(); ////定义汽车组装行为 } /** * ConcreteComponent-具体构件 */ class BasicCar implements ICar{ @Override public void assemble() { System.out.println("最低配置的普通车"); } } /** * Decorator-抽象装饰角色 */ abstract class AbstractCar implements ICar{ private ICar car; public AbstractCar(ICar car){ this.car = car; } @Override public void assemble() { car.assemble(); } ///作为抽象方法,一些希望子类实现的功能可以声明 } //ConcreteDecorator-具体装饰器(普通越野款) class SportsCar extends AbstractCar{ public SportsCar(ICar car) { super(car); } @Override public void assemble() { super.assemble(); System.out.println("装配高底盘,我是越野款"); } } //ConcreteDecorator-具体装饰器(高端款) class LuxuryCar extends AbstractCar{ public LuxuryCar(ICar car) { super(car); } @Override public void assemble() { super.assemble(); System.out.println("装配高端发动机,我是高档款"); } }
2)创建客户端
public class Client { public static void main(String[] args) { //最基本的车 BasicCar car = new BasicCar(); car.assemble(); System.out.println("客户1:第一次改装:-----------"); SportsCar sp1 = new SportCar(car); //已有对象增加/修改一些功能
sp1.assemble();
System.out.println("顾客二:第一次改装:-----------");
LuxuryCar lc2 = new LuxuryCar(car); //已有对象增加/修改一些功能
lc2.assemble();
System.out.println("顾客一:第二次改装:-----------");
LuxuryCar lc1 = new LuxuryCar(sp1); //传SportsCar,就是在此版本上修改,这样组装非常灵活
lc1.assemble();
}
}
-
案例效果
在java io流中,我们也可以对原有对象实现自己的一些特殊处理。比如,下面的案例自定义实现转大写装饰器
public class toUpperCaseInputStream extends FilterInputStream{
protected toUpperCaseInputStream(InputStream in) {
super(in);
}
@Override
public int read() throws IOException {
int c = super.read();
return (c == -1 ? c : Character.toUpperCase(c));
}
public static void main(String[] args) throws IOException {
int c;
//使用自定义的装饰器去装饰ByteArrayInputStream对象
InputStream in = new toUpperCaseInputStream(new ByteArrayInputStream(new String("abcd").getBytes()));
try {
while ((c = in.read()) >= 0) {
System.out.print((char) c); //ABCD
}
} finally {
in.close();
}
}
}
1.4、总结及建议
装饰器提供运行时修改能力,因此更加灵活。当可供选择的数量越多时,使用装饰器模式会更加灵活。
应用场景:
1)
2)
3)。
JDK中装饰器模式的应用:
java io流中的FilterInputStream、FilterOutputStream、FilterReader、BufferedWriter等都是抽象装饰类。