Home Decorator design pattern and Java I/O package
Post
Cancel

Decorator design pattern and Java I/O package

The decorator pattern is a way to dynamically add functionality to an instance of an object at runtime.

If we want to read data from file with Java, our code will look something like the following:

1
2
3
4
5
6
InputStream in = new FileInputStream("demo.txt");
InputStream bin = new BufferedInputStream(in);
byte[] data = new byte[128];
while (bin.read(data) != -1) {
  //...
}

We almost always need to do the following:

Create a FileInputStream object first, and then pass it to BufferedInputStream object to use.

Why not just have a BufferedFileInputStream class that inherits from FileInputStream and supports caching? This way we can create a BufferedFileInputStream object like the code below and open the file to read the data, wouldn’t it be easier to use?

The truth is this approach is not scalable. Let’s say in addition to adding buffering function, we also want encryption and decryption support when transmitting data, as well as I/O from different data source like socket and pipe. 3 different final data sources with 2 functions will need 6 subclasses in total. If we continue to add functionalities & data source support, the subclasses will explode.

Instead of utilizing inheritance, utilizing composition will keep the class structure relatively simple. The following code shows how Java I/O accomplished this with decorator pattern:

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
public abstract class InputStream {
  //...
  public int read(byte b[]) throws IOException {
    return read(b, 0, b.length);
  }
  
  public int read(byte b[], int off, int len) throws IOException {
    //...
  }
  
  public long skip(long n) throws IOException {
    //...
  }
  
  public void close() throws IOException {}

  //...

}

public class BufferedInputStream extends InputStream {
  protected volatile InputStream in;

  protected BufferedInputStream(InputStream in) {
    this.in = in;
  }
  
  //...
}

public class DataInputStream extends InputStream {
  protected volatile InputStream in;

  protected DataInputStream(InputStream in) {
    this.in = in;
  }
  
  //...
}

The purpose of the decorator pattern is to add additional functionalities to the original layer in a layering manner so that we can get the ultimate function we want by combining them.

Interesting note: Python directly integrated decorator as a language feature.

Appendix

Following is a Golang example of the decorator pattern:

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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
// notifier interface
type notifier interface {
    notify() bool
}

// default_notifier.go
type defaultNotifier struct {
}

func (d *defaultNotifier) notify() bool {
    // phone bannr push
    return success
}

// sms_notifier.go
type smsNotifier struct {
    notifier notifier
}

func (s *smsNotifier) notify() bool {
    success := s.notifier.notify()
    // ...
    // sms push
    return success
}


// email_notifier.go
type emailNotifier struct {
    notifier notifier
}

func (e *emailNotifier) notify() bool {
    success := e.notifier.notify()
    // ...
    // email push
    return success
}


// main.go
func main() {

    notifier := &defaultNotifier{}

    // add sms functionality
    notifierSms := &smsNotifier{
        notifier: notifier,
    }

    // add email functionality
    notifierSmsEmail := &emailNotifier{
        notifier: notifierSms,
    }

    notifierSmsEmail.notify()
}

Following is a C++ example of the decorator pattern:

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
42
43
class Notifier {
 public:
  virtual ~Notifier() {}
  virtual bool Notify() const = 0;
};


class Decorator : public Notifier {
 protected:
  Notifier* notifier_;

 public:
  Decorator(Notifier* notifier) : notifier_(notifier) {
  }

  bool Notify() const override {
    return this->component_->Notify();
  }
};


class SmsNotifier : public Decorator {
 public:
  SmsNotifier(Notifier* notifier) : Decorator(notifier) {
  }
  bool Notify() const override {
    // send SMS msg
    Decorator::Notify();
    return 1; 
  }
};

class EmailNotifier : public Decorator {
 public:
  SmsNotifier(Notifier* notifier) : Decorator(notifier) {
  }
  bool Notify() const override {
    // send email msg
    Decorator::Notify();
    return 1; 
  }
};
This post is licensed under CC BY 4.0 by the author.