Decorator pattern is used to add some additional responsibilities to an object dynamically. We can add some new tasks, checking, etc. before and/or after the original functionality of the the object.
Using the Decorator pattern we can extend the behavior, with property transparency, and without affecting other objects.
NOTES
In this article, we discuss the implementation of the Decorator Pattern in Python.
See the Decorator in other languages in the “Other Code Implementations” section. Or, use the link below to check the details of the Decorator Pattern-
Implementation
WARNING
We can achieve similar results in Python, using Python Decorator. For most of the use cases, Python Decorators are suitable, and we do not need to go through the full process described below.
If we want to implement a Decorator using OOP, then we can use the following implementation. We may need to implement a complex decorator in some use cases.
There are four(4) parts of a decorator implementation-
NOTES
Two(2) points to note here-
- Make sure to implement the subject interface for the decorator.
- It is not mandatory to make the base decorator an abstract class. If you do not need any features from an abstract class, you can ignore making it abstract.
Here is a very basic implementation of the Decorator pattern-
from abc import ABC, abstractmethod
# Subject Interface
class Subject(ABC):
@abstractmethod
def operationOne(self) -> None:
pass
@abstractmethod
def operationTwo(self) -> None:
pass
# Concrete Subject
class ConcreteSubject(Subject):
def operationOne(self) -> None:
print("Operation One(1) in concrete subject")
def operationTwo(self) -> None:
print("Operation Two(2) in concrete subject")
# Base Decorator
class Decorator(Subject, ABC):
def __init__(self, subject: Subject) -> None:
self.subject = subject
def operationOne(self) -> None:
# Some additional/new functionality can be be added here
self.subject.operationOne()
def operationTwo(self) -> None:
# Some additional/new functionality can be be added here
self.subject.operationTwo()
# some additional operation, if required in your user case
def myAdditionalOp(self) -> None:
self.subject.operationOne()
print("Performing some operation in my additional op in abstract decorator")
# some abstract method, if required in case of your implemenation
@abstractmethod
def myAbsOp(self) -> None:
pass
# Concrete Decorator
class ConcreteDecorator(Decorator):
# Perform some additional or different operaiton for operationOne
def operationOne(self) -> None:
super().operationOne()
print(
"Performing some additional operation in operation one of concrete decorator"
)
def myAbsOp(self) -> None:
print("my abstract operation implemented in the concreate decorator")
# Demo usage
def main():
# Instantiate a decorator
# and pass an instance of subject
my_decorator = ConcreteDecorator(ConcreteSubject())
my_decorator.operationOne()
my_decorator.operationTwo()
my_decorator.myAdditionalOp()
my_decorator.myAbsOp()
if __name__ == "__main__":
main()
PythonOutput:
Operation One(1) in concrete subject
Performing some additional operation in operation one of concrete decorator
Operation Two(2) in concrete subject
Operation One(1) in concrete subject
Performing some operation in my additional op in abstract decorator
my abstract operation implemented in the concreate decorator
Examples
Let’s take a look at a few use cases where we can use the decorator pattern.
Example #1: Data Export
In the following implementation we have the following elements-
DataExport Interface [Existing – subject interface]
from abc import ABC, abstractmethod
# Base Component
class DataExport(ABC):
@abstractmethod
def process_data(self) -> None:
pass
PythonSimpleExport Class [Existing – concrete subject]
# Concrete Component
class SimpleDataExport(DataExport):
def process_data(self) -> None:
print("Processing Data")
PythonBase Decorator Class [Decorator Interface]
# Base Decorator
class DataExportDecorator(DataExport):
def __init__(self, data_exporter: DataExport):
self._data_exporter = data_exporter
def process_data(self) -> None:
self._data_exporter.process_data()
PythonCSV Decorator Class [Concrete Decorator]
# Concrete Decorator for CSV
class CsvDataExportDecorator(DataExportDecorator):
def __init__(self, data_exporter: DataExport):
super().__init__(data_exporter)
def process_data(self) -> None:
super().process_data()
self.process_csv()
def process_csv(self) -> None:
print("Processed data to CSV")
PythonExcel Decorator Class [Concrete Decorator]
# Concrete Decorator for Excel
class ExcelDataExportDecorator(DataExportDecorator):
def process_data(self) -> None:
super().process_data()
self.process_excel()
def process_excel(self) -> None:
print("Processed data to Excel")
PythonJSON Decorator Class [Concrete Decorator]
# Concrete Decorator for JSON
class JsonDataExportDecorator(DataExportDecorator):
def __init__(self, data_exporter: DataExport):
super().__init__(data_exporter)
def process_data(self) -> None:
super().process_data()
self.process_json()
def process_json(self) -> None:
print("Processed data to JSON")
PythonDemo
# Demo
def main():
# CSV Export
csv_data_export = CsvDataExportDecorator(SimpleDataExport())
csv_data_export.process_data()
# Excel Export
excel_data_export = ExcelDataExportDecorator(SimpleDataExport())
excel_data_export.process_data()
# JSON Export
json_data_export = JsonDataExportDecorator(SimpleDataExport())
json_data_export.process_data()
if __name__ == "__main__":
main()
PythonOutput
The output of the demo above will be as shown below.
Processing Data
Processed data to CSV
Processing Data
Processed data to Excel
Processing Data
Processed data to JSON
PlaintextExample #2: UI Elements
Let’s see how we can use the Decorator pattern to add new functionality for UI elements.
UI Element Interface [Subject Interface]
from abc import ABC, abstractmethod
# Base Component
class UIElement(ABC):
@abstractmethod
def draw(self) -> None:
pass
PythonButton UI Element Class [Concrete Subject]
# Concrete Components
class Button(UIElement):
def draw(self) -> None:
print("Drawing Button")
PythonInput Box UI Element Class [Concrete Subject]
class InputBox(UIElement):
def draw(self) -> None:
print("Drawing Input Box")
PythonTable UI Element Class
class Table(UIElement):
def draw(self) -> None:
print("Drawing Table")
PythonAbstract UI Decorator [Abstract Decorator]
# Base Decorator
class UIDecorator(UIElement):
def __init__(self, ui_element: UIElement):
self._ui_element = ui_element
def draw(self) -> None:
self._ui_element.draw()
PythonBorder Decorator Class [Concrete Decorator]
# Concrete Decorators
class BorderDecorator(UIDecorator):
def draw(self) -> None:
super().draw()
self.add_border()
def add_border(self) -> None:
print("Adding Border to the element")
PythonBackground Decorator Class [Decorator]
class BackgroundDecorator(UIDecorator):
def draw(self) -> None:
super().draw()
self.add_background()
def add_background(self) -> None:
print("Adding Background to the element")
PythonMargin Decorator Class [Decorator]
class MarginDecorator(UIDecorator):
def draw(self) -> None:
super().draw()
self.add_margin()
def add_margin(self) -> None:
print("Adding margin to the element")
PythonDemo
# Demo
def main():
# Table with Border
table_with_border = BorderDecorator(Table())
table_with_border.draw()
# Input Box with Border and Background
input_with_border_and_background = BackgroundDecorator(BorderDecorator(InputBox()))
input_with_border_and_background.draw()
# Button with all Decorators
button_with_all_decorators = MarginDecorator(
BackgroundDecorator(BorderDecorator(Button()))
)
button_with_all_decorators.draw()
if __name__ == "__main__":
main()
PythonOutput
Drawing Table
Adding Border to the element
Drawing Input Box
Adding Border to the element
Adding Background to the element
Drawing Button
Adding Border to the element
Adding Background to the element
Adding margin to the element
PlaintextSource Code
Use the following link to get the source code:
Other Code Implementations
Use the following links to check the implementation of the Decorator pattern in other programming languages.