Java基础—IO流(一)

IO流概述

一、概述

Java对数据的处理都是通过流的方式,称为IO(Input-Outpu)流。IO流用于处理设备上的数据传输,如硬盘上储存的数据,内存中驻留的数据。

二、IO流的区分

1.按流向分
按流向分为:输入流和输出流。

2.按操作数据分
流按操作数据分为两种:字节流与字符流。

小扩展:
字符流的出现是为了方便处理文本字符。英语有英文字符集ASCII码;中文字符集由原来的GB2312,扩展为现在的GBK。之后,国际标准化组织计划将世界上所有国家的文字都进行编排,形成了国际标准码表,UNICODE;优化之后,形成了UTF-8字符编码表。那么,如果一个电脑用GBK编码存储文本,另一个电脑用UTF-8编码读取,就会出现乱码。Java就这个情况,在字节流的基础上,增加了字符流。字符流内部融合了编码表,并可以指定查询的编码表,处理文本字符更加方便,避免乱码。

三、IO流常用基类

1.字符流的抽象基类

  • Reader
  • Writer

2.字节流的抽象基类

  • InputStream
  • OutputStream

IO流用于操作数据,数据的最常见体现形式是文件。那么以操作文件为主,从字符流开始,来演示IO流程序。


IO流(一) 字符流

这里写图片描述

一、Writer类

1.概述
Writer类可以在硬盘上创建一个文件,并写入或添加数据。该类的子类还能实现写入过程中的不同功能。

2.FileWriter
FileWriter类专门用于操作文件。该对象中只有构造方法,并且没有空参数的构造方法,因为初始化的时候必须要有文件对象,才能进行写入操作。在示例代码中,将对每个环节做详细注释。

1)文件创建并写入

第一步:创建FileWriter对象,传入要操作的文件名

class FileWriterDemo {
    public static void main(String[] args) throws IOException {
        /* 在当前目录创建一个名为demo.txt的文件作为文本写入的目标 */
        FileWriter fw = new FileWriter("demo.txt");
    }
}

注意:
-创建文件会报出异常,此处先抛出,后期会有专门处理异常的代码;
-如果指定文件夹下有同名文件,该文件将被覆盖,所以要小心创建操作

第二步:写入自定义数据

class FileWriterDemo {
    public static void main(String[] args) throws IOException {
        FileWriter fw = new FileWriter("demo.txt");
        /* 将字符串写入缓冲区 */
        fw.write("abcde");
        /* 将缓冲区中的内容写入文件 */
        fw.flush();
    }
}

注意:
-writer()方法不是将文本内容直接写入文件,而是先写入流的缓冲区;
-flush()方法将缓冲区中的内容写入到文件;每次调用完writer()方法,都要调用flush()

第三步:关闭流资源

class FileWriterDemo {
    public static void main(String[] args) throws IOException {
        FileWriter fw = new FileWriter("demo.txt");
        /* 将字符串写入缓冲区 */
        fw.write("abcde");
        /* 将缓冲区中的内容写入文件 */
        fw.flush();
        /* 关闭流资源,关闭之前会刷新缓冲区一次 */
        fw.close();
    }
}

注意:
-由于每个系统创建文件写入内容的方式不同,Java需要调用系统底层的功能来实现文件的创建和写入;使用完系统资源的之后,一定要调用close()方法,关闭该资源;
-close()方法与flush()方法的区别在于,close()方法刷新缓冲区之后,关闭了该输出流资源,之后不能再写入数据;而flush()之后,可以继续写入数据,流资源依然存在;

第四步:针对处理IO异常

class FileWriterDemo {
    public static void main(String[] args) throws IOException {
        FileWriter fw = null;
        try {
            fw = new FileWriter("demo.txt");
            fw.write("abcde");
            fw.flush();
        } catch(IOException e) {
            System.out.println(e.toString());
        } finally {
            try {
                if(fw != null)
                    fw.close();
            } catch(IOException e) {
                System.out.println(e.toString());
            }
        }
    }
}

注意:
-用try catch捕捉并处理异常;
-关闭资源的代码一定要放在finally中;
-close()方法同样会抛出IO异常,同样需要用try catch捕捉并处理;
-如果创建文件的路径有误(例如电脑上没有k盘,创建时却写fw = new FileWriter("k:\"demo.txt");),那么会报出FileNotFoundException文件无法找到异常,同时会报出NullPointerException空指针异常,因为路径找不到,fw对象没有建立,而finally中还要调用close()方法,会发生空指针异常;所以需要在finally中建立判断;
-无论读、写数据还是关闭资源操作,都有可能发生IO异常

2)文件的续写
在原有数据的基础上,续写自定义数据。

第一步:修改构造方法

class FileWriterDemo {
    public static void main(String[] args) throws IOException {
        /* 传递一个布尔型参数,true表示不覆盖已有文件
         * 从文件内容末尾开始添加新内容;如果文件不存在
         * 则创建一个新文件
         */
        FileWriter fw = new FileWriter("demo.txt", true);
    }
}

第二步:写入自定义数据

class FileWriterDemo {
    public static void main(String[] args) throws IOException {
        FileWriter fw = null;
        try {
            fw = new FileWriter("demo.txt", true);
            /* windows中判断换行是用\r\n来判断,\n只适用于linux系统 */
            fw.write("nihao\r\nxiexie");
            fw.flush();
        } catch(IOException e) {
            System.out.println(e.toString());
        } finally {
            try {
                if(fw != null)
                    fw.close();
            } catch(IOException e) {
                System.out.println(e.toString());
            }
        }
    }
}

二、Reader类

1.概述
Reader类用于高效读取字符流。该类的子类可以实现读取过程中的不同功能。

2.FileReader类
FileReader类是用于读取字符文件的便捷类。该类包含默认的字符编码和默认的字节缓冲区大小。

1)read()方法读取文件

第一步:创建FileReader对象

class FileReaderDemo {
    public static void main(String[] args) {
        try {
            /* 创建一个文件读取流对象,并与指定名称的文件相关联 */
            FileReader fr = new FileReader("demo.txt");
        } catch(IOException e) {
            System.out.println(e.toString());
        }
    }
}

注意:
创建FileReader对象时关联的读取文件必须已经存在,不然会发生FileNotFoundException

第二步:读入单个字符并关闭资源

class FileReaderDemo {
    public static void main(String[] args) {
        FileReader fr = null;
        try {
            fr = new FileReader("demo.txt");
            /* read()方法一次读一个字符,第二次调用会自动读取下一个字符 */
            int ch1 = fr.read();

            System.out.print("ch1 = " + (char)ch1);

        } catch(IOException e) {
            System.out.println(e.toString());
        } finally {
            try {
                if(fr != null)
                    fr.close();
            } catch(IOException e) {
                System.out.println(e.toString());
            }
        }
    }
}

注意:
read()方法读到文件末尾没有字符的时候,会返回-1;可以利用这个机制,循环读取所有数据

第三步:一次性读取全部数据

class FileReaderDemo {
    public static void main(String[] args) {
        FileReader fr = null;
        try {
            fr = new FileReader("demo.txt");
            int ch = 0;
            /* 当ch不等于-1的时候,继续读文件,直到文件末尾 */
            while((ch = fr.read()) != -1) {
                System.out.print("ch = " + (char)ch);
            }
        } catch(IOException e) {
            System.out.println(e.toString());
        } finally {
            try {
                if(fr != null)
                    fr.close();
            } catch(IOException e) {
                System.out.println(e.toString());
            }
        }
    }
}

2)read(ch[] ch)方法读取文件

第一步:定义一个字符数组用于储存读到的字符

class FileReaderDemo {
    public static void main(String[] args) {
        try {
            FileReader fr = new FileReader("demo.txt");
            /* 创建长度为10的字符数组 */
            char[] buff = new char[10];

        } catch(IOException e) {
            System.out.println(e.toString());
        }
    }
}

第二步:读取字符并存入字符数组

class FileReaderDemo {
    public static void main(String[] args) {
        FileReader fr = null;
        try {
            fr = new FileReader("demo.txt");

            char[] buff = new char[10];
            /* 读取字符并存入字符数组 */
            int num = fr.read(buff);
            System.out.print(new String(buff));
        } catch(IOException e) {
            System.out.println(e.toString());
        } finally {
            try {
                if(fr != null)
                    fr.close();
            } catch(IOException e) {
                System.out.println(e.toString());
            }
        }
    }
}

注意:
read(ch[] ch)方法返回的是读取了多少个字符;独到文件末尾,返回-1;可以利用这一机制,循环读取全部字符

第三步:一次性读取全部数据

class FileReaderDemo {
    public static void main(String[] args) {
        FileReader fr = null;
        try {
            fr = new FileReader("demo.txt");

            char[] buff = new char[1024];

            int num = 0;
            /* 当返回值不等于-1时,继续读文件 */
            while((num = fr.read(buff)) != -1) {
                /* 读取了多少个字符,就打印多少个字符 */
                System.out.print(new String(buff, 0, num));
            }
        } catch(IOException e) {
            System.out.println(e.toString());
        } finally {
            try {
                if(fr != null)
                    fr.close();
            } catch(IOException e) {
                System.out.println(e.toString());
            }
        }
    }
}

注意:
-缓冲区字符数组通常定义1024的整数倍长度;
-read(ch[] ch)方法的效率优于read()方法;由于read()方法读一个字符,写一个字符,效率低下;而raead(ch[] ch)方法读取一段字符到缓冲区,然后一起写入,效率较高

三、字符流练习

1.读取文件并打印
读取一个.java文件,打印在控制台上。

示例代码:

package com.heisejiuhuche.io;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

public class FileReaderDemo {
    public static void main(String[] args) {
        // print_1();
        print_2();
    }

    /* 用read(ch[] ch)方法读取所有数据 */
    private static void print_1() {
        /* 创建FileReader引用 */
        FileReader fr = null;
        try {
            /* 创建FileReader对象,关联文件 */
            fr = new FileReader(
                    "C:\\RuntimeDemo.java");

            /* 创建字符串缓冲区 */
            char[] ch = new char[1024];

            int len = 0;

            /* 当read方法返回值不等于-1,继续读文件 */
            while ((len = fr.read(ch)) != -1) {
                System.out.print(new String(ch, 0, len));
            }
        } catch (FileNotFoundException e) {
            throw new RuntimeException("文件不存在");
        } catch (IOException e) {
            throw new RuntimeException("操作失败");
        } finally {
            try {
                if (fr != null) {
                    /* 关闭资源 */
                    fr.close();
                }
            } catch (IOException e) {
                throw new RuntimeException("输入流关闭失败");
            }
        }
    }

    /* 用read()方法读取所有数据 */
    private static void print_2() {
        /* 创建FileWriter引用,关联文件 */
        FileReader fr = null;
        try {
            /* 创建FileWriter对象,关联文件 */
            fr = new FileReader(
                    "C:\\RuntimeDemo.java");

            int ch = 0;

            /* 当read方法返回值不等于-1,继续读文件 */
            while ((ch = fr.read()) != -1) {
                System.out.print((char) ch);
            }
        } catch (FileNotFoundException e) {
            throw new RuntimeException("文件不存在");
        } catch (IOException e) {
            throw new RuntimeException("操作失败");
        } finally {
            try {
                if (fr != null) {
                    fr.close();
                }
            } catch (IOException e) {
                throw new RuntimeException("输入流关闭失败");
            }
        }
    }
}

注意:
打印在控制台上不要用println方法,否则会出现不必要的换行

2.复制文件
将指定文件从一个目录复制到另一个目录。

原理:
将一个文件的内容写入到另一个目录下的另一个文件中
示例代码:

package com.heisejiuhuche.io;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class CopyFileTest {
    private static FileReader fr;
    private static FileWriter fw;

    public static void main(String[] args) {
        copy_1();
//        copy_2();
    }

    /* 用read(ch[] ch)方法复制文件 */
    private static void copy_1() {
        try {
            /* 创建目的文件 */
            fw = new FileWriter(
                    "C:/Users/jeremy/Documents/javaTmp/Runtime.java");

            /* 关联需要复制的文件 */
            fr = new FileReader(
                    "C:\\RuntimeDemo.java");

            /* 创建字符数组缓冲区 */
            char[] buff = new char[1024];

            /* 循环控制 */
            int len = 0;

            /* 将需要复制的文件写入缓冲区,并将缓冲区文件写入目的文件 */
            while ((len = fr.read(buff)) != -1) {
                fw.write(buff, 0, len);
            }
            fw.flush();
        } catch (FileNotFoundException e) {
            throw new RuntimeException("文件不存在");
        } catch (IOException e) {
            throw new RuntimeException("操作失败");
        } finally {
            try {
                if (fw != null) {
                    fw.close();
                }
                if (fr != null) {
                    fr.close();
                }
            } catch (IOException e) {
                throw new RuntimeException("流资源关闭失败");
            }
        }
    }
    /* 用read()方法复制文件 */
    private static void copy_2() {
        try {
            /* 步骤与上述方法相近 */
            fw = new FileWriter(
                    "C:/Users/jeremy/Documents/javaTmp/Runtime.java");
            fr = new FileReader(
                    "C:\\RuntimeDemo.java");

            int ch = 0;

            while((ch = fr.read()) != -1) {
                fw.write(ch);
            }
            fw.flush();
        } catch(FileNotFoundException e) {
            throw new RuntimeException("文件不存在");
        } catch(IOException e) {
            throw new RuntimeException("操作失败");
        } finally {
            try {
                if(fw != null) {
                    fw.close();
                }
                if(fr != null) {
                    fr.close();
                }
            } catch(IOException e) {
                throw new RuntimeException("流资源关闭失败");
            }
        }
    }
}

四、字符流缓冲区

1.概述
缓冲区的出现,提高了字符流对数据的读写效率。缓冲区可以实现先读出一部分数据,再一起写入,避免了硬盘在读与写之间频繁操作,提高读写速度。缓冲区必须结合流才能使用,在流的基础上对流的功能进行了增强。开发时通常都有缓冲区。

2.缓冲区对应类
缓冲区对应两个类,分别是BufferedWriterBufferedReader。它们提供的方法,可以实现对文本字符数据的高效读写。

3.BufferedWriter类
将文本写入字符输出流,缓冲各个字符,从而提供单个字符、数组和字符串的高效写入。在创建BufferedWriter类时,必须先有流对象。

1)文件创建并写入

第一步:创建字符流输出流对象

class BufferedReaderDemo {
    public static void main(String[] args) throws IOException {
        /* 创建一个字符输出流对象 */
        FileWriter fw = new FileWriter("Demo.txt");
    }
}

第二步:创建缓冲字符输出流对象

class BufferedReaderDemo {
    public static void main(String[] args) throws IOException {
        /* 创建一个字符输出流对象 */
        FileWriter fw = new FileWriter("Demo.txt");
        /* 创建缓冲字符输出流对象,并将字符输出流对象作为参数传递给其构造方法 */
        BufferedWriter buffWriter = new BufferedWriter(fw);
    }
}

第三步:调用方法进行写入

class BufferedReaderDemo {
    public static void main(String[] args) throws IOException {
        /* 创建一个字符输出流对象 */
        FileWriter fw = new FileWriter("Demo.txt");
        /* 创建缓冲字符输出流对象,并将字符输出流对象作为参数传递给其构造方法 */
        BufferedWriter buffWriter = new BufferedWriter(fw);

        for(int x = 1; x < 5; x++) {
            buffWriter.write("abcde" + x);
            /* 缓冲区提供的新方法,能跨平台换行 */
            buffWriter.newLine();
            buffWriter.flush();
        }
    }
}

注意:
只要用到缓冲区,就要调用flush()方法进行刷新

第四步:写入完毕,关闭资源

class BufferedReaderDemo {
    public static void main(String[] args) throws IOException {
        /* 创建一个字符输出流对象 */
        FileWriter fw = new FileWriter("Demo.txt");
        /* 创建缓冲字符输出流对象,并将字符输出流对象作为参数传递给其构造方法 */
        BufferedWriter buffWriter = new BufferedWriter(fw);

        for(int x = 1; x < 5; x++) {
            buffWriter.write("abcde" + x);
            /* 缓冲区提供的新方法,能跨平台换行 */
            buffWriter.newLine();
            buffWriter.flush();
        }
        /* 关闭缓冲区 */
        buffWriter.close();
    }
}

注意:
正真进行写入操作的是字符输出流FileWriter;所以关闭缓冲区其实就是关闭FileWriter对象

4.BufferedReader类
从字符输入流中读取文本,缓冲各个字符,从而实现字符、数组和行的高效读取。在创建BufferedReader类时,必须先有流对象。该缓冲区提供了readLine()方法,使获取文本数据更高效。

1)文件读取

第一步:创建字符输入流对象

class BufferedReaderDemo {
    public static void main(String[] args) throws IOException {
        /* 创建一个字符输入流对象并关联目标文件 */
        FileReader fr = new FileReader("Demo.txt");
    }
}

第二步:创建缓冲字符输入流对象

class BufferedReaderDemo {
    public static void main(String[] args) throws IOException {
        /* 创建一个字符输入流对象并关联目标文件 */
        FileReader fr = new FileReader("Demo.txt");
        /* 创建缓冲字符输入流对象,并将字符输入流对象作为参数传递给其构造方法 */
        BufferedReader buffReader = new BufferedReader(fr);
    }
}

第三步:调用方法读取数据

class BufferedReaderDemo {
    public static void main(String[] args) throws IOException {
        /* 创建一个字符输入流对象并关联目标文件 */
        FileReader fr = new FileReader("Demo.txt");
        /* 创建缓冲字符输入流对象,并将字符输入流对象作为参数传递给其构造方法 */
        BufferedReader buffReader = new BufferedReader(fr);
        /* 调用readLine()方法读取数据,如果独到最后一行,该方法返回null */
        String line = null;
        while((line = buffReader.readLine()) != null) {
            System.out.println(line);
        }
    }
}

第四步:关闭缓冲区

class BufferedReaderDemo {
    public static void main(String[] args) throws IOException {
        /* 创建一个字符输入流对象并关联目标文件 */
        FileReader fr = new FileReader("Demo.txt");
        /* 创建缓冲字符输入流对象,并将字符输入流对象作为参数传递给其构造方法 */
        BufferedReader buffReader = new BufferedReader(fr);
        /* 调用readLine()方法读取数据,如果独到最后一行,该方法返回null */
        String line = null;
        while((line = buffReader.readLine()) != null) {
            System.out.println(line);
        }
        /* 关闭缓冲区 */
        buffReader.close();
    }
}

5.缓冲区练习
1)通过缓冲区复制一个.java文件

示例代码:

package com.heisejiuhuche.io;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;

public class CopyByBuffTest {
    private static BufferedWriter buffw;
    private static BufferedReader buffr;

    public static void main(String[] args) {
        copy();
    }

    private static void copy() {
        try {
            /* 分别创建缓冲区对象,并关联文件 */
            buffw = new BufferedWriter(new FileWriter(
                    "C:/Users/jeremy/Documents/javaTmp/SystemDemo.java"));
            buffr = new BufferedReader(new FileReader(
                    "C:\\Users\\jeremy\\java\\workspaces\\test\\src\\com\\heisejiuhuche\\api\\SystemDemo.java"));
            /* 循环控制,记录读取的每一行 */
            String line = null;
            /* 读取数据,直到最后一行 */
            while((line = buffr.readLine()) != null) {
                buffw.write(line);
                buffw.newLine();
                buffw.flush();
            }
        } catch(FileNotFoundException e) {
            throw new RuntimeException("文件不存在");
        } catch(IOException e) {
            throw new RuntimeException("操作失败");
        } finally {
            /* 关闭缓冲区 */
            try {
                if(buffw != null) {
                    buffw.close();
                }
                if(buffr != null) {
                    buffr.close();
                }
            } catch(IOException e) {
                throw new RuntimeException("流资源关闭失败");
            }
        }
    }
}

注意:
使用readLine()方法读取时,不会读取每一行的回车符;所以在写入时,每写一行要调用newLine()方法换行

6.readLine()方法
1)原理

这里写图片描述

readLine()方法的实现是基于read()方法的。如图,readLine()方法在内存中创建了一个字符数组作为缓冲,每次读取操作都调用read()方法,读取一个字符。步骤12345读取了五次,并分别将这五个字符存入字符数组。当读到换行附的时候,readLine()方法不会将换行字符存入数组,而将字符数组中的字符组成字符串返回,再由输出流调用写入方法,在第6步写入目标文件。

2)自定义readLine()方法
模拟一个BufferedReader类,要求有readLine()功能的方法和close()功能的方法。

示例代码:

package com.heisejiuhuche.io;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;

public class MyBufferedReaderTest {
    /* 在main方法中读取一个文件,并在控制台打印,测试MyBufferedReader类 */
    public static void main(String[] args) {
        MyBufferedReader buffr = null;
        try {
            FileReader fr = new FileReader("Demo.txt");

            buffr = new MyBufferedReader(fr);

            String line = null;

            while ((line = buffr.readLine()) != null) {
                System.out.println(line);
            }
        } catch (FileNotFoundException e) {
            throw new RuntimeException("文件不存在");
        } catch (IOException e) {
            throw new RuntimeException("操作失败");
        } finally {
            try {
                if(buffr != null)
                    buffr.close();
            } catch(IOException e) {
                throw new RuntimeException("资源关闭失败");
            }
        }
    }
}

class MyBufferedReader {
    /* 持有FileReader的引用,因为readLine()方法基于read()方法 */
    private FileReader fr;

    /* 初始化时需要传入字符流对象 */
    MyBufferedReader(FileReader fr) {
        this.fr = fr;
    }

    /* 创建readLine()方法,标识异常 */
    public String readLine() throws IOException {
        /* 用StringBuilder代替数组,完成字符串拼接 */
        StringBuilder sb = new StringBuilder();

        int ch = 0;

        /* 调用read()方法,读取数据 */
        while ((ch = fr.read()) != -1) {
            /* 如果读到'\r',跳过,继续读下一个字符 */
            if (ch == '\r')
                continue;
            /*如果读到'\n',标识一行结束,返回这行的字符串 */
            if (ch == '\n')
                return sb.toString();
            else
                /* 如果是字符,就加入StringBuilder */
                sb.append((char)ch);
        }
        /* 这行代码排除了行末没有'\n'而整行字符串不会被打印的情况 */
        if (sb.length() != 0) {
            return sb.toString();
        }
        /* 如果读到文件最后,返回null */
        return null;
    }

    /* 创建关闭缓冲区方法,标识异常 */
    public void close() throws IOException {
        fr.close();
    }
}

程序输出结果:

abcd1
abcd2
abcd3

7.LineNumberReader类
LineNumberReader类是可以跟踪行号的缓冲字符输入流。LineNumberReaderBufferedReader的子类,其使用也必须结合流。该类的用法和BufferedReader基本一致。

1)输出带行号文本

示例代码:

package com.heisejiuhuche.io;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.LineNumberReader;

public class LineNumberReaderDemo {
    public static void main(String[] args) {
        LineNumberReader lnr = null;
        try {
            lnr = new LineNumberReader(new FileReader("SystemDemo.java"));

            String line = null;

            while((line = lnr.readLine()) != null) {
                /* 调用getLineNumber()方法现实行号 */
                System.out.println(lnr.getLineNumber() + ":" + line);
            }
        } catch(FileNotFoundException e) {
            throw new RuntimeException("文件不存在");
        } catch(IOException e) {
            throw new RuntimeException("操作失败");
        } finally {
            try {
                if(lnr != null)
                    lnr.close();
            } catch(IOException e) {
                throw new RuntimeException("资源关闭失败");
            }
        }
    }
}

程序输出部分结果:

6:public class SystemDemo {
7:    public static void main(String[] args) {
8:        Properties prop = System.getProperties();

五、装饰设计模式

1.定义
装饰设计模式是当要对已有对象进行功能增强时,可以定义一个新类,将已有对象传入;基于已有对象的功能提供加强功能;那么自定义的这个类就叫做装饰类。如第四部分自定义readLine()方法练习中,自定义的MyBufferedReader就是一个装饰类。

2.装饰和继承的区别
装饰类增强了原有类的功能,问题是为什么不直接继承原有类,并复写方法,在方法里增强该方法的功能?以一个例子说明装饰和继承的区别。

现在有一个继承体系,MyReader类是基类,用于读取数据;该基类有数个子类,MyTextReaderMyMediaReaderMyDataReader,分别用于读取文本、多媒体和数据库数据,已有继承体系结构如下:

  • MyReader
    • MyTextReader
    • MyMediaReader
    • MyDataReader

现发现三个子类的功能不够强大,要要加缓冲技术;按照继承的思想,那么就在三个子类下面再各自创建三个具备缓冲功能的子类,继承结构变为:

  • MyReader
    • MyTextReader
      • MyBufferedTextReader
    • MyMediaReader
      • MyBufferedMediaReader
    • MyDataReader
      • MyBufferedDataReader

这样做看似没有问题,但是缺点在于拥有缓冲功能的代码没有复用性,并导致继承体系结构庞大臃肿。因此,做好的做法不是创建子类,而是保持原有继承体系不变,将具备缓冲功能的代码提取出来,将需要增强的对象传递进来即可:

class MyBufferedReader {
    MyBufferedReader(MyTextReader text) {}
    MyBufferedReader(MyMediaReader media) {}
    MyBufferedReader(MyDataReader data) {}
}

这样的做法已经简化了继承体系,同时增强了功能;但是后期MyReader基类下继续添加新的子类时,这个MyBufferedReader类还需修改;因此,应该使用多态的特性,在MyBufferedReader中只需接收MyReader类的对象即可;由于MyBufferedReader增强的是MyReader子类的功能,这些功能都是趋同的,因此最好让MyBufferedReader作为MyReader的子类,加入继承体系:

class MyBufferedReader extends MyReader{
    MyBufferedReader(MyReader r) {}
}

新的继承结构如下:

  • MyReader
    • MyTextReader
    • MyMediaReader
    • MyDataReader
    • MyBufferedReader

装饰设计模式比继承灵活,避免了继承体系的复杂臃肿。

3.装饰设计模式特点
装饰设计模式灵活性强,在扩展原有类功能的前提下,不失原有类的方法,即后期开发中,扩展功能和原有功能都可以视情况使用。