(十)装饰器模式详解(与IO不解的情缘)

LZ到目前已经写了九个设计模式,回过去看看,貌似写的有点凌乱,LZ后面会尽量改进。

那么本章LZ和各位读友讨论一个与JAVA中IO有着不解情缘的设计模式,装饰器模式。

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

                 这一个解释,引自百度百科,我们注意其中的几点。

1,不改变原类文件。

                 2,不使用继承。

                 3,动态扩展。

上述三句话一语道出了装饰器模式的特点,下面LZ给出装饰器模式的类图,先上图再解释。

从图中可以看到,我们装饰的是一个接口的任何实现类,而这些实现类也包括了装饰器本身,装饰器本身也可以再被装饰。

另外,这个类图只是装饰器模式的完整结构,但其实里面有很多可以变化的地方,LZ给出如下两条。

                  1,Component接口可以是接口也可以是抽象类,甚至是一个普通的父类(这个强烈不推荐,普通的类作为继承体系的超级父类不易于维护)。

                  2,装饰器的抽象父类Decorator并不是必须的。

那么我们将上述标准的装饰器模式,用我们熟悉的JAVA代码给诠释一下。首先是待装饰的接口Component。

[java] view plaincopy

<EMBED id=ZeroClipboardMovie_1 name=ZeroClipboardMovie_1 type=application/x-shockwave-flash align=middle pluginspage=http://www.macromedia.com/go/getflashplayer height=18 width=18 src=http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf wmode="transparent" flashvars="id=1&width=18&height=18" allowfullscreen="false" allowscriptaccess="always" bgcolor="#ffffff" quality="best" menu="false" loop="false">

  1. package com.decorator;
  2. public interface Component {
  3. void method();
  4. }

接下来便是我们的一个具体的接口实现类,也就是俗称的原始对象,或者说待装饰对象。

[java] view plaincopy

<EMBED id=ZeroClipboardMovie_2 name=ZeroClipboardMovie_2 type=application/x-shockwave-flash align=middle pluginspage=http://www.macromedia.com/go/getflashplayer height=18 width=18 src=http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf wmode="transparent" flashvars="id=2&width=18&height=18" allowfullscreen="false" allowscriptaccess="always" bgcolor="#ffffff" quality="best" menu="false" loop="false">

  1. package com.decorator;
  2. public class ConcreteComponent implements Component{
  3. public void method() {
  4. System.out.println("原来的方法");
  5. }
  6. }

下面便是我们的抽象装饰器父类,它主要是为装饰器定义了我们需要装饰的目标是什么,并对Component进行了基础的装饰。

[java] view plaincopy

<EMBED id=ZeroClipboardMovie_3 name=ZeroClipboardMovie_3 type=application/x-shockwave-flash align=middle pluginspage=http://www.macromedia.com/go/getflashplayer height=18 width=18 src=http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf wmode="transparent" flashvars="id=3&width=18&height=18" allowfullscreen="false" allowscriptaccess="always" bgcolor="#ffffff" quality="best" menu="false" loop="false">

  1. package com.decorator;
  2. public abstract class Decorator implements Component{
  3. protected Component component;
  4. public Decorator(Component component) {
  5. super();
  6. this.component = component;
  7. }
  8. public void method() {
  9. component.method();
  10. }
  11. }

再来便是我们具体的装饰器A和装饰器B。

[java] view plaincopy

<EMBED id=ZeroClipboardMovie_4 name=ZeroClipboardMovie_4 type=application/x-shockwave-flash align=middle pluginspage=http://www.macromedia.com/go/getflashplayer height=18 width=18 src=http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf wmode="transparent" flashvars="id=4&width=18&height=18" allowfullscreen="false" allowscriptaccess="always" bgcolor="#ffffff" quality="best" menu="false" loop="false">

  1. package com.decorator;
  2. public class ConcreteDecoratorA extends Decorator{
  3. public ConcreteDecoratorA(Component component) {
  4. super(component);
  5. }
  6. public void methodA(){
  7. System.out.println("被装饰器A扩展的功能");
  8. }
  9. public void method(){
  10. System.out.println("针对该方法加一层A包装");
  11. super.method();
  12. System.out.println("A包装结束");
  13. }
  14. }

[java] view plaincopy

<EMBED id=ZeroClipboardMovie_5 name=ZeroClipboardMovie_5 type=application/x-shockwave-flash align=middle pluginspage=http://www.macromedia.com/go/getflashplayer height=18 width=18 src=http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf wmode="transparent" flashvars="id=5&width=18&height=18" allowfullscreen="false" allowscriptaccess="always" bgcolor="#ffffff" quality="best" menu="false" loop="false">

  1. package com.decorator;
  2. public class ConcreteDecoratorB extends Decorator{
  3. public ConcreteDecoratorB(Component component) {
  4. super(component);
  5. }
  6. public void methodB(){
  7. System.out.println("被装饰器B扩展的功能");
  8. }
  9. public void method(){
  10. System.out.println("针对该方法加一层B包装");
  11. super.method();
  12. System.out.println("B包装结束");
  13. }
  14. }

下面给出我们的测试类。我们针对多种情况进行包装。

[java] view plaincopy

<EMBED id=ZeroClipboardMovie_6 name=ZeroClipboardMovie_6 type=application/x-shockwave-flash align=middle pluginspage=http://www.macromedia.com/go/getflashplayer height=18 width=18 src=http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf wmode="transparent" flashvars="id=6&width=18&height=18" allowfullscreen="false" allowscriptaccess="always" bgcolor="#ffffff" quality="best" menu="false" loop="false">

  1. package com.decorator;
  2. public class Main {
  3. public static void main(String[] args) {
  4. Component component =new ConcreteComponent();//原来的对象
  5. System.out.println("------------------------------");
  6. component.method();//原来的方法
  7. ConcreteDecoratorA concreteDecoratorA = new ConcreteDecoratorA(component);//装饰成A
  8. System.out.println("------------------------------");
  9. concreteDecoratorA.method();//原来的方法
  10. concreteDecoratorA.methodA();//装饰成A以后新增的方法
  11. ConcreteDecoratorB concreteDecoratorB = new ConcreteDecoratorB(component);//装饰成B
  12. System.out.println("------------------------------");
  13. concreteDecoratorB.method();//原来的方法
  14. concreteDecoratorB.methodB();//装饰成B以后新增的方法
  15. concreteDecoratorB = new ConcreteDecoratorB(concreteDecoratorA);//装饰成A以后再装饰成B
  16. System.out.println("------------------------------");
  17. concreteDecoratorB.method();//原来的方法
  18. concreteDecoratorB.methodB();//装饰成B以后新增的方法
  19. }
  20. }

下面看下我们运行的结果,到底是产生了什么效果。

从此可以看到,我们首先是使用的原始的类的方法,然后分别让A和B装饰完以后再调用,最后我们将两个装饰器一起使用,再调用该接口定义的方法。

上述当中,我们分别对待装饰类进行了原方法的装饰和新功能的增加,methodA和methodB就是新增加的功能,这些都是装饰器可以做的,当然两者并不一定兼有,但一般至少会有一种,否则也就失去了装饰的意义。

另外,文章开篇就说道了IO与装饰器的情缘,相信各位就算不太清楚,也都大致听说过JAVA的IO是装饰器模式实现的,所以LZ也不再废话,在给出一个标准的模板示例以后,直接拿出IO的示例,我们真枪实弹的来。

下面LZ直接给出IO包中的部分装饰过程,上面LZ加了详细的注释以及各个装饰器的功能演示,各位可以和上面标准的装饰器模式对比一下,LZ不得不感叹,IO与装饰器的孽缘。

[java] view plaincopy

<EMBED id=ZeroClipboardMovie_7 name=ZeroClipboardMovie_7 type=application/x-shockwave-flash align=middle pluginspage=http://www.macromedia.com/go/getflashplayer height=18 width=18 src=http://static.blog.csdn.net/scripts/ZeroClipboard/ZeroClipboard.swf wmode="transparent" flashvars="id=7&width=18&height=18" allowfullscreen="false" allowscriptaccess="always" bgcolor="#ffffff" quality="best" menu="false" loop="false">

  1. package com.decorator;
  2. import java.io.BufferedInputStream;
  3. import java.io.BufferedReader;
  4. import java.io.DataInputStream;
  5. import java.io.FileInputStream;
  6. import java.io.IOException;
  7. import java.io.InputStream;
  8. import java.io.InputStreamReader;
  9. import java.io.LineNumberReader;
  10. import java.io.PushbackInputStream;
  11. import java.io.PushbackReader;
  12. public class IOTest {
  13. /* test.txt内容:
  14. * hello world!
  15. */
  16. public static void main(String[] args) throws IOException, ClassNotFoundException {
  17. //文件路径可自行更换
  18. final String filePath = "E:/myeclipse project/POITest/src/com/decorator/test.txt";
  19. //InputStream相当于被装饰的接口或者抽象类,FileInputStream相当于原始的待装饰的对象,FileInputStream无法装饰InputStream
  20. //另外FileInputStream是以只读方式打开了一个文件,并打开了一个文件的句柄存放在FileDescriptor对象的handle属性
  21. //所以下面有关回退和重新标记等操作,都是在堆中建立缓冲区所造成的假象,并不是真正的文件流在回退或者重新标记
  22. InputStream inputStream = new FileInputStream(filePath);
  23. final int len = inputStream.available();//记录一下流的长度
  24. System.out.println("FileInputStream不支持mark和reset:" + inputStream.markSupported());
  25. System.out.println("---------------------------------------------------------------------------------");
  26. /* 下面分别展示三种装饰器的作用BufferedInputStream,DataInputStream,PushbackInputStream,LZ下面做了三个装饰器的功能演示  */
  27. //首先装饰成BufferedInputStream,它提供我们mark,reset的功能
  28. BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);//装饰成 BufferedInputStream
  29. System.out.println("BufferedInputStream支持mark和reset:" + bufferedInputStream.markSupported());
  30. bufferedInputStream.mark(0);//标记一下
  31. char c = (char) bufferedInputStream.read();
  32. System.out.println("LZ文件的第一个字符:" + c);
  33. bufferedInputStream.reset();//重置
  34. c = (char) bufferedInputStream.read();//再读
  35. System.out.println("重置以后再读一个字符,依然会是第一个字符:" + c);
  36. bufferedInputStream.reset();
  37. System.out.println("---------------------------------------------------------------------------------");
  38. //装饰成 DataInputStream,我们为了又使用DataInputStream,又使用BufferedInputStream的mark reset功能,所以我们再进行一层包装
  39. //注意,这里如果不使用BufferedInputStream,而使用原始的InputStream,read方法返回的结果会是-1,即已经读取结束
  40. //因为BufferedInputStream已经将文本的内容读取完毕,并缓冲到堆上,默认的初始缓冲区大小是8192B
  41. DataInputStream dataInputStream = new DataInputStream(bufferedInputStream);
  42. dataInputStream.reset();//这是BufferedInputStream提供的功能,如果不在这个基础上包装会出错
  43. System.out.println("DataInputStream现在具有readInt,readChar,readUTF等功能");
  44. int value = dataInputStream.readInt();//读出来一个int,包含四个字节
  45. //我们转换成字符依次显示出来,可以看到LZ文件的前四个字符
  46. String binary = Integer.toBinaryString(value);
  47. int first = binary.length() % 8;
  48. System.out.print("使用readInt读取的前四个字符:");
  49. for (int i = 0; i < 4; i++) {
  50. if (i == 0) {
  51. System.out.print(((char)Integer.valueOf(binary.substring(0, first), 2).intValue()));
  52. }else {
  53. System.out.print(((char)Integer.valueOf(binary.substring(( i - 1 ) * 8 + first, i * 8 + first), 2).intValue()));
  54. }
  55. }
  56. System.out.println();
  57. System.out.println("---------------------------------------------------------------------------------");
  58. //PushbackInputStream无法包装BufferedInputStream支持mark reset,因为它覆盖了reset和mark方法
  59. //因为流已经被读取到末尾,所以我们必须重新打开一个文件的句柄,即FileInputStream
  60. inputStream = new FileInputStream(filePath);
  61. PushbackInputStream pushbackInputStream = new PushbackInputStream(inputStream,len);//装饰成 PushbackInputStream
  62. System.out.println("PushbackInputStream装饰以后支持退回操作unread");
  63. byte[] bytes = new byte[len];
  64. pushbackInputStream.read(bytes);//读完了整个流
  65. System.out.println("unread回退前的内容:" + new String(bytes));
  66. pushbackInputStream.unread(bytes);//再退回去
  67. bytes = new byte[len];//清空byte数组
  68. pushbackInputStream.read(bytes);//再读
  69. System.out.println("unread回退后的内容:" + new String(bytes));
  70. System.out.println("---------------------------------------------------------------------------------");
  71. /*  以上有两个一层装饰和一个两层装饰,下面我们先装饰成Reader,再进行其它装饰   */
  72. //由于之前被PushbackInputStream将流读取到末尾,我们需要再次重新打开文件句柄
  73. inputStream = new FileInputStream(filePath);
  74. InputStreamReader inputStreamReader = new InputStreamReader(inputStream,"utf-8");//先装饰成InputStreamReader
  75. System.out.println("InputStreamReader有reader的功能,比如转码:" + inputStreamReader.getEncoding());
  76. System.out.println("---------------------------------------------------------------------------------");
  77. BufferedReader bufferedReader = new BufferedReader(inputStreamReader);//我们进一步在reader的基础上装饰成BufferedReader
  78. System.out.println("BufferedReader有readLine等功能:" + bufferedReader.readLine());
  79. System.out.println("---------------------------------------------------------------------------------");
  80. LineNumberReader lineNumberReader = new LineNumberReader(inputStreamReader);//我们进一步在reader的基础上装饰成LineNumberReader
  81. System.out.println("LineNumberReader有设置行号,获取行号等功能(行号从0开始),当前行号:" + lineNumberReader.getLineNumber());
  82. System.out.println("---------------------------------------------------------------------------------");
  83. //此处由于刚才被readLine方法将流读取到末尾,所以我们再次重新打开文件句柄,并需要将inputstream再次包装成reader
  84. inputStreamReader = new InputStreamReader(new FileInputStream(filePath));
  85. PushbackReader pushbackReader = new PushbackReader(inputStreamReader,len);//我们进一步在reader的基础上装饰成PushbackReader
  86. System.out.println("PushbackReader是拥有退回操作的reader对象");
  87. char[] chars = new char[len];
  88. pushbackReader.read(chars);
  89. System.out.println("unread回退前的内容:" + new String(chars));
  90. pushbackReader.unread(chars);//再退回去
  91. chars = new char[len];//清空char数组
  92. pushbackReader.read(chars);//再读
  93. System.out.println("unread回退后的内容:" + new String(chars));
  94. }
  95. }

上述便是IO的装饰器使用,其中InputStream就相当于上述的Component接口,只不过这里是一个抽象类,这是我们装饰的目标抽象类。FileInputstream就是一个ConcreteComponent,即待装饰的具体对象,它并不是JAVA的IO结构中的一个装饰器,因为它无法装饰InputStream。剩下BufferedInputStream,DataInputstream等等就是各种装饰器了,对比上述的标准装饰器样板,JAVA的IO中也有抽象的装饰器基类的存在,只是上述没有体现出来,就是FilterInputStream,它是很多装饰器最基础的装饰基类。

在上述过程中,其中dataInputStream是经过两次装饰后得到的,它具有了dataInputStream和bufferedInputStream的双重功能,另外,InputStreamReader是一个特殊的装饰器,它提供了字节流到字符流的桥梁,其实它除了具有装饰器的特点以外,也有点像一个适配器,但LZ还是觉得它应当算是一个装饰器。

其它的IO装饰器各位可以自行尝试或者和上述的标准的装饰器模式代码比对一下,下面另附LZ的IO装饰器程序运行后结果。

从上面的展示中,已经可以充分体会到装饰器模式的灵活了,我们创建的一个FileInputstream对象,我们可以使用各种装饰器让它具有不同的特别的功能,这正是动态扩展一个类的功能的最佳体现,而装饰器模式的灵活性正是JAVA中IO所需要的,不得不赞一下JAVA类库的建造者实在是强悍。

上述的XXXXInputStream的各个类都继承了InputStream,这样做不仅是为了复用InputStream的父类功能(InputStream也是一种模板方法模式,它定义了read(byte[])方法的简单算法,并将read()方法交给具体的InputStream去实现),也是为了可以重叠装饰,即装饰器也可以再次被装饰,而过渡到Reader以后,Reader的装饰器体系则是类似的。

下面LZ给出上面IO包中所涉及的类的类图,各位可以自行和上面的标准装饰器模式对比一下。

LZ在类图上标注了各个类负责的角色,并且使用背景颜色将InputStream和Reader体系分开,其中左半部分就是InputStream的装饰体系,右半部分就是Reader的装饰体系,并且他们之间的桥梁是InputStreamReader,他们每一个装饰体系都与上面标准的装饰器模式类图极其相似,各位可以自己看一下,感受一下,尤其是InputStreamReader,它的位置比较特殊。

总之呢,装饰器模式就是一个可以非常灵活的动态扩展类功能的设计模式,它采用组合的方式取代继承,使得各个功能的扩展更加独立和灵活。

本次装饰器模式就到此结束了,感谢各位的收看,下期再见。

下期预告,外观模式。

时间: 2024-08-27 04:52:52

(十)装饰器模式详解(与IO不解的情缘)的相关文章

Javascript设计模式之装饰者模式详解篇

一.前言: 装饰者模式(Decorator Pattern):在不改变原类和继承的情况下动态扩展对象功能,通过包装一个对象来实现一个新的具有原对象相同接口的新的对象. 装饰者模式的特点:1. 在不改变原对象的原本结构的情况下进行功能添加.2. 装饰对象和原对象具有相同的接口,可以使客户以与原对象相同的方式使用装饰对象.3. 装饰对象中包含原对象的引用,即装饰对象是真正的原对象经过包装后的对象. 二.Javascript装饰者模式详解: 描述:装饰者模式中,可以在运行时动态添加附加功能到对象中.当

Swift学习之装饰者模式详解

本文和大家分享的主要是swift装饰者模式相关内容,一起来看看吧,希望对大家学习swift有所帮助. 装饰者模式以对客户透明的方式动态地给一个对象附加上更多的责任,例如生活中常用的生日蛋糕,可以添加蓝莓,巧克力来装饰,可以动态地给一个对象添加额外的职责. 装饰者模式.jpg Cake类: class Cake { func cakeTypeName() -> String { return "" } func make() { } } class BirthdayCake: Ca

进阶Python:装饰器 全面详解

进阶Python:装饰器 前言 前段时间我发了一篇讲解Python调试工具PySnooper的文章,在那篇文章开始一部分我简单的介绍了一下装饰器,文章发出之后有几位同学说"终于了解装饰器的用法了",可见有不少同学对装饰器感兴趣.但是那篇文章主要的目的是在介绍PySnooper,所以没有太深入的展开讲解装饰器,于是在这里就详细的介绍一些装饰器的使用. 装饰器是Python中非常重要的一个概念,如果你会Python的基本语法,你可以写出能够跑通的代码,但是如果你想写出高效.简洁的代码,我认

python 函数及变量作用域及装饰器decorator @详解

一.函数及变量的作用 在python程序中,函数都会创建一个新的作用域,又称为命名空间,当函数遇到变量时,Python就会到该函数的命名空间来寻找变量,因为Python一切都是对象,而在命名空间中,都是以字典形式存在着,这些变量名,函数名都是索引,而值就是,对应的变量值和函数内存地址.在python中可以用globals()查看全局变量,locals()局部变量. >>> global_v = '全局变量' >>> def func(): ... local_v = '

设计模式之装饰器模式io的小入门(十一)

装饰器模式详解地址 原文总结 定义: 在不必改变原类文件和使用继承的情况下, 动态的扩展一个对象的功能. 通过创建一个包装对象, 也就是装饰来包裹真实的对象 部分详解提示 看了一些文档, 装饰器模式非常依赖构造器 与 重写方法 装饰器模式的特点: 不改变原来的类 , 不使用继承 , 动态扩展 流这块除了文件上传下载使用过, 确实用的太少了这里继续复习下最简单的文件上传 文件目录的创建 目录的是否存在没有就创建 不推荐: 年/月/日 一般没什么用户权限的图片之类的不过 推荐: 模块/用户/模块/年

10.9-全栈Java笔记:装饰器模式构建IO流体系

装饰器模式 装饰器模式是GOF23种设计模式中较为常用的一种模式.它可以实现对原有类的包装和装饰,使新的类具有更强的功能. 我这里有智能手机Iphone, 我们可以通过加装投影组件,实现原有手机功能的扩展.这就是一种"装饰器模式". 我们在未来普通人加装"外骨骼"装饰,让普通人具有力扛千斤的能力,也是一种"装饰器模式". [图] 手机经过投影套件"装饰后",成为功能更强的"投影手机" [示例1]装饰器模式代

装饰器模式及JAVA IO流例子★★★☆☆

一.什么是装饰模式 通过关联机制给类增加行为,其行为的扩展由修饰对象来决定: 二.补充说明 与继承相似,不同点在于继承是在编译期间扩展父类,而装饰器模式在运行期间动态扩展原有对象: 或者说,继承是对类进行扩展,装饰模式是对对象进行扩展: 三.角色 抽象构件 具体构件 抽象装饰类 具体装饰类 说明:具体构件.抽象装饰类.具体装饰类的共同父类是抽象构件,具体装饰类继承抽象装饰类并在运行期间装饰具体构件: 四.例子 例子说明: 画家接口Painter,为抽象构件,有两个方法,获取画家描述信息及绘画:

Java IO流以及装饰器模式在其上的运用

流概述 Java中,流是一种有序的字节序列,可以有任意的长度.从应用流向目的地称为输出流,从目的地流向应用称为输入流. Java的流族谱 Java的 java.io 包中囊括了整个流的家族,输出流和输入流的谱系如下所示: InputStream和OutputStream InputStream和OutputStream分别是输入输出流的顶级抽象父类,只定义了一些抽象方法供子类实现. 在输出流OutputStream中,如果你需要向一个输出流写入数据,可以调用 void write(int b)

IO中的装饰器模式

//可以进InputStream 类 区分为目的和方法两类 //一般直接子类,都是目的不同的(A类), // 如FileInputStream, #从文件中获得字节.// ByteArrayInputStream #包含一个内存缓冲区,字节从中取出.// ObjectInputStream #用来恢复被序列化的对象.// PipedInputStream #管道输入流,读取管道内容.多和PipedOutputStream一起用于多线程通信.// SequenceInputStream #是多种输