Theme index -- Keyboard shortcut: 'u'  Previous theme in this lecture -- Keyboard shortcut: 'p'  Next slide in this lecture -- Keyboard shortcut: 'n'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.

40.  Patterns and Techniques

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 Pattern40.2 The Decorator Pattern and Streams
 

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.

  • Component: Defines the common interface of participants in the Decorator pattern

  • Decorator: References another Component to which it delegates responsibilities

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;
  }

}
Program 40.1    A program that compresses a file.

1
2
> compress compress.cs out
CompressionRate: 545/1126
Listing 40.2    Sample application together with program output (compression rate).

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);
    }
  }

}
Program 40.3    The corresponding program that decompresses the file.

With this we are done with the IO lecture.

Generated: Monday February 7, 2011, 12:20:06
Theme index -- Keyboard shortcut: 'u'  Previous theme in this lecture -- Keyboard shortcut: 'p'  Next slide in this lecture -- Keyboard shortcut: 'n'