装饰器模式
装饰器模式又称为包装(Wrapper)模式。装饰器模式以多客户端透明的方式扩展对象的功能,是继承关系的一个替代方案。
装饰器模式的结构
通常给对象添加功能,要么直接修改对象添加相应的功能,要么派生子类来扩展,抑或是使用对象组合的方式。显然,直接修改对应的类的方式并不可取,在面向对象的设计中,我们应该尽量使用组合对象而不是继承对象来扩展和复用功能,装饰器模式就是基于对象组合的方式的。
装饰器模式以对客户端透明的方式动态地给一个对象附加上了更多的责任。换言之,客户端并不会角色对象在装饰前和装饰后有什么不同。装饰器模式可以在不用创建更多子类的情况下,将对象的功能加以扩展。
装饰器模式中的角色有:
1、抽象构件角色
给出一个抽象接口,以规范准备接受附加责任的对象
2、具体构件角色
定义一个将要接受附加责任的类
3、装饰角色
持有一个构建对象的实例,并定义一个与抽象构件接口一致的接口
4、具体装饰角色
负责给构建对象贴上附加的责任
装饰器模式的例子
以齐天大圣孙悟空作为例子,孙悟空就是一个抽象构件角色:
/** 抽象构件角色,定义一个move()方法 */ public interface TheGreatestSage { void move(); }
具体构件角色就是一只猴子,实现了move()方法:
/** 具体构件角色,一只猴子,必须实现move方法 */ public class Monkey implements TheGreatestSage { public void move() { System.out.println("Monkey Move"); } }
定义一个装饰角色装饰者,实现了抽象构件角色并持有一个抽象构件角色的引用:
/** 装饰者,七十二变 */ public class Change implements TheGreatestSage { private TheGreatestSage sage; public Change(TheGreatestSage sage) { this.sage = sage; } public void move() { sage.move(); } }
具体装饰角色,小鱼和小鸟:
/** 具体装饰,小鸟 */ public class Bird extends Change { public Bird(TheGreatestSage sage) { super(sage); } public void move() { System.out.println("Bird Move"); } }
/** 具体装饰,小鱼 */ public class Fish extends Change { public Fish(TheGreatestSage sage) { super(sage); } public void move() { System.out.println("Fish Move"); } }
客户端角色:
public static void main(String[] args) { TheGreatestSage sage = new Monkey(); // 第一种写法 TheGreatestSage bird = new Bird(sage); TheGreatestSage fish = new Fish(bird); // 第二种写法 // TheGreatestSage fish = new Fish(new Bird(sage)); fish.move(); }
运行结果为:
Fish Move
解释一下,齐天大圣是本尊,小鱼和小鸟是装饰,要装饰的是猴子。第一步装饰,将猴子装饰成了小鸟;第二步装饰,将小鸟装饰成了小鱼。
装饰器模式在Java中的应用及解读
或许上面的例子还是不好理解,那么我们不妨使用Java中的例子再来理解一下装饰器模式,看到Java中的装饰器模式的例子,再去理解一下上面的齐天大圣的例子。I/O是Java中装饰器模式最典型的应用,以输入字节流InputStream作为例子,把我在Java IO8:IO总结一文中画的InputStream的UML图拿出来:
我们来划分一下装饰器模式的角色:
1、InputStream是一个抽象构件角色:
public abstract class InputStream implements Closeable { // SKIP_BUFFER_SIZE is used to determine the size of skipBuffer private static final int SKIP_BUFFER_SIZE = 2048; // skipBuffer is initialized in skip(long), if needed. private static byte[] skipBuffer; ... }
2、ByteArrayInputStream、FileInputStream、ObjectInputStream、PipedInputStream都是具体构建角色,比如FileInputStream,它的声明是:
public class FileInputStream extends InputStream { /* File Descriptor - handle to the open file */ private FileDescriptor fd; private FileChannel channel = null; ... }
3、FilterInputStream无疑就是一个装饰角色,因为FilterInputStream实现了InputStream内的所有抽象方法并且持有一个InputStream的引用,:
public class FilterInputStream extends InputStream { /** * The input stream to be filtered. */ protected volatile InputStream in; ... }
4、具体装饰角色就是InflaterInputStream、BufferedInputStream、DataInputStream,比如BufferedInputStream的声明就是:
public class BufferedInputStream extends FilterInputStream { private static int defaultBufferSize = 8192; /** * The internal buffer array where the data is stored. When necessary, * it may be replaced by another array of * a different size. */ protected volatile byte buf[]; ... }
所以我们就可以这么写了:
public static void main(String[] args) throws Exception { File file = new File("D:/aaa.txt"); InputStream in0 = new FileInputStream(file); InputStream in1 = new BufferedInputStream(new FileInputStream(file)); InputStream in2 = new DataInputStream(new BufferedInputStream(new FileInputStream(file))); }
我们这里实例化出了三个InputStream的实现类:
1、in0这个引用指向的是new出来的FileInputStream,这和装饰器模式无关,因为InputStream本身就是一个输入流,FileInputStream实现了最基本的功能罢了
2、in1这个引用指向的是new出来的BufferedInputStream,这就和装饰器模式有关了,它给FileInputStream增加了功能,使得FileInputStream读取文件的内容保存在内存中,以提高读取的功能
3、in2这个引用指向的是new出来的DataInputStream,这也和装饰器模式有关,因为它也给FileInputStream增加了功能,因为它有DataInputStream和BufferedInputStream两个附加的功能
2和3这两点,对于客户端来说,只是使用InputStream的方法而已,根本不知道某个InputStream是否被装饰了,如果被装饰了,InputStream在装饰前和装饰后有什么区别。在客户端不知情的情况下,给这些方法附加上了额外的功能,这就是装饰器模式。
不过要说明一点,上面的这种写法包括齐天大圣的例子都只是一种完全透明的装饰器模式,这意味着装饰后的类有着和抽象构件角色同样的接口方法,但是现实中这是不可能的。比如我把一只猴子装饰成一条小鱼,小鱼只能有移动的功能,就没有小鱼自己特有的功能?所以,完全透明的装饰器模式在现实中是基本不可能出现的,更多的我们是采用半透明的装饰器模式,即允许装饰后的类中有属于自己的方法,所以,上面的I/O代码可以这么改一下:
public static void main(String[] args) throws Exception { File file = new File("D:/aaa.txt"); FileInputStream in0 = new FileInputStream(file); BufferedInputStream in1 = new BufferedInputStream(new FileInputStream(file)); DataInputStream in2 = new DataInputStream(new BufferedInputStream(new FileInputStream(file))); }
这样才更有现实意义。
装饰器模式的优缺点
优点
1、装饰器模式与继承关系的目的都是要扩展对象的功能,但是装饰器模式可以提供比继承更多的灵活性。装饰器模式允许系统动态决定贴上一个需要的装饰,或者除掉一个不需要的装饰。继承关系是不同,继承关系是静态的,它在系统运行前就决定了
2、通过使用不同的具体装饰器以及这些装饰类的排列组合,设计师可以创造出很多不同的行为组合
缺点
由于使用装饰器模式,可以比使用继承关系需要较少数目的类。使用较少的类,当然使设计比较易于进行。但是另一方面,由于使用装饰器模式会产生比使用继承关系更多的对象,更多的对象会使得查错变得困难,特别是这些对象看上去都很像。
装饰器模式和适配器模式的区别
其实适配器模式也是一种包装(Wrapper)模式,它们看似都是起到包装一个类或对象的作用,但是它们使用的目的非常不一样:
1、适配器模式的意义是要将一个接口转变成另外一个接口,它的目的是通过改变接口来达到重复使用的目的
2、装饰器模式不要改变被装饰对象的接口,而是恰恰要保持原有的借口哦,但是增强原有接口的功能,或者改变元有对象的处理方法而提升性能
所以这两种设计模式的目的是不同的。