Design Pattern: Decorator Pattern in Python

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.

Decorator Pattern General Diagram
Decorator Pattern General Diagram

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-

Subject Interface: interface for the subject, to which we want to add the new functionality.
Concrete Subject: classes for the subject. these classes implement the subject interface.
Base(abstract) Decorator: this is the interface/abstract class for the decorator.
Concrete Decorator: class with concrete 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()
Python

Output:

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]

Create class “DataExport”.
Inherit from “ABC” to make it an abstract class. This is used as an interface.
Declare abstract methods as per requirement.
from abc import ABC, abstractmethod


# Base Component
class DataExport(ABC):
    @abstractmethod
    def process_data(self) -> None:
        pass
Python

SimpleExport Class [Existing – concrete subject]

Create the class “SimpleDataExport“.
Inherith for “DataExport“.
Define methods that are declared in the interface/abstract class.
# Concrete Component
class SimpleDataExport(DataExport):
    def process_data(self) -> None:
        print("Processing Data")
Python

Base Decorator Class [Decorator Interface]

Define class “DataExportDecorator“.
Inherit from the same interface “DataExport“.
In “__init__” method accept an object of type “DataExport“, and save it in an attribute.
Define the methods declared in “DataExport“, and in the method definitions, call the method(s) from the data exporter object.
# 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()
Python

CSV Decorator Class [Concrete Decorator]

Define class “CsvDataExportDecorator“.
Inherit from “DataExportDecorator
The “__init__” method accepts a “DataExport” object and calls the “__init__” method from the parent.
Define required methods- here we have defined “process_data“.
In the method definition, we have called the same method from the parent and added some new functionality steps.
# 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")
Python

Excel Decorator Class [Concrete Decorator]

Define class “CsvDataExportDecorator“.
Inherit from “DataExportDecorator“.
In the “__init__” method accept a “DataExport” object, and call the “__init__” from the parent.
Define required methods- here we have defined “process_data“.
# 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")
Python

JSON Decorator Class [Concrete Decorator]

Define class “JsonDataExportDecorator“.
Inherit from “DataExportDecorator
In the “__init__” method accept a “DataExport” object, and call the “__init__” from the parent.
Define required methods- here we have defined “process_data“.
# 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")
Python

Demo

# 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()
Python

Output

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
Plaintext

Example #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]

Create abstract class “UIElement“.
Inherit from “ABC” to make it an abstract class.
Declare abstract method “draw“.
from abc import ABC, abstractmethod


# Base Component
class UIElement(ABC):
    @abstractmethod
    def draw(self) -> None:
        pass
Python

Button UI Element Class [Concrete Subject]

Define class “UIElement“.
Inherit from “UIElement” abstract class.
Define the methods that were declared abstract in the subject interface. In our case, we have the “draw” method.
# Concrete Components
class Button(UIElement):
    def draw(self) -> None:
        print("Drawing Button")
Python

Input Box UI Element Class [Concrete Subject]

Define the class “InputBox“.
Inherit from the “UIElement” abstract class, and define all the methods that are declared abstract in the subject interface.
class InputBox(UIElement):
    def draw(self) -> None:
        print("Drawing Input Box")
Python

Table UI Element Class

Define the class “Table“.
Inherit from the “UIElement” abstract class, and define all the methods that are declared abstract in the subject interface.
class Table(UIElement):
    def draw(self) -> None:
        print("Drawing Table")
Python

Abstract UI Decorator [Abstract Decorator]

Define class “UIDecorator“.
Inherit from “UIElement“.
In the “__init__” method accept an object of type “UIElement“, and set that in an attribute. We have save the “UIElement” object in the private attribute “_ui_element“.
Define the method “draw“, and in the implementation use/call the method “draw” from “_ui_elmement“.
# Base Decorator
class UIDecorator(UIElement):
    def __init__(self, ui_element: UIElement):
        self._ui_element = ui_element

    def draw(self) -> None:
        self._ui_element.draw()
Python

Border Decorator Class [Concrete Decorator]

Define class “BorderDecorator“.
Inherit from “b”.
In “draw” method definition we can call the method from parent and also add some new functionality.
# 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")
Python

Background Decorator Class [Decorator]

Define class “BackgroundDecorator“.
Inherit from “UIDecorator“.
In the “draw” method definition we can call the method from the parent and also add some new functionality.
class BackgroundDecorator(UIDecorator):
    def draw(self) -> None:
        super().draw()
        self.add_background()

    def add_background(self) -> None:
        print("Adding Background to the element")
Python

Margin Decorator Class [Decorator]

Define class “MarginDecorator“. Inherit from “UIDecorator“.
In the “draw” method definition we can call the method from the parent and also add some new functionality.
class MarginDecorator(UIDecorator):
    def draw(self) -> None:
        super().draw()
        self.add_margin()

    def add_margin(self) -> None:
        print("Adding margin to the element")
Python

Demo

# 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()
Python

Output

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
Plaintext

Source 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.

Leave a Comment


The reCAPTCHA verification period has expired. Please reload the page.