Early previewThis guide is in development. Items tagged verify are still being checked against official docs.
nebx Learn
GitHub
Menu
Py

Python

Readable, batteries-included, and the default language of AI.

Paradigm
Multi-paradigm, OOP, functional
Typing
Dynamic, strong, optional type hints
Memory
Reference counting + cycle GC
Runs
CPython bytecode interpreter
Concurrency
asyncio, threads (GIL), multiprocessing
Structural pattern

Decorator

Intent

Attaches new behaviours to objects by placing them inside wrapper objects that share the same interface.

Problem

Users want notifications by email, SMS and Slack in any combination. Subclassing every combination explodes.

Solution

Wrap the base notifier in decorators. Each adds its behaviour and delegates to the wrapped object.

Real-world analogy

Think of it like this

Dressing in layers: a sweater when it's cold, a jacket over it, a raincoat on top when it rains. Each layer adds something and can come off.

Structure

association / extends implements / depends on
Client 5 «interface» Component + execute() 1 ConcreteComponent + execute() 2 BaseDecorator - wrappee: Component+ execute() 3 ConcreteDecorator + execute()+ extra() 4

Hover or tap a numbered role to highlight it in the diagram.

  1. 1Component — Component declares the interface shared by wrappers and wrapped objects.
  2. 2ConcreteComponent — Concrete Component is the object being wrapped; it defines the basic behaviour.
  3. 3BaseDecorator — Base Decorator holds a reference to a wrapped Component and delegates all work to it.
  4. 4ConcreteDecorator — Concrete Decorators add behaviour before or after calling the parent method.
  5. 5Client — Client wraps components in as many layers as it needs.

Try it

InteractiveWrap the notifier in layers, then send. Calls travel in, output comes back out.
Wrap with (in click order)

    
// output appears here

Code example

decorator.py
import functools
import time

# Python's @decorator syntax wraps functions; it is a close cousin of the GoF Decorator.
def timed(fn):
    @functools.wraps(fn)
    def wrapper(*args, **kwargs):
        start = time.perf_counter()
        try:
            return fn(*args, **kwargs)
        finally:
            print(f"{fn.__name__} took {time.perf_counter() - start:.3f}s")
    return wrapper

@timed
def build_report() -> None:
    ...

Applicability

Use Decorator when adding behaviour at runtime without changing the object's code.

Use Decorator when inheritance is awkward or impossible (final classes).

How to implement

  1. Make sure the domain can be a primary component with optional layers
  2. Define a component interface
  3. Create a concrete component
  4. Create a base decorator holding a wrapped component and delegating to it
  5. Create concrete decorators that add behaviour before or after delegating
  6. Let the client compose decorators

Pros and cons

Pros

  • Extend behaviour without subclassing
  • Combine behaviours by stacking wrappers
  • Single Responsibility per wrapper

Cons

  • Hard to remove a specific wrapper from the stack
  • Order of wrappers matters
  • Many small objects can be hard to debug

Check yourself

Check yourself

Add compression and then encryption around a file stream, in any combination, at runtime.

Saved in this browser only. Part of Structural patterns (0/7), Hands-on: patterns with live demos (0/5).