Chapter 3 装饰设计模式与Java IO

1 概述

装饰设计模式属于23种设计模式之一,英文叫Decorator Pattern,又叫装饰者模式。装饰设计模式是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。

装饰设计模式在Java中最经典的实践莫过于对Java IO库的设计。通过装饰设计模式在不使用继承关系的前提下,动态的给IO流对象提供加强的功能,其中常见的加强功能指的是缓冲技术,通过缓冲区提高流的读取和写入的效率。

2 装饰设计模式简介

2.1 应用场景

当想要对已有的对象进行功能增强时,可以定义一个新的类,将已有对象传入,并提供基于已有功能的加强功能,自定义的类称为装饰类。

装饰类通常会通过构造方法接收被装饰的对象。

一个简单的应用参考如下代码,其中被装饰类Phone提供了传统的语音打电话的功能,而装饰类MobilePhone通过构造函数传入一个Phone对象,提供一个加强的打电话功能,在原有的语音打电话的基础上提供增强的视频电话的功能。

/**
 * Created by fubinhe on 16/10/22.
 */
public class DecoratorPattern {
    public static void main(String[] args) {
        MobilePhone mobilePhone = new MobilePhone(new Phone());
        mobilePhone.newCall();
    }
}
 
class Phone {
 
    public void call() {
        System.out.println("语音......");
    }
}
 
class MobilePhone {
 
    private Phone phone;
 
    public MobilePhone(Phone phone) {
        this.phone = phone;
    }
 
    public void newCall() {
        phone.call();
        System.out.println("视频......");
    }
}

在实际应用中,被装饰类和装饰类继承同一个父类或者实现同一个接口,表示两个类属于同一个类体系中。

2.2 和继承的比较

上面的功能也可以通过继承来实现,装饰类可以继承被装饰类并复写其call函数。然而,继承是一种强依赖关系,整个体系会显得非常的臃肿。

而装饰设计模式通过将原有的对象传入并调用其方法,如果这个方法有问题可以将其删除而不影响其它的功能。因此,装饰设计模式通过将两个类进行解耦,使得整个体系变得灵活。

3 装饰设计模式在Java IO中的应用

Java IO的十分精巧,其中重要的一点是利用装饰设计模式提供缓冲技术,提高流的读写效率。而Java IO中用到的装饰设计模式也是该模式在Java中最有名的实践。

3.1 BufferedReader的源码解析

3.1.1 典型应用

FileReader提供读取字符文件的read方法,可以一次读取一个字符或一个字符数组。

而应用装饰设计模式,BufferedReader提供增强功能的readLine方法,一次读取文本中的一行字符。该方法和FileReader的读取一个字符数组的read方法的区别在于不用自己显式的定义字符数组,而且不用处理换行符的问题。

典型的应用该类读取字符文件中的每行数据的代码如下所示:

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
 
/**
 * Created by fubinhe on 16/10/22.
 */
public class BufferedReaderDemo {
    public static void main(String[] args) {
        BufferedReader br = null;
        try {
            br = new BufferedReader(new FileReader("1.txt"));
            String line;
            while ((line = br.readLine()) != null) {
                System.out.println(line);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (br != null) {
                try {
                    br.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

3.1.2 构造函数

BufferedReader的类签名为:public class BufferedReader extends Reader

该类维护了一个Reader对象:private Reader in,该对象通过构造函数传入,上面的代码就是通过构造函数传入了一个FileReader对象,该对象是Reader的子类(准确的说是子类的子类)。而BufferedReader类的构造函数有两个实现,分别如下所示:

/**
 * Creates a buffering character-input stream that uses a default-sized
 * input buffer.
 *
 * @param  in   A Reader
 */
public BufferedReader(Reader in) {
    this(in, defaultCharBufferSize);
}

/**
 * Creates a buffering character-input stream that uses an input buffer of
 * the specified size.
 *
 * @param  in   A Reader
 * @param  sz   Input-buffer size
 *
 * @exception  IllegalArgumentException  If sz is <= 0
 */
public BufferedReader(Reader in, int sz) {
    super(in);
    if (sz <= 0)
        throw new IllegalArgumentException("Buffer size <= 0");
    this.in = in;
    cb = new char[sz];
    nextChar = nChars = 0;
}

其中,前者通过传入一个默认缓冲区大小参数给后者实现的,而在后者中,主要是通过传入的sz参数初始化缓冲区数组cb,并且初始化nextChar和nChars两个参数。

3.1.3 readLine方法

BufferedReader类的核心方法是一次读取一行字符的readLine方法,该方法的实现如下:

/**
 * Reads a line of text.  A line is considered to be terminated by any one
 * of a line feed ('\n'), a carriage return ('\r'), or a carriage return
 * followed immediately by a linefeed.
 *
 * @return     A String containing the contents of the line, not including
 *             any line-termination characters, or null if the end of the
 *             stream has been reached
 *
 * @exception  IOException  If an I/O error occurs
 *
 * @see java.nio.file.Files#readAllLines
 */
public String readLine() throws IOException {
    return readLine(false);
}
该方法其实是调用其重载函数实现的,该重载函数的源代码如下:

/**
 * Reads a line of text.  A line is considered to be terminated by any one
 * of a line feed ('\n'), a carriage return ('\r'), or a carriage return
 * followed immediately by a linefeed.
 *
 * @param      ignoreLF  If true, the next '\n' will be skipped
 *
 * @return     A String containing the contents of the line, not including
 *             any line-termination characters, or null if the end of the
 *             stream has been reached
 *
 * @see        java.io.LineNumberReader#readLine()
 *
 * @exception  IOException  If an I/O error occurs
 */
String readLine(boolean ignoreLF) throws IOException {
    StringBuffer s = null;
    int startChar;
 
    synchronized (lock) {
        ensureOpen();
        boolean omitLF = ignoreLF || skipLF;
 
    bufferLoop:
        for (;;) {
 
            if (nextChar >= nChars)
                fill();
            if (nextChar >= nChars) { /* EOF */
                if (s != null && s.length() > 0)
                    return s.toString();
                else
                    return null;
            }
            boolean eol = false;
            char c = 0;
            int i;
 
            /* Skip a leftover '\n', if necessary */
            if (omitLF && (cb[nextChar] == '\n'))
                nextChar++;
            skipLF = false;
            omitLF = false;
 
        charLoop:
            for (i = nextChar; i < nChars; i++) {
                c = cb[i];
                if ((c == '\n') || (c == '\r')) {
                    eol = true;
                    break charLoop;
                }
            }
 
            startChar = nextChar;
            nextChar = i;
 
            if (eol) {
                String str;
                if (s == null) {
                    str = new String(cb, startChar, i - startChar);
                } else {
                    s.append(cb, startChar, i - startChar);
                    str = s.toString();
                }
                nextChar++;
                if (c == '\r') {
                    skipLF = true;
                }
                return str;
            }
 
            if (s == null)
                s = new StringBuffer(defaultExpectedLineLength);
            s.append(cb, startChar, i - startChar);
        }
    }
}

注意该函数的签名签名没有任何权限修饰符,这就是表示我们只能显式调用前面的readLine方法,将参数ignoreLF传入为false。该参数的含义是是否跳过一行后面的换行符。false表示不跳过,那么如果一行只有换行符也会被读出来。

在上面的源代码中主要执行两层循环,其中外层循环bufferLoop主要是通过fill方法将字符文件中的字符读取到缓冲区中,而内层循环charLoop主要功能是将数据写到一个StringBuffer中。需要注意的是,每次调用readLine方法时,缓冲区是从上一次调用后的位置开始的,而并不是从数组的0角标开始,这是因为数组的大小是固定的,而每行数据的大小不是固定的。而标记读取的位置是通过nextChar和nChars两个参数实现的。

3.2 BufferedWriter应用

使用装饰设计模式的写操作的类BufferedWriter相较于BufferedReader比较简单,它和普通的FileWriter类的不同在于可以调用newLine方法实现换行,而不用显式的添加换行符来实现,这样可以实现跨平台性。

下面仅给出该类的典型应用,源代码可以自行查看JDK。

import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
 
/**
 * Created by fubinhe on 16/10/22.
 */
public class BufferedWriterDemo {
    public static void main(String[] args) {
        BufferedWriter bw = null;
        try {
            bw = new BufferedWriter(new FileWriter("2.txt"));
            bw.write("11111");
            bw.newLine();
            bw.write("22222");
            bw.newLine();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (bw != null) {
                try {
                    bw.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值