Thorough analysis of Java IO Design Patterns

Source: Internet
Author: User

 

I. Introduction (General Introduction to Java I/O)

Regardless of the programming language, input and output are an important part. Java is no exception, and Java greatly expands the input/output functions and application scope. It uses the stream mechanism to implement input/output. The so-called stream is the orderly arrangement of data, and the stream can be from a source (called streams or source of stream, to a destination (called a streaming or sink of stream. A program reads data from the input stream and writes data to the output stream.

For example, a program can use the fileinputstream class to read data from a disk file, as shown in:

 

A stream processor like fileinputstream is called a stream processor. Like a Stream pipeline, it sucks some type of data from a queue and outputs some type of data. The above is called a Stream pipeline diagram.

Similarly, you can use the fileoutputstream class to write data to a disk file, as shown in:

   

In practice, this mechanism is not very useful, and the program needs to write very structured information. Therefore, the byte data is actually numerical values, text, source code, and so on. The Java I/O Library provides a chaining mechanism that connects a stream processor to the beginning and end of another stream processor and uses the output of one of them as the input, form a Stream pipeline link.

For example, the datainputstream stream processor can use the output of the fileinputstream Stream object as the input, and convert the byte type data to the original Java type and string type data. As shown in:

 

Similarly, writing data of the byte type to a file is not a simple process. The data that a program needs to write to a file is often structured, while the byte type is the original type. Therefore, it must be converted during writing. The dataoutputstream stream processor receives the raw data type and the string data type, while the output data of this stream processor is of the byte type. That is to say, dataoutputstream can convert the source data to byte data and then input it.

In this way, you can link dataoutputstream to fileoutputstream, so that the program can write source data of the original data type and string type into the dual pipeline of this link, to write structured data to a disk file, as shown in:

 

This is the major role of the link.

The stream processing process by the stream processor must all have streams. If the streams processed by the stream class are classified, they can be basically divided into two categories:

The first array, String, file, etc. This is called the original struct.

Second, a stream of the same type is used as a link stream class, called a link pipeline.

Design Principles of Java I/O Libraries

The Java I/O Library abstracts a variety of common tokens, streams, and processing processes. The client's Java program does not need to know the final scheme, whether the streaming is a file on the disk or an array, or whether the data is buffered, and whether the data can be read by row number or other processing details.

As mentioned in the book, people who first met the Java/IO library are all confused by the complexity of the library, and those who are familiar with the library, however, it is often difficult to argue whether the library is designed properly. The author of the book puts forward his own opinions. To understand the huge and complex library of Java I/O, the key is to master two symmetry and two design pattern modes.

Java I/O libraries have two symmetry:

1. Input-Output symmetry. For example, inputstream and outputstream occupy the root of two parallel hierarchical structures of the input and output of the byte stream. Reader and writer occupy the root of the two parallel hierarchical structures of the input and output of the char stream.

2 byte-Char symmetry. inputstream and reader sub-classes are responsible for the input of byte and char streams respectively. outputstream and writer sub-classes are responsible for the output of byte and char streams respectively, they form a parallel hierarchical structure.

Two design modes of the Java I/O Library:

The overall design of the Java I/O Library conforms to the decorator and adapter modes. As mentioned above, the class for processing a stream in this database is called a stream class. The fileinputstream, fileoutputstream, datainputstream, and dataoutputstream mentioned in the Introduction are examples of stream processors.

1. modifier mode: in the hierarchical structure represented by inputstream, outputstream, reader, and writer, some stream processors can play a decorative role on other stream processors to form a new one, stream processor with improved features. The modifier mode is the overall design mode of the Java I/O library. This principle complies with the modifier mode, as shown in:

 

2. Adapter mode: in the hierarchical structure represented by inputstream, outputstream, reader, and writer, some stream processors adapt to other types of processors. This is the application of the adapter mode, as shown in.

  

The adapter mode is applied to the original stream processor design and forms the starting point of all stream processors in the I/O library.

This evening, we will first go here. Tomorrow, we will take a closer look at how the two design patterns are applied in the I/O library.

 

 

JDK provides a large number of class libraries for programmers. To maintain the reusability, scalability, and flexibility of class libraries, a large number of design modes are used, this article describes the decorator mode used in the jdk I/O package, and uses this mode to implement a new output stream class.

Decorator Mode
The decorator mode, also known as wrapper, is used to dynamically add some additional responsibilities to an object. It is more flexible than generating child classes.
Sometimes, we need to add some new functions for an object instead of the entire class. For example, we need to add a scroll bar to a worker area. We can use the inheritance mechanism to implement this function, but this method is not flexible enough. We cannot control the method and timing of adding a scroll bar in the render area. In addition, when you need to add more functions to the partition, such as borders, you need to create a new class. When you need to combine these functions, it will undoubtedly cause the class explosion.
We can use a more flexible method, that is, to embed the partition area into the scroll bar. The class of this scroll bar is equivalent to a decoration of the worker area. This decoration (scroll bar) must be inherited from the same interface as the decorated component, so that users do not have to care about the decoration implementation, because it is transparent to them. The decoration will forward user requests to corresponding components (that is, call related methods), and may make some additional actions (such as adding a scroll bar) before and after the forwarding ). In this way, we can add any number of functions by embedding different decorations in the embedding area according to the combination. This dynamic method of adding functions to objects does not cause class explosion, but also has more flexibility.
The above method is the decorator mode, which dynamically adds new functions by adding decoration to the object. The following is a UML diagram of the decorator mode:

 

Component is a common parent class for components and decoration. It defines the methods that must be implemented by subclass.
Concretecomponent is a specific component class. You can add decoration to it to add new functions.
Decorator is a common parent class for all decorations. It defines the methods that must be implemented for all decorations. It also saves a reference to component to forward user requests to component, some additional actions may be executed before and after the request is forwarded.
Concretedecoratora and concretedecoratorb are specific decorations that can be used to decorate specific component.

Decorator mode in Java Io package
The decorator mode is used in the Java. Io package provided by JDK to encapsulate various input and output streams. The following uses Java. Io. outputstream and its subclass as an example to describe how to use the decorator mode in Io.
First, let's look at the code used to create an IO stream:

 

 
 
 

The following is a code snippet:
Try {
Outputstream out = new dataoutputstream (New fileoutputstream ("test.txt "));
} Catch (filenotfoundexception e ){
E. printstacktrace ();
}

 

 

This code is no longer familiar to those who have used JAVA input and output streams. We use dataoutputstream to encapsulate a fileoutputstream. This is a typical decorator mode. fileoutputstream is equivalent to component, and dataoutputstream is a decorator. You can easily understand the code by changing it to the following:

 

 

 
 
 

The following is a code snippet:
Try {
Outputstream out = new fileoutputstream ("test.txt ");
Out = new dataoutputstream (out );
} Catch (filenotfoundexception e ){
E. printstatcktrace ();
}

 

 

Since fileoutputstream and dataoutputstream have public parent classes outputstream, object decoration is almost transparent to users. Next let's take a look at how outputstream and its subclass form the decorator mode:

 

Outputstream is an abstract class that is the common parent class of all output streams. Its source code is as follows:

 

 
 
 

The following is a code snippet:
Public abstract class outputstream implements closeable, flushable {
Public abstract void write (int B) throws ioexception;
...
}

 

It defines the abstract method of write (int B. This is equivalent to the component class in decorator mode.

Bytearrayoutputstream, fileoutputstream, and pipedoutputstream are inherited directly from outputstream. Take bytearrayoutputstream as an example:

 

 
 
 

The following is a code snippet:
Public class bytearrayoutputstream extends outputstream {
Protected byte Buf [];
Protected int count;
Public bytearrayoutputstream (){
This (32 );
}
Public bytearrayoutputstream (INT size ){
If (size <0 ){
Throw new illegalargumentexception ("negative initial size :"
+ Size );
}
Buf = new byte [size];
}
Public synchronized void write (int B ){
Int newcount = count + 1;
If (newcount> Buf. Length ){
Byte newbuf [] = new byte [math. Max (BUF. Length <1, newcount)];
System. arraycopy (BUF, 0, newbuf, 0, count );
Buf = newbuf;
}
Buf [count] = (byte) B;
Count = newcount;
}
...
}

 

It implements the write (int B) method in outputstream, so we can create an output stream object and complete the output in a specific format. It is equivalent to the concretecomponent class in decorator mode.

Next, let's take a look at filteroutputstream. The Code is as follows:

 

 
 
 

The following is a code snippet:
Public class filteroutputstream extends outputstream {
Protected outputstream out;
Public filteroutputstream (outputstream out ){
This. Out = out;
}
Public void write (int B) throws ioexception {
Out. Write (B );
}
...
}

 

 

It also inherits from outputstream. However, its constructor is very special. It needs to pass an outputstream reference to it and it will save the reference to this object. If no specific outputstream object exists, we cannot create filteroutputstream. Because out can be a reference to the filteroutputstream type or a reference to a specific output stream class such as bytearrayoutputstream, we can add a variety of decorations for bytearrayoutputstream using multi-layer nesting. This filteroutputstream class is equivalent to the decorator class in the decorator mode. Its write (int B) method simply calls the write (int B) method of the incoming stream, instead of doing more processing, it basically does not have convection decoration, so the subclass that inherits it must overwrite this method to achieve the purpose of decoration.

Bufferedoutputstream and dataoutputstream are two subclasses of filteroutputstream. They are equivalent to concretedecorator in decorator mode, and different decorations are made on the input output stream. Take the bufferedoutputstream class as an example:

 

 
 
 

The following is a code snippet:
Public class bufferedoutputstream extends filteroutputstream {
...
Private void flushbuffer () throws ioexception {
If (count> 0 ){
Out. Write (BUF, 0, count );
Count = 0;
}
}
Public synchronized void write (int B) throws ioexception {
If (count> = Buf. Length ){
Flushbuffer ();
}
Buf [count ++] = (byte) B;
}
...
}

 

 

This class provides a caching mechanism to write data into the output stream when the cache capacity reaches a certain number of bytes. First, it inherits filteroutputstream and overwrites the write (int B) method of the parent class. before calling the output stream to write data, it will check whether the cache is full. If it is not full, it will not be written. In this way, new functions are dynamically added to the output stream object.
Next, we will use the decorator mode to write a new output stream for Io.

Write a new output stream by yourself
After understanding the structure of outputstream and its subclass, we can write a new output stream to add new functions. This section provides an example of a new output stream, which filters out space characters in the statements to be output. For example, to output "Java Io outputstream", the filtered output is "javaiooutputstream ". The code for the skipspaceoutputstream class is as follows:

 

 
 
 

The following is a code snippet:
Import java. Io. filteroutputstream;
Import java. Io. ioexception;
Import java. Io. outputstream;
/**
* A new output stream, which will check the space character
* And won't write it to the output stream.
* @ Author magic
*
*/
Public class skipspaceoutputstream extends filteroutputstream {
Public skipspaceoutputstream (outputstream out ){
Super (out );
}
/**
* Rewrite the method in the parent class, and
* Skip the space character.
*/
Public void write (int B) throws ioexception {
If (B! = ''){
Super. Write (B );
}
}
}

 

It inherits from filteroutputstream and overwrites its write (int B) method. In the write (int B) method, the input character is checked first. If it is not a space, it is output.

The following is a test procedure:

 

 
 
 

The following is a code snippet:
Import java. Io. bufferedinputstream;
Import java. Io. datainputstream;
Import java. Io. dataoutputstream;
Import java. Io. ioexception;
Import java. Io. inputstream;
Import java. Io. outputstream;
/**
* Test the skipspaceoutputstream.
* @ Author magic
*
*/
Public class test {
Public static void main (string [] ARGs ){
Byte [] buffer = new byte [1024];

/**
* Create input stream from the standard input.
*/
Inputstream in = new bufferedinputstream (New datainputstream (system. In ));

/**
* Write to the standard output.
*/
Outputstream out = new skipspaceoutputstream (New dataoutputstream (system. Out ));

Try {
System. Out. println ("Please input your words :");
Int n = in. Read (buffer, 0, buffer. Length );
For (INT I = 0; I <n; I ++ ){
Out. Write (buffer [I]);
}
} Catch (ioexception e ){
E. printstacktrace ();
}
}
}

 

To run the above test program, you are required to enter information in the console window. The program filters out spaces in the information and outputs the final result to the console window. For example:

 

 
 
 

The following is a reference clip:
Please input your words:
A B C D E F
Abcdef

 

Summary

In the java. Io package, not only does outputstream use the decorator design mode, but also inputstream, reader, and writer use this mode. As a flexible and scalable class library, JDK uses a large number of design patterns, such as the MVC pattern in the swing package and the proxy pattern in RMI. The Research on models in JDK not only deepens the understanding of the models, but also facilitates a more thorough understanding of the structure and composition of the class libraries.

 

 

 

Trackback: http://tb.blog.csdn.net/TrackBack.aspx? Postid = 551960

 

 

 

David's design patterns study note 11: decorator

I. Overview
Inheritance is a basic method for extending classes to provide more features, but sometimes simple inheritance may not meet our needs. For example, our system needs to provide multiple types of products:
Type A, type B ,...
At the same time, these products need to support multiple features:
Features a, features B ,...
There are two possible implementations:
1. Inheritance: Implement AA, AB, Ba, BB ,...
This implementation method will cause "class explosion" when the number of types and the number of supported features are large, that is, too many types will be introduced, and the encapsulation of this implementation is also very poor, it is very difficult for the customer to write the code and is very undesirable.
2. Modify the implementation of each type, including whether features a, B, and... are supported, and enable each feature based on the customer's choice. This implementation is a typical MFC implementation method, but this implementation is only suitable for scenarios with extremely stable features. Otherwise, each type of implementation may need to be modified when the features increase or decrease.
Therefore, although similar implementations are not uncommon, the above two implementation methods cannot meet the design requirements of dynamic changes of types or features because they are too sensitive to feature changes or type changes. If we can define the types and features separately, and dynamically combine the types and features according to the needs of the customer code, we can overcome the above problems.
As the name of the decorator mode implies, the decorator mode can play a role when we need to add some additional features/features to the object. In addition, more importantly, the decorator model studies how to dynamically attach more features to an object in a transparent manner to the customer. In other words, the client does not think that objects are different before and after decoration. The decorator mode can extend the functions of objects without creating more child classes. The decorator mode uses an instance of a subclass of the previously decorated class to delegate client calls to the decorated class. The key of the decorator mode is that the extension is completely transparent.
Because the decorator mode can dynamically expand the features of decoratee, some people call it the "dynamic inheritance mode", which is based on inheritance, but different from the Function Extension under static inheritance, such extensions can be dynamically assigned to decoratee.

Ii. Structure
Shows the structure of the decorator mode:

Figure 1: demo of the decorator Mode
The preceding class diagram consists of the following components:
1. component (Abstract component) Role: provides an abstract interface to standardize the objects to receive additional responsibilities.
2. concrete component (specific component) Role: defines a class that will receive additional responsibilities.
3. decorator: holds an instance of a component object and defines an interface consistent with the abstract Component Interface.
4. Concrete decorator (Specific decoration) Role: "attaches" to the component object.

Iii. Application
You can consider using the decorator mode in the following cases:
1. Add roles to a single object dynamically and transparently without affecting other objects. (See the example)
2. Handle responsibilities that can be undone. (Similar to the above, when you need to dynamically add and revoke a decoration class, you can add a corresponding decoration class member to the class, but set this member to null when you want to undo the decoration. Similarly, it is also easy to support dynamic switching)
3. When the subclass generation method cannot be used for expansion. One case is that there may be a large number of independent extensions. To support each combination, a large number of child classes will be generated, increasing the number of child classes exponentially. Another scenario is that the class definition is hidden, or the class definition cannot be used to generate a subclass. (As long as you have learned the arrangement and combination, it is not hard to understand)

The difference between decorator and adapter is that the former does not change the interface, while the latter provides a new interface. Decorator can be regarded as a degraded composite with only one component. However, the purpose of decorator is to add some additional responsibilities to the object, rather than object aggregation.

Since the decorator model is so powerful, can it be widely promoted, and a large number of use of decorator to replace simple inheritance? In this way, you can exit the historical stage by extending the class functions directly through inheritance! In fact, this is not possible, mainly because:
1. Although "inheritance damages encapsulation" (the parent class opens too many permissions to sub-classes), inheritance is the most basic relationship in the objective world and OOP, and, inheritance is the basis for deepening interface specifications. Without inheritance, there will be no polymorphism and many other oo features. Therefore, inheritance is more common and easier to define and implement than decorator;
2. Because the dynamic superposition of decorator does not affect the requirements of decoratee and other features, it is difficult for decorator to define complex features;
3. decorator is a combination of aggregation and inheritance. Other restrictions exist in the decorate mode, which will be discussed in the implementation example section.

Iv. Advantages and Disadvantages
The decoration mode has the following advantages:
1. The purpose of the relationship between the decoration mode and inheritance is to expand the object functions, but the decoration mode can provide more flexibility than inheritance.
2. By using different specific decorative classes and the arrangement and combination of these decorative classes, designers can create a combination of many different behaviors.

The decoration mode has the following Disadvantages:
Because of the decorative pattern, there may be a few categories that are needed in comparison to the inheritance relationship, and fewer classes are used, although the design is easier, on the other hand, the disadvantage of decorator is that some very similar small objects are generated, which are customized to provide a very small number of special functions.

V. Example
The basic implementation of the decorator mode is as follows: the decorator class is derived from the base class component of the concretecomponent class to maintain the same interface as concretecomponent, all function calls are forwarded to the internal concretecomponent object for execution. (1. The concretecomponent object is passed in through the concretecomponent of decorator. 2. Some "modifiers" are often added. Otherwise, the decorator will be in vain ).

In the following example, xsstream is used to decorate sstream to count the number of times operator is called <. The sample code is as follows:

# Include <iostream>
Using namespace STD;

Struct stream
{
Virtual stream & operator <(int I) = 0;
Virtual stream & operator <(const char * Str) = 0;
};

Struct sstream: Public stream
{
Stream & operator <(int I)
{
Cout <I;
Return * this;
}
Stream & operator <(const char * Str)
{
Cout <STR;
Return * this;
}
};

Class xsstream: Public stream
{
Static int count;
Stream * pS;
Public:
Xsstream (stream * s): PS (s ){}

Stream & operator <(int I ){
* Ps <"this is [" <++ count <"] Call to operator <. I =" <I <"
";
Return * this;
}
Stream & operator <(const char * Str ){
* Ps <"this is [" <++ count <"] Call to operator <. Str =" <STR <"
";
Return * this;
}
};
Int xsstream: Count = 0;

Int main (){
Sstream SS;
Stream * PS = new xsstream (& SS );
Stream & s = * pS;

Int I = 1;
S <I <"ABC ";

Delete pS;

Return 0;
}

The decorator Mode Implemented by the above method is essentially an inclusive extension (because there is only one concretecomponent, it looks like the proxy mode, but the intent is different), although the above example has no application value, however, it basically clarifies the basic implementation methods of the decorator:
Redefine the interface methods in decoratee (because decorator and decoratee are derived from the same base class, this is possible), add the necessary Decoration in it, call the corresponding decoratee method to complete the work other than decoration. for auxiliary methods that do not need to be modified, you can directly forward the method call to decoratee.

However, the above implementation also tells us that the decorator mode has a very important limitation:
If we derive from an abstract base class, we must implement every virtual method of the abstract base class (or, for a non-virtual base class, or overload the base class method, either use the implementation of the base class, but this will lose the implementation of the overload in the concretecomponent subclass, that is, the (-) feature is lost, which is modified with the decorator mode, that is, add (+) the essence of the feature is different), and when the number of virtual methods is large, this will become a burden. For example, operator implemented by the actual basic_ostream To solve this problem, we can derive from sstream instead of stream. This trick seems to be "useful" when we only need to describe some of the methods that have been implemented ", however, this is contrary to the original intention of the decorator mode. Because the purpose of the decorator mode is to modify a class system, rather than a single class. To modify a single class, simple inheritance extension can solve the problem. You do not need to consider this problem from the perspective of decorator. If you are dealing with multiple classes, in this way, we must face the dilemma of "class combination explosion" again.
Fortunately, the above problem does not exist in message/event-based applications. For example, in MFC, when performing interface modification, you can only process specially modified messages, forward all other messages to the control to be modified.
In this case, the decorator mode seems to be useful in the MFC application, but this is not the case. This is probably because one of the decorator modes will complicate the customer code and we must create our own features, instead of specifying it directly at create, the implementer of MFC wants the MFC encapsulation class to maintain the same interface as the API, and the static designation of the feature when creating the control and the dynamic creation and designation by the user, the implementer of MFC selects the former. Second, we can see from the above that the decorator mode is suitable for defining small features. When the features or classes are very complex, the decorator mode is hard to cover all aspects.

In Java, Java. io. linenumberreader is a typical decorator. It can decorate the entire reader class to collect linenumber information while reading file information. You can use linenumberreader after Readline. the getlinenumber method gets the current row number. Its implementation is relatively simple. If you are interested, you can study it.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.