About Java IO (a): decorative pattern

Java's IO system uses a decorator design pattern. IO byte-oriented and which is divided into two kinds of character-oriented, byte-oriented input and output in byte unit, in characters for character input and output units. Further, in each section, the input and output is divided into two parts correspond to each other, such as InputStreamthe type and OutputStreamtype. Further down points, divided into data source type and decorative type. Data source type is represented by the data source and destination, and the type of decorator to the inputs and outputs may confer additional functions.

 
Java IO structure

In use, in order to obtain the input and output functions we need, we often need to be combined to a data source and a plurality of decorative objects. For example, we need a way to buffer the data byte read from the local file, then it needs to be a FileInputStreamtarget and a BufferedInputStreamtarget combined, wherein the FileInputStreamobject is responsible for reading data in units of bytes from the file, and the BufferedInputStreamobject is responsible for the buffering the read data.

If you do not understand the words of decorative patterns, Java IO will become difficult to understand. If the structure is unclear Java IO, then, you will find it difficult to use. This blog combined with decorative pattern describes the structure of the Java IO, as well as achieve some IO class. This is actually my study notes, if insufficient, welcome that.


First, the input source

Our input, for example, to explain the structure of the Java IO. The basic function of the input data is to be read from an input source. The input source may be a file, there may be a ByteArraytarget, there may be a Stringtarget. Different data sources, read in different ways. Therefore, Java developers to write for each input source corresponding input class, there are reads data from a file FileInputStream, from there ByteArrayread data objects ByteArrayInputStream, ....... Unified interface to reduce duplicate write code, Java designers inputs from these classes, the same parts extracted, abstract prepared based input InputStream, as the base class for all input class. So far, it can be summarized as follows class diagram, for convenience of description, and methods are omitted member variables.

 
Structure of the input source

Where, InputStreamit is an abstract class, which is the ancestor of all input sources. It provides the interface input source, which read()is a byte is read from the input source, and returns the return value to form. And read(byte[] b)to read data from a source to the input byte[] b, it returns the value of the number of bytes actually read. And read(byte[] b, int off, int len)it was read from the input source lenbytes, filled into byte[] bthe b[off]position on and after.

The read operation of the input source to the input source varies due, therefore, InputStreamthe read()method is abstract, an input source by the particular subclasses implement.

In InputStreamthe, read(byte[] b)and read(byte[] b, int off, int len)it is invoked read()to achieve, i.e., constant use read()to read into the byte one by one and placed byte[] bin the proper position. But this reading, efficiency is not high. In Case moving bricks, we moved to B brick 10 at the walls of the old master from A. In InputStreamthe handling logic, we need to pick up a brick from A, B ran at the bricks to the old master, back to the B, and then pick up an ....... Run a lot of times, wasting a lot of time, effort large, you can pick up the bricks 10, a one-time move those. Therefore, in most of its subclasses, these methods are overridden.

Since the need to read the file system of the operating system call, need C/C++to complete, therefore, in the FileInputStreammiddle, there are two nativemethods, read0()and readBytes(byte[] b, int off, int len), for each system call and call a byte read file system call to read the 1 byte in the file stack. Other methods are read by calling these two methods to achieve.

Second, the decorator

With the following input sources, we are able to complete the operation of a variety of reading data. We can read a byte from a data source, or a bunch of bytes. However, for performance and other considerations, we usually give input operation will add some features, such as a buffer.

1. buffer

An example of moving bricks talked about before, we move from place A to 10 bricks at the old master B, taking into account the heavy task master craftsman today drywall, then it might let us go to him moving bricks, so we better to give him a one-time move past few put at B, and then he showed us that he can be brick from B just fine, you do not have to go at a moving bricks coming. This saves a lot of time transmission.

Buffer is such a reason. We usually give input and output establishment of a buffer zone. After taking into account the likely read the data again, when the data is read, in addition to the data we need, but also to read some of the data in, into the buffer. Before each reading data, will take a look at our data, there are no buffer want, if any, will read from the buffer, if not go inside to read the data source. And in the output data, the output data will first go to the buffer, when the buffer is full, then all the data in the output buffer to the destination in.

Note: read and write buffers should also consider the consistency of the data, there is not too much exposition.

2. decorator class

Like buffer, we usually give input and output plus some additional features. So the question is, how can we make each input source with these functions? The simplest is that for each additional function for each input source are writing a class like this (in order to allow small map that omitted other input source).

 
Class structure when not in use Decorator

This design will bring a lot of problems.

  • First, the class too much. In the case without considering the combination of features, if there are m input source to achieve n functions, it would need to write m-by-n classes, consider the combination of features, then even more.
  • Second, too much repetition code. In fact, the same function code are similar, but give each input source to write again. The writing of trouble, that time to change the function of the code, had one change in the past, is not conducive to maintenance.

In order to solve the above problem, Java designers will carry out individual functions, each function to write a separate class function, such as by BufferedInputStreamthe class to provide cushioning function as the input source, by DataInputStreamproviding basic types of data as an input source class read the function. Note that, at this time, functional class only provides the function itself does not read data from the input source , so that inside the class will have a function in the member variables of a data source class, the data read operation from the data source are by this member variables to complete. Like this:

class Func1Decorator extends InputStream { private InputStream in; Func1Decorator(InputStream in){ this.in = in; } public int read() { ... a = in.read(); ... } ... } 

Knowledge: in fact, it can be seen from here, the combination to be more flexible than inheritance, and polymorphism because the combination can be combined.

在功能类初始化时,就从外界传入了输入源对象,其后,从数据源读取数据的操作都由这个对象负责,而功能类仅负责对读入的数据进行处理来完成其功能。

注意到,这里的功能类还继承了输入源类 InputStream。一方面,这是因为从外界看来,功能类确实是一个 InputStream,它实现了 InputStream 中所有的接口。它的语意是一个带有 Func1 功能的 InputStream。另一方面,这也方便了功能的组合,当功能类同时也是 InputStream 时,要组合两个功能到一起时,只需要按一定的顺序把一个功能类的对象看作输入源对象传入进去即可。如:

DataInputStream in = new DataInputStream(
                        new BufferedInputStream(new FileInputStream("filename"))); 

上面这段代码创建了一个能读取基本数据类型数据并带有缓冲的文件输入对象。因为功能类也是一个 InputStream,它可以被当作其他功能类的数据源类,其他的功能类会在它的 read 方法的基础上,继续拓展自己的功能。

其实,之前我们所说的功能类就是装饰器,用来给基础类扩展功能。而这种用组合语法利用多态为基础类扩展功能的模式就是装饰模式。

3. 装饰器模式的优点

  1. 装饰模式分离了装饰类和被装饰类的逻辑。装饰器类中保持了一个被装饰对象的引用,当装饰器类需要底层的功能时,只需要通过这个引用调用对应方法即可,并不需要了解其具体逻辑。这对代码的维护有很大的帮助。

  2. 装饰模式可以减少类的数量。在前面我们已经看到了,用纯继承语法来扩展功能需要为每种基础类和功能的各种组合编写类,类的数量会非常地多。而通过装饰器模式,我们只需要写几个装饰器类就可以了。装饰器类中保持的被装饰对象的引用,会发挥其多态性,我们传入什么基础类对象,就执行对应的方法。这使得一个装饰器类可以和几乎所有基础类(及其子类,从语义上来说,子类是特殊的父类)结合产生相应的扩展类。

  3. 装饰模式的扩展性很好。当要为基础类扩展新的功能时,用纯继承语法需要为每种基础类,为另外的各种功能组合编写类。但使用装饰器模式的话,只需要编写一个装饰器类即可。

装饰模式利用了组合语法,在复用代码时,组合语法与继承语法相比有一个明显的优点,就是可以利用多态,从而根据组合对象的不同能够产生不同的语义

三、结构

装饰模式的通用类图如下:

 
装饰器模式的通用类图

在我们之前的叙述中,是没有中间这个 Decorator 抽象类的。它是所有装饰器类的父类,它一方面可以使类的结构更加清晰,另一方面这个抽象类可以减少各个子类中重复逻辑的书写。当然,我们刚才所叙述的也是装饰模式,只不过没有了 Decorator 抽象类,所有的装饰器类都是直接继承自 Component 的。这是一种简化的装饰模式。当装饰器数量比较少时,可以省略装饰器基类。另外在确定只有一种 Component 时,可以不写 Component 基类,用那一个 ConcreteComponent 来代替 Component 基类。

下面是 Java IO 的类图,只画了字节流的输入部分,其他部分相似。另外,因为页面的大小是有限的,而且一些类在类结构中的位置是相似的,所以省略了一些类。

 
Java IO 的结构

其中,FilterInputStream 就是装饰模式中的 Decorator 基类。继承自它的都是装饰器类,它们为输入扩展了功能。

四、参考资料

  • JDK文档
  • 《Thinking in Java》
  • 《设计模式之禅》


Author: Happioo
link: https: //www.jianshu.com/p/33a57fd4725e
Source: Jane books
are copyrighted by the author. Commercial reprint please contact the author authorized, non-commercial reprint please indicate the source.

Guess you like

Origin www.cnblogs.com/eryun/p/12100490.html