Summary
Pattern Name | Decorator Pattern |
Pattern Type | Structural Pattern |
Scope | Object |
Tagline | Additional responsibility to individual objects without affecting other objects |
Use cases | When we want to attach additional responsibility/functionality to individual objects and not to the class |
Related Patterns | Adapter Proxy Strategy |
Difficulty Level | Easy |
Implementations |
Definition
In Decorator design pattern, our target is to add some new functionality without affecting the existing object.
Decorators can be considered as an alternative to sub-classing for introducing additional functionality.
Say, there is an existing class that is responsible for printing a UI element (like a Button or a Table). Later we need the ability to add a border or text color to that element.
We can add new properties and functions to that existing class and introduce those properties. But that will violate the Open-Close principle of the SOLID principle.
So we can use the Decorator pattern and introduce those new properties and functions. The decorator pattern will add one or more new Classes which will contain the existing functionality of the existing object and will also contain new functionalities. That way these new properties will not be added to every object. Only the new decorator class(es) objects will have these new functionalities.
Decorators add new functionality/responsibility to individual objects at runtime, not to the entire class.
You can think of the decorator as a new skin/layer over the existing object. The actual object exists as it is, by using Decorator pattern we are just adding classes that will wrap the existing class and add new functionalities on top of that.
Use Cases
Here are the use cases of Decorator pattern:
- When we want to add new responsibility to an individual object dynamically (not to the entire class).
- When we want to add new functionality to an object without affecting any other object of that class.
- When creating a subclass is not possible or has side effects, and we want to add new functionality to objects.
Implementation
Decorator pattern implementation has 4 elements:
- Subject Interface [Actual Item Interface]: interface for the actual time classes. This will ensure the type of the actual class inside the decorator.
- Subject Concrete Class [Actual Item class]: item/subject class, to which we want to add the new responsibility.
- Decorator Interface: interface (abstract class) that holds the reference to the actual item object. This also ensures a common interface for all the decorators.
- Concrete Decorator Class: classes that introduce new responsibilities to the actual item objects.
A simple implementation of Decorator pattern will look like the diagram below.
Here are the steps you need to follow while implementing the Decorator design pattern.
- Create an Abstract class that implements the same interface as the actual object.
- This main Abstract class should set the actual object in the constructor.
- Introduce the decorator classes which extend the Main decorator abstract class.
- In the new decorator class call the constructor abstract class constructor so that the main class object can be set here.
- In the new decorator class call the functions from the actual class, and include additional functionality with that existing functionality.
Examples
The following examples demonstrate how can we implement Decorator pattern in different use cases.
Example #1: Simple Decorator
Let’s take an example that demonstrates a general decorator.
Subject [Interface and Class]
We need an interface for the subject and one or more concrete subject classes.
// Subject Interface
interface Subject
operationOne()
operationTwo()
end interface
// Concrete Subject
class ConcreteSubject implements Subject
method operationOne()
output "Performing Operation One(1) in Subject"
end method
method operationTwo()
output "Performing Operation Two(2) in Subject"
end method
end class
Decorator [Abstract and Concrete]
Then we can create the decorator, an abstract class, which implements the same Subject interface.
This decorator stores a subject object in a variable, which we can set using the constructor. For each method, we are calling the method from the subject.
In the decorator concrete class, we extend the Decorator abstract class, and in the methods, we are calling the method from the parent and also performing addition operations here.
// Decorator
abstract class Decorator implements Subject
var subject: Subject
constructor(subjectParam: Subject)
subject = subjectParam
end construcor
method operationOne()
this.subject.operationOne()
end method
method operationTwo()
this.subject.operationTwo()
end method
end class
// Concrete Decorator
class ConcreteDecorator extends Decorator
constructor(subjectParam: Subject)
super(subjectParam)
end constructor
method operationOne() {
// perform some additional operation if required
subject.operationOne()
// perform some additional operation if required
output "Performing additional operation in Concrete Decorator"
end method
end class
Demo
In the client, we can call the Concrete Decorator and pass the subject concrete class to it. Then we can call the operations from the decorator object.
var someDecorator = new ConcreteDecorator(new ConcreteSubject())
someDecorator.operationOne();
Output
Performing Operation One(1) in Subject
Performing additional operation in Concrete Decorator
Example #2: Data Export
We are considering a data export operation for this example of Decorator pattern.
We have an existing class that can handle simple data export (fetching data and exporting in simple text format). Later we need to implement export as CSV, Excel, and JSON.
We can implement Decorator pattern to implement these new functionalities. Check the following implementation.
DataExport Interface
interface DataExport
processData()
end interface
SimpleExport Class
class SimpleDataExport implements DataExport
method processData()
// some operation to fetch data and handling
output "Processing Data"
end method
end class
Main Decorator Class
abstract class DataExportDecorator implements DataExport
var dataExporter: DataExport
constructor(dataExporterParam: DataExport)
dataExporter = dataExporterParam
end constructor
method processData()
dataExporter.processData()
end method
end class
CSV Decorator Class
class CsvDataExportDecorator extends DataExportDecorator
constructor(dataExporterParam: DataExport)
// call parent constructor
super(dataExporterParam)
end constructor
method processData()
dataExporter.processData()
processCsv()
end method
method processCsv()
// process data to CSV
output "Processed data to CSV"
end method
end class
Excel Decorator Class
class ExcelDataExportDecorator extends DataExportDecorator
constructor(dataExporterParam: DataExport)
// call parent constructor
super(dataExporterParam)
end constructor
method processData()
dataExporter.processData()
processExcel()
end method
method processExcel()
// process data to Excel
output "Processed data to Excel"
end method
end class
JSON Decorator Class
class JsonDataExportDecorator extends DataExportDecorator
constructor(dataExporterParam: DataExport)
// call parent constructor
super(dataExporterParam)
end constructor
method processData()
dataExporter.processData()
processJson()
end method
method processJson()
// process data to JSON
output "Processed data to JSON"
end method
end class
Usage
csvDataExport = new CsvDataExportDecorator(new SimpleDataExport())
csvDataExport.processData()
excelDataExport = new ExcelDataExportDecorator(new SimpleDataExport())
excelDataExport.processData()
jsonDataExport = new JsonDataExportDecorator(new SimpleDataExport())
jsonDataExport.processData()
Output
The output of the demo above will be like below.
Processing Data
Processed data to CSV
Processing Data
Processed data to Excel
Processing Data
Processed data to JSON
Code Implementations
Use the following links to check Decorator pattern implementation in specific programming languages.