设计模式09 - 设计模式 - 装饰器模式(结构型)
作者:互联网
一、定义
装饰器(Decorator)模式:指不改变现有对象结构的情况下,动态地给该对象增加额外功能。它是继承方式的一种替代方案。
这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供额外的功能。
简单的说:就是用一个装饰类A包装一个原有的类B,在不改变B类完整性的情况下扩展其某些功能的设计模式;这么做的好处就是可以动态的给B添加一些额外的功能,但是又不影响到B类本身的职责。
二、实现方式
装饰器模式中的角色有:
1、抽象构件角色 - Component
给出一个抽象接口,以规范准备接受附加责任的对象,Component是一个接口或者是抽象类,就是定义我们最核心的对象,也就是最原始的对象。
2、具体构件角色 - ConcreteComponent
定义一个将要接受附加责任的类,ConcreteComponent是最核心、最原始、最基本的接口或抽象类的实现,你要装饰的就是它。
3、抽象装饰角色 - Decorator
一般是一个抽象类,做什么用呢?实现接口或者抽象方法,它里面可不一定有抽象的方法,在它的属性里必然有一个private变量指向Component抽象构件。
4、具体装饰角色
ConcreteDecoratorA和ConcreteDecoratorB是两个具体的装饰类,你要把你最核心的、最原始的、最基本的东西装饰成其他东西。也就是负责给构建对象贴上附加的责任
IO设计
装饰器模式在Java体系中的经典应用是Java I/O,下面先讲解字节输入流InputStream:
InputStream是一个顶层的接口,文章开头就说,装饰器模式是继承关系的一种替代方案,看一下为什么:
-
InputStream假设这里写了两个实现类,FileInputStream,ObjectInputStream分别表示文件字节输入流,对象字节输入流
-
现在我要给这两个输入流加入一点缓冲功能以提高输入流效率,使用继承的方式,那么就写一个BufferedInputStream,继承FileInputStream、ObjectInputStream,给它们加功能
-
现在我有另外一个需求,需要给这两个输入流加入一点网络功能,那么就写一个SocketInputStream,继承继承FileInputStream,ObjectInputStream,给它们加功能
这样就导致两个问题:
-
因为我要给哪个类加功能就必须继承它,比如我要给FileInputStream,ObjectInputStream加上缓冲功能、网络功能就得扩展出2*2=4个类,更多的以此类推,这样势必导致类数量不断膨胀
-
代码无法复用,给FileInputStream,ObjectInputStream加入缓冲功能,本身代码应该是一样的,现在却必须继承完毕后把一样的代码重写一遍,多此一举,代码修改的时候必须修改多个地方,可维护性很差
所以,这个的时候我们就想到了一种解决方案:
-
在要扩展的类比如BufferedInputStream中持有一个InputStream的引用,在BufferedInputStream调用InputStream中的方法,这样扩展的代码就可以复用起来
-
将BufferedInputStream作为InputStream的子类,这样客户端只知道我用的是InputStream而不需要关心具体实现,可以在客户端不知情的情况下,扩展InputStream的功能,加上缓冲功能
这就是装饰器模式简单的由来,一切都是为了解决实际问题而诞生。下一步,根据UML图,我们来划分一下装饰器模式的角色。
1、InputStream是一个抽象构件角色:
1 public abstract class InputStream implements Closeable { { 2 ... 3 public abstract int read() throws IOException; 4 public int read(byte b[], int off, int len) throws IOException { 5 。。。 6 read(); 7 。。。 8 return i; 9 } 10 }
2、具体构建角色:ByteArrayInputStream、FileInputStream、ObjectInputStream等,它们实现了抽象构件角色所规定的接口
1 public class FileInputStream extends InputStream 2 { 3 public int read() throws IOException { 4 return read0(); //read0()为native方法 5 } 6 }
3、抽象装饰角色:FilterInputStream无疑就是一个装饰角色,IO的设计里,FilterInputStream是装饰器的基类,因为FilterInputStream实现了InputStream内的所有抽象方法并且持有一个InputStream的引用:
1 public class FilterInputStream extends InputStream { 2 /** 3 * The input stream to be filtered. 4 */ 5 protected volatile InputStream in; 6 protected FilterInputStream(InputStream in) { 7 this.in = in; 8 } 9 10 public int read() throws IOException { 11 return in.read(); 12 } 13 ... 14 }
4、具体装饰角色:BufferedInputStream、DataInputStream等
1 public class BufferedInputStream extends FilterInputStream { 2 protected volatile byte buf[]; 3 public BufferedInputStream(InputStream in, int size) { 4 super(in); 5 if (size <= 0) { 6 throw new IllegalArgumentException("Buffer size <= 0"); 7 } 8 buf = new byte[size]; 9 } 10 //...实现基于缓存的读数据接口... 11 //带有缓冲的read功能,read()方法的增强 12 public synchronized int read() throws IOException { 13 if (pos >= count) { 14 fill(); 15 if (pos >= count) 16 return -1; 17 } 18 return getBufIfOpen()[pos++] & 0xff; 19 } 20 21 } 22 23 //支持按照基本类型读取数据的类 24 public class DataInputStream extends FilterInputStream implements DataInput { 25 public DataInputStream(InputStream in) { 26 super(in); 27 } 28 //read方法的增强,方法里边用到了read()方法 29 public final char readChar() throws IOException { 30 int ch1 = in.read(); 31 int ch2 = in.read(); 32 if ((ch1 | ch2) < 0) 33 throw new EOFException(); 34 return (char)((ch1 << 8) + (ch2 << 0)); 35 } 36 37 }
5、使用:
1 public static void main(String[] args) throws Exception 2 { 3 File file = new File("D:/aaa.txt"); 4 InputStream in0 = new FileInputStream(file); 5 InputStream in1 = new BufferedInputStream(new FileInputStream(file)); 6 InputStream in2 = new DataInputStream(new BufferedInputStream(new FileInputStream(file))); 7 }
我们这里实例化出了三个InputStream的实现类:
1、in0这个引用指向的是new出来的FileInputStream,这里简单构造出了一个文件字节输入流
2、in1这个引用指向的是new出来的BufferedInputStream,它给FileInputStream增加了缓冲功能,使得FileInputStream读取文件的内容保存在内存中,以提高读取的功能
3、in2这个引用指向的是new出来的DataInputStream,它也给FileInputStream增加了功能,因为它有DataInputStream和BufferedInputStream两个附加的功能
通用装饰器设计
//抽象接口 public interface Component { void method(); } //需要装饰的类 public class ConcreteComponent implements Component{ public void method() { System.out.println("原来的方法"); } } //装饰器基类 public abstract class Decorator implements Component{ protected Component component; public Decorator(Component component) { super(); this.component = component; } public void method() { component.method(); } } //装饰器A public class ConcreteDecoratorA extends Decorator{ public ConcreteDecoratorA(Component component) { super(component); } public void methodA(){ System.out.println("被装饰器A扩展的功能"); } public void method(){ System.out.println("针对该方法加一层A包装"); super.method(); System.out.println("A包装结束"); } } //装饰器B public class ConcreteDecoratorB extends Decorator{ public ConcreteDecoratorB(Component component) { super(component); } public void methodB(){ System.out.println("被装饰器B扩展的功能"); } public void method(){ System.out.println("针对该方法加一层B包装"); super.method(); System.out.println("B包装结束"); } } 使用: Component componentB = new ConcreteDecoratorB(new ConcreteDecoratorA(new ConcreteComponent())); componentB.method(); 输出: 针对该方法加一层B包装 针对该方法加一层A包装 原来的方法 A包装结束 B包装结束
半透明装饰器模式与全透明装饰器模式
半透明装饰器模式与全透明装饰器模式,它们的区别是:
- 对于半透明装饰器模式,装饰后的类未必有和抽象构件角色同样的接口方法,它可以有自己扩展的方法
- 对于全透明装饰器模式,装饰后的类有着和抽象构件角色同样的接口方法
全透明装饰器模式是一种比较理想主义的想法,现实中不太可能出现。
比如BufferedInputStream吧,我把FileInputStream装饰为BufferedInputStream,难道BufferedInputStream就完全没有自己的行为?比如返回缓冲区的大小、清空缓冲区(这里只是举个例子,实际BufferedInputStream是没有这两个动作的),这些都是InputStream本身不具备的,因为InputStream根本不知道缓冲区这个概念,它只知道定义读数据相关方法。
三、使用场景-待补充
JDK中的IO流 + 业务场景
标签:BufferedInputStream,09,InputStream,装饰,new,FileInputStream,设计模式,public,结构型 来源: https://www.cnblogs.com/baopeer/p/16608961.html