Decorator

Decorators attach new responsibilities to an object dynamically, thus providing a flexible alternative to subclassing for extending functionality. They wrap objects to add functionality.

TL;DR

Decorators allow you to extend functionality without modifying the original code; it is basically calling a function that wraps the original code with the goal of modifying that object’s behaviour (rather than constructing a new object, as would be the case with the Builder pattern).

A good way to think of this is that it works very much like Function Composition

Problem

You have an object defined by a class, say Text. At runtime, you need to be able to modify the text into italics or bold, or even italics + bold. A naive option would be to sublcass text into ItalicText, BoldText, ItalicBoldText.

This breaks down very quickly, however, since the object itself needs to be instantiated every time, and the complexity increases rapidly if we want to add more text formats (underline, strikethrough, etc.) and all their combinations.

Solution

A solution is to use the Decorator pattern to wrap the original Text object. This way, we do not have to modify or subclass the Text, and we can apply transformations at run-time as we choose:

# Base Component
class Text:
    def __init__(self, content):
        self.content = content
 
    def render(self):
        return self.content
 
# Decorator Base Class
class TextDecorator:
    def __init__(self, wrapped):
        self.wrapped = wrapped
 
    def render(self):
        return self.wrapped.render()
 
# Concrete Decorators
class BoldDecorator(TextDecorator):
    def render(self):
        return f"<b>{super().render()}</b>"
 
class ItalicDecorator(TextDecorator):
    def render(self):
        return f"<i>{super().render()}</i>"
 
# Usage
if __name__ == "__main__":
    simple_text = Text("Hello, world!")
    bold_text = BoldDecorator(simple_text)
    italic_bold_text = ItalicDecorator(bold_text)
 
    print("Original:", simple_text.render())
    print("Bold:", bold_text.render())
    print("Italic + Bold:", italic_bold_text.render())