Skip to content

Structural Patterns: Adapter, Decorator, Facade

Structural patterns describe how objects and classes are composed to form larger structures.

Adapter

Converts the interface of one class to the interface expected by the client. Allows incompatible interfaces to work together.

from typing import Protocol

class ModernPaymentGateway(Protocol):
    def process(self, amount: float, currency: str) -> bool: ...

class LegacyPaymentAPI:
    def make_payment(self, amount_cents: int) -> str:
        return "OK" if amount_cents > 0 else "FAIL"

class LegacyPaymentAdapter:
    def __init__(self, legacy: LegacyPaymentAPI) -> None:
        self._legacy = legacy

    def process(self, amount: float, currency: str) -> bool:
        cents = int(amount * 100)
        return self._legacy.make_payment(cents) == "OK"

adapter = LegacyPaymentAdapter(LegacyPaymentAPI())
result = adapter.process(29.99, "USD")

Use when: integrating a third-party library or legacy system whose interface does not match your domain interface.

Decorator

Add behavior to an object dynamically without changing its class. Wraps the original object.

from typing import Protocol

class DataStore(Protocol):
    def read(self, key: str) -> str: ...
    def write(self, key: str, value: str) -> None: ...

class InMemoryStore:
    def __init__(self) -> None:
        self._data: dict[str, str] = {}

    def read(self, key: str) -> str:
        return self._data.get(key, "")

    def write(self, key: str, value: str) -> None:
        self._data[key] = value

class LoggingDecorator:
    def __init__(self, store: DataStore) -> None:
        self._store = store

    def read(self, key: str) -> str:
        value = self._store.read(key)
        print(f"READ {key}={value!r}")
        return value

    def write(self, key: str, value: str) -> None:
        print(f"WRITE {key}={value!r}")
        self._store.write(key, value)

store: DataStore = LoggingDecorator(InMemoryStore())
store.write("user:1", "Alice")
_ = store.read("user:1")

Use when: you need to add cross-cutting behavior (logging, caching, validation, auth) without modifying the core class. Stack multiple decorators.

Facade

Provide a simple interface over a complex subsystem. Hide implementation details.

class AuthService:
    def validate_token(self, token: str) -> bool: ...

class OrderService:
    def get_orders(self, user_id: str) -> list[dict]: ...

class ShippingService:
    def get_tracking(self, order_id: str) -> dict: ...

class DashboardFacade:
    def __init__(
        self,
        auth: AuthService,
        orders: OrderService,
        shipping: ShippingService,
    ) -> None:
        self._auth = auth
        self._orders = orders
        self._shipping = shipping

    def get_user_dashboard(self, token: str, user_id: str) -> dict:
        if not self._auth.validate_token(token):
            raise PermissionError("Invalid token")
        orders = self._orders.get_orders(user_id)
        tracking = [self._shipping.get_tracking(o["id"]) for o in orders]
        return {"orders": orders, "tracking": tracking}

Use when: a subsystem is complex and clients need a simple unified entry point. Client does not need to know the internals.