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