装饰器模式与IO

装饰器模式

允许像一个现有的对象添加新的功能,同时不改变其结构。这种类型的设计模式属于结构性模式。它是作为现有类的一个包装。

这种模式创建了一个装饰类,用来包装原有的类,并在保持类方法签名完整性的前提下,提供了额外的功能。

我们通过下面的实例来演示装饰器模式的用法。其中,我们将把一个形状装饰上不同的颜色,同时又不改变形状类。

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

类图:

意图:动态的给一个对象添加一些额外的职责,就增加功能来说,装饰器模式相比生成子类更加灵活。

主要解决:一般的,我们为了扩展一个类经常使用继承方式实现,由于继承为类引入静态特征,并且随着扩展功能的增多,子类会很膨胀。

何时使用:在不想增加很多子类的情况下扩展类,可代替继承。

优点:装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。

缺点:多层装饰比较复杂。

使用场景:1.扩展一个类的功能。2.动态增加功能,动态撤销。

实现

创建一个shape接口和实现了shape接口的实体类。然后我们创建一个实现了shape接口的抽象装饰类shapeDecorator,并把Shape对象作为它的实例变量。

RedShapeDecorator是实现了shapeDecorator的实体类。

DecoratorPattrernDemo是演示类,使用RedShapeDecorator来装饰Shape对象。

 

创建形状接口:

public interface Shape {
    void draw();
}

创建形状接口实现类:

public class Circle implements Shape{
    @Override
    public void draw() {
        System.out.println("画个圆形");
    }
}
public class Rectangle implements Shape{
    @Override
    public void draw() {
        System.out.println("画个矩形");
    }
}

创建实现了形状接口的抽象修饰类:

public abstract class ShapeDecorator implements Shape{
    //子类具有访问权限
    protected Shape s;

    public ShapeDecorator(Shape s) {
        this.s = s;
    }

    @Override
    public void draw() {
        s.draw();
    }
}

创建扩展了抽象修饰类的实体修饰类:

public class RedShapeDecorator extends ShapeDecorator{

    public RedShapeDecorator(Shape s) {
        super(s);
    }

    @Override
    public void draw() {
        super.draw();
        setRedBorder(s);

    }

    private static void setRedBorder(Shape shape){
        System.out.println("将边框化为红色");
    }
}

测试类:

public class DecoratorPatternDemo {
    public static void main(String[] args) {
        Circle circle = new Circle();
        circle.draw();
        System.out.println("------------------------");
        ShapeDecorator shapeDecorator = new RedShapeDecorator(circle);
        shapeDecorator.draw();

    }
}

运行结果:

关于java 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));
    }
}

InputStream就相当于Component接口,只不过这里是个抽象类,这是我们装饰的目标抽象类。

FileInputStream就是一个ConcreteComponent,即待装饰的具体对象,它并不是JAVA的IO结构中的一个装饰器,因为它无法装饰InputStream。 像是BufferedInputStream,DataInputStream等等就是各种装饰器了,对比上述的标准装饰器样板,JAVA的IO中也有抽象的装饰器基类存在,那就是FilterInputStream,它是很多装饰器最基础的装饰基类。

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

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

下图为IO包中的类图,可以自行与上面的标准装饰器对比一下。

左半部分是InputStream的装饰体系,右半部分是Reader的装饰体系,并且他们之间的桥梁是InputStreamReader,他们每一个装饰体系都与上面标准的装饰器模式类图很相似。

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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值