Decorator

the decorator pattern is a design pattern that allows behavior to be added to an individual object, dynamically, without affecting the behavior of other objects from the same class.

In plain words

Decorator pattern lets you dynamically change the behavior of an object at run time by wrapping them in an object of a decorator class.

Wikipedia says

In object-oriented programming, the decorator pattern is a design pattern that allows behavior to be added to an individual object, either statically or dynamically, without affecting the behavior of other objects from the same class. The decorator pattern is often useful for adhering to the Single Responsibility Principle, as it allows functionality to be divided between classes with unique areas of concern.

Example

This example shows how you can adjust the behavior of an object without changing its code.

Initially, the business logic class could only read and write data in plain text. Then we created several small wrapper classes that add new behavior after executing standard operations in a wrapped object.

The first wrapper encrypts and decrypts data, and the second one compresses and extracts data.

1
2
3
4
5
public interface DataSource {
void writeData(String data);

String readData();
}

Simple data reader-writer

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
public class FileDataSource implements DataSource {
private String name;

public FileDataSource(String name) {
this.name = name;
}

@Override
public void writeData(String data) {
File file = new File(name);
try (OutputStream fos = new FileOutputStream(file)) {
fos.write(data.getBytes(), 0, data.length());
} catch (IOException ex) {
System.out.println(ex.getMessage());
}
}

@Override
public String readData() {
char[] buffer = null;
File file = new File(name);
try (FileReader reader = new FileReader(file)) {
buffer = new char[(int) file.length()];
reader.read(buffer);
} catch (IOException ex) {
System.out.println(ex.getMessage());
}
return new String(buffer);
}
}

Abstract base decorator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class DataSourceDecorator implements DataSource {
private DataSource wrappee;

DataSourceDecorator(DataSource source) {
this.wrappee = source;
}

@Override
public void writeData(String data) {
wrappee.writeData(data);
}

@Override
public String readData() {
return wrappee.readData();
}
}

Encryption decorator

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class EncryptionDecorator extends DataSourceDecorator {

public EncryptionDecorator(DataSource source) {
super(source);
}

@Override
public void writeData(String data) {
super.writeData(encode(data));
}

@Override
public String readData() {
return decode(super.readData());
}

private String encode(String data) {
//...
}

private String decode(String data) {
//...
}
}

Compression decorator

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
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Base64;
import java.util.zip.Deflater;
import java.util.zip.DeflaterOutputStream;
import java.util.zip.InflaterInputStream;

public class CompressionDecorator extends DataSourceDecorator {
private int compLevel = 6;

public CompressionDecorator(DataSource source) {
super(source);
}

public int getCompressionLevel() {
return compLevel;
}

public void setCompressionLevel(int value) {
compLevel = value;
}

@Override
public void writeData(String data) {
super.writeData(compress(data));
}

@Override
public String readData() {
return decompress(super.readData());
}

private String compress(String stringData) {
//...
}

private String decompress(String stringData) {
//...
}
}

Client code

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
import refactoring_guru.decorator.example.decorators.*;

public class Demo {
public static void main(String[] args) {
String salaryRecords = "Name,Salary\nJohn Smith,100000\nSteven Jobs,912000";
DataSourceDecorator encoded = new CompressionDecorator(
new EncryptionDecorator(
new FileDataSource("out/OutputDemo.txt")));
encoded.writeData(salaryRecords);
DataSource plain = new FileDataSource("out/OutputDemo.txt");

System.out.println("- Input ----------------");
System.out.println(salaryRecords);
System.out.println("- Encoded --------------");
System.out.println(plain.readData());
System.out.println("- Decoded --------------");
System.out.println(encoded.readData());
}
}

- Input ----------------
Name,Salary
John Smith,100000
Steven Jobs,912000
- Encoded --------------
Zkt7e1Q5eU8yUm1Qe0ZsdHJ2VXp6dDBKVnhrUHtUe0sxRUYxQkJIdjVLTVZ0dVI5
- Decoded --------------
Name,Salary
John Smith,100000
Steven Jobs,912000

Difference between Adapter and Decorator patterns

  • Decorator, attach additional responsibilities to an object dynamically (i.e. at run-time, add new behavior without changing source code). For example adding sugar in a coffee.

  • Adapter, adapts interface of an existing class to another interface. For example eletrical adapter to conect three-legged plug with a two-pronged outlet.