The Decorator pattern is a structural design pattern that allows you to dynamically add new behavior to objects. It does so by wrapping them in special objects called decorators, which contain the added behavior. This is an alternative to inheritance.
The Decorator pattern is useful when you want to add behavior to individual objects, rather than to an entire class of objects. It is also useful when you want to add behavior without affecting the existing hierarchy, or when you want to add behavior that can be changed dynamically at runtime.
In this article, we will discuss the benefits of using the Decorator pattern, and how it can be implemented in practice.
The Decorator pattern can be useful when you want to:
You can find the full example source code here.
In our example I'm going to create the interface DataSource
.
interface DataSource {
writeData(data: string): void;
readData(): string;
}
In our example I'm going to create the class FileDataSource
.
class FileDataSource implements DataSource {
private fileName: string;
private data: string;
constructor(fileName: string) {
this.fileName = fileName;
}
writeData(data: string): void {
console.log(
`[FileDataSource] Writing to file: ${this.fileName}, data: ${data}`
);
this.data = data;
}
readData(): string {
console.log(
`[FileDataSource] Reading from file: ${this.fileName}, data: ${this.data}`
);
return this.data;
}
}
class DataSourceDecorator implements DataSource {
protected source: DataSource;
constructor(source: DataSource) {
this.source = source;
}
writeData(data: string): void {
this.source.writeData(data);
}
readData(): string {
return this.source.readData();
}
}
In our example I want to add two new behaviors. The first one is to encrypt the data before writing it to the file and decrypting it after reading it from the file.
class EncryptionDecorator extends DataSourceDecorator {
writeData(data: string): void {
const base64Data = btoa(data);
console.log(`[EncryptionDecorator] Encrypted data: ${base64Data}`);
super.writeData(base64Data);
}
readData(): string {
const base64Data = super.readData();
const data = atob(base64Data);
console.log(`[EncryptionDecorator] Decrypted data: ${data}`);
return data;
}
}
The second one is to reverse the data before writing it to the file and unreversing it after reading it from the file.
class ReverseDecorator extends DataSourceDecorator {
writeData(data: string): void {
const compressedData = data.split("").reverse().join("");
console.log(`[ReverseDecorator] Reversed data: ${compressedData}`);
super.writeData(compressedData);
}
readData(): string {
const compressedData = super.readData();
const data = compressedData.split("").reverse().join("");
console.log(`[ReverseDecorator] Unreversed data: ${data}`);
return data;
}
}
An example of how client code would use the Decorator pattern with the EncryptionDecorator
:
const file = new FileDataSource("file.txt");
const encryptedFile = new EncryptionDecorator(file);
encryptedFile.writeData("Hello world!");
encryptedFile.readData();
/* Output:
[EncryptionDecorator] Encrypted data: SGVsbG8gd29ybGQh
[FileDataSource] Writing to file: file.txt, data: SGVsbG8gd29ybGQh
[FileDataSource] Reading from file: file.txt, data: SGVsbG8gd29ybGQh
[EncryptionDecorator] Decrypted data: Hello world!
*/
Another example but using the ReverseDecorator
:
const file = new FileDataSource("file.txt");
const compressedFile = new ReverseDecorator(file);
compressedFile.writeData("Hello world!");
compressedFile.readData();
/* Output:
[ReverseDecorator] Reversed data: !dlrow olleH
[FileDataSource] Writing to file: file.txt, data: !dlrow olleH
[FileDataSource] Reading from file: file.txt, data: !dlrow olleH
[ReverseDecorator] Unreversed data: Hello world!
*/
Both decorators can also be used together:
const file = new FileDataSource("file.txt");
const encryptedFile = new EncryptionDecorator(file);
const compressedEncryptedFile = new ReverseDecorator(encryptedFile);
compressedEncryptedFile.writeData("Hello world!");
compressedEncryptedFile.readData();
/* Output:
[ReverseDecorator] Reversed data: !dlrow olleH
[EncryptionDecorator] Encrypted data: IWRscm93IG9sbGVI
[FileDataSource] Writing to file: file.txt, data: IWRscm93IG9sbGVI
[FileDataSource] Reading from file: file.txt, data: IWRscm93IG9sbGVI
[EncryptionDecorator] Decrypted data: !dlrow olleH
[ReverseDecorator] Unreversed data: Hello world!
*/