Input and Output Classes |
A complete PDF version of the text book is now available. The PDF version is an almost complete subset of the HTML version (where only a few, long program listings have been removed). See here. |
In relation to streams, which we discussed in Chapter 37 in the beginning of the IO lecture, it is relevant to bring up the Decorator design pattern. Therefore we conclude the IO lecture with a discussion of Decorator.
|
40.1. The Decorator Pattern
Contents Up Previous Next Slide Annotated slide Aggregated slides Subject index Program index Exercise index
It is often necessary to extend an object of class C with extra capabilities. As an example, the Draw method of a Triangle class can be extended with the traditional angle and edge annotations for equally sized angles or edges. The typical way to solve the problem is to define a subclass of class C that extends C in the appropriate way. In this section we are primarily concerned with extensions of class C that do not affect the client interface of C. Therefore, the extensions we have in mind behave like specializations (see Chapter 25). The extensions we will deal with consist of adding additional code to the existing methods of C.
The decorator design pattern allows us to extend a class dynamically, at run-time. Extension by use of inheritance, as discussed above, is static because it takes place at compile-time. The main idea behind Decorator is a chain of objects, along the line illustrated in Figure 40.1. A message from Client to an instance of ConcreteComponent is passed through two instances of ConcreteDecorator by means of delegation. In order to arrange such delegation, a ConcreteDecorator and a ConcreteComponent should implement a common interface. This is important because a ConcreteDecorator is used as a stand in for a ConcreteComponent. This arrangement can be obtained by use of the class hierarchy shown in Figure 40.2.
Figure 40.1 Two decorator objects of a ConcreteComponent object |
In Figure 40.2 the Decorators and the ConcreteComponent share a common, abstract superclass called Component. When a Client operate on a ConcreteComponent it should do so via the type Component. This facilitates the object organization of Figure 40.1, because a Decorator can act as a stand in for a ConcreteComponent.
Figure 40.2 A template of the class structure in the Decorator design pattern. |
|
The class diagram of Decorator is similar to Composite, see Section 32.1. In Figure 40.2 a Decorator is intended to aggregate (reference) a single Component. In Figure 32.1 a Composite typically aggregate two or more Components . Thus, a Composite typically gives rise to trees, whereas a Decorator gives rise to linear lists.
Decorator objects can be added and chained at run-time. A Client accesses the outer Component (typically a ConcreteDecorator), which delegates part of the work to another Component. While passing, it does part of the work itself.
Use of Decorator can be seen as a dynamic alternative to static subclassing |
40.2. The Decorator Pattern and Streams
Contents Up Previous Next Slide Annotated slide Aggregated slides Subject index Program index Exercise index
The Decorator discussion above in Section 40.1 was abstract and general. It is not obvious how it relates to streams and IO. We will now introduce the stream decorators that drive our interest in the pattern. The following summarizes the stream classes that are involved.
We build a compressed stream on a buffered stream on a file stream The compressed stream decorates the buffered stream The buffered stream decorates the file stream |
The idea behind the decoration of class FileStream (see Section 37.4) is to supply additional properties of the stream. The additional properties in our example are buffering and compression. Buffering may result in better performance because many read and write operations do not need to touch the harddisk as such. Use of compression means that the files become smaller. (Notice that class FileStream already apply buffering itself, and as such the buffer decoration is more of illustrative nature than of practical value).
Figure 40.3 corresponds to Figure 40.1. Thus, Figure 40.3 shows objects, not classes. A FileStream object is decorated with buffering and compression. A Client program is able to operate on GZipStream (a compressed stream) as if it was a FileStream.
Figure 40.3 Compression and buffering decoration of a FileStream |
In Program 40.1 we read a FileStream into a buffer of type byte[]. This is done in line 11-16. In line 18-27 we establish the decorated FileStream (see the purple parts). In line 27 we write the buffer to the decorated stream. In line 29-32 we compare the size of the original file and the compressed file. We see the effect in Listing 40.2 when the program is applied on its own source file.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | using System; using System.IO; using System.IO.Compression; public class CompressProg{ public static void Main(string[] args){ byte[] buffer; long originalLength; // Read a file, arg[0], into buffer using(Stream infile = new FileStream(args[0], FileMode.Open)){ buffer = new byte[infile.Length]; infile.Read(buffer, 0, buffer.Length); originalLength = infile.Length; } // Compress buffer to a GZipStream Stream compressedzipStream = new GZipStream( new BufferedStream( new FileStream( args[1], FileMode.Create), 128), CompressionMode.Compress); compressedzipStream.Write(buffer, 0, buffer.Length); compressedzipStream.Close(); // Report compression rate: Console.WriteLine("CompressionRate: {0}/{1}", MeasureFileLength(args[1]), originalLength); } public static long MeasureFileLength(string fileName){ using(Stream infile = new FileStream(fileName, FileMode.Open)) return infile.Length; } } | |||
|
1 2 | > compress compress.cs out CompressionRate: 545/1126 | |||
|
When Program 40.1 is executed, a compressed file is written. In Program 40.3 we show how to read this file back again. In line 11-17 we set up the decorated stream, very similar to Program 40.1. In line 21-28 we read the compressed file into the buffer, and finally in line 32-35 we write the buffer back to an uncompressed file.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | using System; using System.IO; using System.IO.Compression; public class CompressProg{ public static void Main(string[] args){ byte[] buffer; const int LargeEnough = 10000; Stream compressedzipStream = new GZipStream( new BufferedStream( new FileStream( args[0], FileMode.Open), 128), CompressionMode.Decompress); buffer = new byte[LargeEnough]; // Read and decompress the compressed stream: int bytesRead = 0, bufferPtr = 0; do{ // Read chunks of 10 bytes per call of Read: bytesRead = compressedzipStream.Read(buffer, bufferPtr, 10); if (bytesRead != 0) bufferPtr += bytesRead; } while (bytesRead != 0); compressedzipStream.Close(); // Write contens of buffer to the output file using(Stream outfile = new FileStream(args[1], FileMode.Create)){ outfile.Write(buffer, 0, bufferPtr); } } } | |||
|
With this we are done with the IO lecture.