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

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

1,不改变原类文件。

                 2,不使用继承。

                 3,动态扩展。

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

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

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

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

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

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

package com.decorator;

public interface Component {

    void method();

}

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

package com.decorator;

public class ConcreteComponent implements Component{

    public void method() {
System.out.println("原来的方法");
} }

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

package com.decorator;

public abstract class Decorator implements Component{

    protected Component component;

    public Decorator(Component component) {
super();
this.component = component;
} public void method() {
component.method();
} }

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

package com.decorator;

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包装结束");
}
}
package com.decorator;

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包装结束");
}
}

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

package com.decorator;

public class Main {

    public static void main(String[] args) {
Component component =new ConcreteComponent();//原来的对象
System.out.println("------------------------------");
component.method();//原来的方法
ConcreteDecoratorA concreteDecoratorA = new ConcreteDecoratorA(component);//装饰成A
System.out.println("------------------------------");
concreteDecoratorA.method();//原来的方法
concreteDecoratorA.methodA();//装饰成A以后新增的方法
ConcreteDecoratorB concreteDecoratorB = new ConcreteDecoratorB(component);//装饰成B
System.out.println("------------------------------");
concreteDecoratorB.method();//原来的方法
concreteDecoratorB.methodB();//装饰成B以后新增的方法
concreteDecoratorB = new ConcreteDecoratorB(concreteDecoratorA);//装饰成A以后再装饰成B
System.out.println("------------------------------");
concreteDecoratorB.method();//原来的方法
concreteDecoratorB.methodB();//装饰成B以后新增的方法
}
}

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

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

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

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

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

package com.decorator;

import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.DataInputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.LineNumberReader;
import java.io.PushbackInputStream;
import java.io.PushbackReader; public class IOTest { /* test.txt内容:
* hello world!
*/
public static void main(String[] args) throws IOException, ClassNotFoundException {
//文件路径可自行更换
final String filePath = "E:/myeclipse project/POITest/src/com/decorator/test.txt"; //InputStream相当于被装饰的接口或者抽象类,FileInputStream相当于原始的待装饰的对象,FileInputStream无法装饰InputStream
//另外FileInputStream是以只读方式打开了一个文件,并打开了一个文件的句柄存放在FileDescriptor对象的handle属性
//所以下面有关回退和重新标记等操作,都是在堆中建立缓冲区所造成的假象,并不是真正的文件流在回退或者重新标记
InputStream inputStream = new FileInputStream(filePath);
final int len = inputStream.available();//记录一下流的长度
System.out.println("FileInputStream不支持mark和reset:" + inputStream.markSupported()); System.out.println("---------------------------------------------------------------------------------"); /* 下面分别展示三种装饰器的作用BufferedInputStream,DataInputStream,PushbackInputStream,LZ下面做了三个装饰器的功能演示 */ //首先装饰成BufferedInputStream,它提供我们mark,reset的功能
BufferedInputStream bufferedInputStream = new BufferedInputStream(inputStream);//装饰成 BufferedInputStream
System.out.println("BufferedInputStream支持mark和reset:" + bufferedInputStream.markSupported());
bufferedInputStream.mark(0);//标记一下
char c = (char) bufferedInputStream.read();
System.out.println("LZ文件的第一个字符:" + c);
bufferedInputStream.reset();//重置
c = (char) bufferedInputStream.read();//再读
System.out.println("重置以后再读一个字符,依然会是第一个字符:" + c);
bufferedInputStream.reset(); System.out.println("---------------------------------------------------------------------------------"); //装饰成 DataInputStream,我们为了又使用DataInputStream,又使用BufferedInputStream的mark reset功能,所以我们再进行一层包装
//注意,这里如果不使用BufferedInputStream,而使用原始的InputStream,read方法返回的结果会是-1,即已经读取结束
//因为BufferedInputStream已经将文本的内容读取完毕,并缓冲到堆上,默认的初始缓冲区大小是8192B
DataInputStream dataInputStream = new DataInputStream(bufferedInputStream);
dataInputStream.reset();//这是BufferedInputStream提供的功能,如果不在这个基础上包装会出错
System.out.println("DataInputStream现在具有readInt,readChar,readUTF等功能");
int value = dataInputStream.readInt();//读出来一个int,包含四个字节
//我们转换成字符依次显示出来,可以看到LZ文件的前四个字符
String binary = Integer.toBinaryString(value);
int first = binary.length() % 8;
System.out.print("使用readInt读取的前四个字符:");
for (int i = 0; i < 4; i++) {
if (i == 0) {
System.out.print(((char)Integer.valueOf(binary.substring(0, first), 2).intValue()));
}else {
System.out.print(((char)Integer.valueOf(binary.substring(( i - 1 ) * 8 + first, i * 8 + first), 2).intValue()));
}
}
System.out.println(); System.out.println("---------------------------------------------------------------------------------"); //PushbackInputStream无法包装BufferedInputStream支持mark reset,因为它覆盖了reset和mark方法
//因为流已经被读取到末尾,所以我们必须重新打开一个文件的句柄,即FileInputStream
inputStream = new FileInputStream(filePath);
PushbackInputStream pushbackInputStream = new PushbackInputStream(inputStream,len);//装饰成 PushbackInputStream
System.out.println("PushbackInputStream装饰以后支持退回操作unread");
byte[] bytes = new byte[len];
pushbackInputStream.read(bytes);//读完了整个流
System.out.println("unread回退前的内容:" + new String(bytes));
pushbackInputStream.unread(bytes);//再退回去
bytes = new byte[len];//清空byte数组
pushbackInputStream.read(bytes);//再读
System.out.println("unread回退后的内容:" + new String(bytes)); System.out.println("---------------------------------------------------------------------------------"); /* 以上有两个一层装饰和一个两层装饰,下面我们先装饰成Reader,再进行其它装饰 */ //由于之前被PushbackInputStream将流读取到末尾,我们需要再次重新打开文件句柄
inputStream = new FileInputStream(filePath);
InputStreamReader inputStreamReader = new InputStreamReader(inputStream,"utf-8");//先装饰成InputStreamReader
System.out.println("InputStreamReader有reader的功能,比如转码:" + inputStreamReader.getEncoding()); System.out.println("---------------------------------------------------------------------------------"); BufferedReader bufferedReader = new BufferedReader(inputStreamReader);//我们进一步在reader的基础上装饰成BufferedReader
System.out.println("BufferedReader有readLine等功能:" + bufferedReader.readLine()); System.out.println("---------------------------------------------------------------------------------"); LineNumberReader lineNumberReader = new LineNumberReader(inputStreamReader);//我们进一步在reader的基础上装饰成LineNumberReader
System.out.println("LineNumberReader有设置行号,获取行号等功能(行号从0开始),当前行号:" + lineNumberReader.getLineNumber()); System.out.println("---------------------------------------------------------------------------------"); //此处由于刚才被readLine方法将流读取到末尾,所以我们再次重新打开文件句柄,并需要将inputstream再次包装成reader
inputStreamReader = new InputStreamReader(new FileInputStream(filePath));
PushbackReader pushbackReader = new PushbackReader(inputStreamReader,len);//我们进一步在reader的基础上装饰成PushbackReader
System.out.println("PushbackReader是拥有退回操作的reader对象");
char[] chars = new char[len];
pushbackReader.read(chars);
System.out.println("unread回退前的内容:" + new String(chars));
pushbackReader.unread(chars);//再退回去
chars = new char[len];//清空char数组
pushbackReader.read(chars);//再读
System.out.println("unread回退后的内容:" + new String(chars));
}
}

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

十:装饰器模式(io流)的更多相关文章

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

    装饰器模式详解地址 原文总结 定义: 在不必改变原类文件和使用继承的情况下, 动态的扩展一个对象的功能. 通过创建一个包装对象, 也就是装饰来包裹真实的对象 部分详解提示 看了一些文档, 装饰器模式非 ...

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

    作者:zuoxiaolong8810(左潇龙),转载请注明出处,特别说明:本博文来自博主原博客,为保证新博客中博文的完整性,特复制到此留存,如需转载请注明新博客地址即可. LZ到目前已经写了九个设计模 ...

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

    流概述 Java中,流是一种有序的字节序列,可以有任意的长度.从应用流向目的地称为输出流,从目的地流向应用称为输入流. Java的流族谱 Java的 java.io 包中囊括了整个流的家族,输出流和输 ...

  4. Java设计模式--装饰器模式到Java IO 流

    装饰器模式 抽象构件角色:给出一个抽象接口,以规范准备接受附加责任的对象. 具体构件角色:定义准备接受附加责任的对象. 抽象装饰角色:持有一个构件对象的实例,并对应一个与抽象构件接口一致的接口. 具体 ...

  5. 装饰器模式(IO流案例)

    装饰器模式,也成为包装模式,顾名思义,就是对已经存在的某些类进行装饰,以此来扩展一些功能.其结构图如下: Component为统一接口,也是装饰类和被装饰类的基本类型. ConcreteCompone ...

  6. java IO之 字符流 (字符流 = 字节流 + 编码表) 装饰器模式

    字符流 计算机并不区分二进制文件与文本文件.所有的文件都是以二进制形式来存储的,因此, 从本质上说,所有的文件都是二进制文件.所以字符流是建立在字节流之上的,它能够提供字符 层次的编码和解码.列如,在 ...

  7. 涉及模式之 装饰器模式详解(与IO不解的情缘)

    作者:zuoxiaolong8810(左潇龙),转载请注明出处,特别说明:本博文来自博主原博客,为保证新博客中博文的完整性,特复制到此留存,如需转载请注明新博客地址即可. LZ到目前已经写了九个设计模 ...

  8. 装饰器模式 Decorator 结构型 设计模式 (十)

    引子           现实世界的装饰器模式 大家应该都吃过手抓饼,本文装饰器模式以手抓饼为模型展开简介 "老板,来一个手抓饼,  加个培根,  加个鸡蛋,多少钱?" 这句话会不 ...

  9. java进阶系列之装饰器模式

    1.介绍 装饰器模式顾名思义就是装饰某个对象的,是一种结构型模式.装饰器模式允许向一个现有对象添加新的功能,同时不改变其结构,用户可以随意的扩展原有的对象.它是作为现有的类的一个包装.装饰器模式一方面 ...

随机推荐

  1. 算法基础:BFS和DFS的直观解释

    算法基础:BFS和DFS的直观解释 https://cuijiahua.com/blog/2018/01/alogrithm_10.html 一.前言 我们首次接触 BFS 和 DFS 时,应该是在数 ...

  2. c#_sort排序函数的返回值

    C# List.Sort函数的返回值 值 含义 小于零 left在right的前面 零 位置不变 大于零 right在left的前面 示例: 本测试结果在unity3d 和纯C#环境下执行. List ...

  3. c# 第36节 接口的声明

    本节内容: 1:字面理解接口 2:计算机的接口是什么呢 3:接口的声明 4:接口的注意点 1:字面理解接口 大家现在手机上,可能有很多app软件,比如天气预报的软件,有很多种,什么墨迹天气啊之类的等等 ...

  4. python爬虫之深度爬取实例

    写了一个之前没完成的项目,代码优化不够,速度有点慢,应该也有错误的地方,望大佬看了之后能给点建议......... 这是开始的url,先看一下它的网页结构:http://www.cymodel.net ...

  5. Linux中自旋锁

    传统的spinlock Linux的的内核最常见的锁是自旋锁.自旋锁最多只能被一个可执行线程持有.如果一个执行线程试图获得一个被已经持有(争用)的自旋锁,那么该线程就会一直进行忙循环-旋转-等待锁重新 ...

  6. Mybatis拦截器(六)

    拦截器的作用就是我们可以拦截某些方法的调用,在目标方法前后加上我们自己逻辑. Mybatis拦截器设计的一个初衷是为了供用户在某些时候可以实现自己的逻辑而不必去动Mybatis固有的逻辑. Mybat ...

  7. FLOPS

    FLOPS FLOPS(Float Operations Per Second):每秒浮点运算量,是衡量吞吐率的一个单位,通过折算到具体的浮点操作数量上. 所谓,吞吐率——就如同水管,每秒可以流出多少 ...

  8. LG3389 「模板」高斯消元法 高斯消元

    问题描述 LG3389 题解 高斯消元,是用来解\(n\)元一次方程组的算法,时间复杂度\(O(n^3)\) 这样就构造出了这个方程组的矩阵 目标就是把这个矩阵左边\(n \times n\)消为单位 ...

  9. 提高python运行效率-numba

    Numba是Python的即时编译器,在使用NumPy数组和函数以及循环的代码上效果最佳.使用Numba的最常见方法是通过其装饰器集合,这些装饰器可应用于您的函数以指示Numba对其进行编译.调用Nu ...

  10. 编译安装nmap

    官方编译文档 参考 安装对应的库,并编译安装.