LESSON 59 · ADVANCED4 min read

Structural Patterns

Adapt incompatible interfaces, add behavior with decorators, and control access with a proxy without hiding the real object.

Python 3.12+Free to readModule 13 of 19
YOUR NEXT SMALL WINS

By the end, you’ll be able to…

  • Wrap a third-party interface with an adapter.
  • Use decorators to add behavior around a call.
  • Implement a proxy for lazy loading or access control.
  • Choose the smallest structure that solves the problem.

01An adapter translates an interface

An adapter wraps an existing object and exposes the interface your code expects. It is useful at a third-party boundary, where the vendor's method names and error types should not leak through the application. The adapter translates both data and failures into your vocabulary.

Keep the adapter thin: validate assumptions, call the dependency, and translate the result. Do not put business rules in the adapter, or every backend will need a copy. If the third-party API changes, the adapter bounds the blast radius to one file.

temperature_adapter.py🐍Python
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.

02Decorators add behavior around a call

A Python decorator is a function that receives a function and returns a replacement. Use functools.wraps to preserve the original name and docstring. Decorators are a natural fit for logging, timing, caching, and authorization checks that wrap a call without changing its core logic.

A decorator should be transparent about what it changes. If it can raise a new exception, document that. If it caches, document the key and invalidation policy. Avoid stacking many decorators that each mutate arguments; the call becomes difficult to reason about and debug.

timed.py🐍Python
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.

03A proxy controls access

A proxy implements the same interface as the real object and decides when to delegate. A lazy proxy creates the expensive object on first use. A protective proxy checks permission before forwarding. A caching proxy can return a stored result without contacting the origin.

The proxy must preserve the real object's contract. If the real repository raises NotFoundError, the proxy should not convert it to None. Keep the proxy's additional behavior visible in its name and documentation; a proxy that silently changes semantics is a trap.

lazy_proxy.py🐍Python
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.

04Choose the smallest structure

Patterns are not a checklist. A one-method adapter that only renames a method may be unnecessary; a direct call with a comment can be clearer. A decorator that hides a network request inside an attribute access is surprising. The structure should reduce coupling or make a boundary testable, not merely look architectural.

Write the caller first. If the caller can depend on a simple protocol and the existing object already satisfies it, no adapter is needed. If a decorator would need to know about five unrelated concerns, split the concerns instead. The best pattern is the one another developer can explain without opening the catalog.

protocol_boundary.py🐍Python
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.
LET IT CLICK

The ideas worth keeping.

  • Adapters translate a dependency into your application's vocabulary.
  • Decorators wrap behavior and should preserve metadata.
  • Proxies control access while preserving the underlying contract.
TURN UNDERSTANDING INTO SOMETHING YOU CAN DO

Make it yours.

Type and test your code right here in the interactive workspace. Run your solution, check the output, and compare with the worked guide when ready.

01

Adapt a temperature sensor

Wrap a sensor that returns Celsius so callers see a temperature() method in Fahrenheit. Verify a reading of 25 becomes 77.0.

your_turn_1.py🐍Python
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.
Explore a worked solution
one_possible_solution.pyPython
class CelsiusSensor:    def reading(self):        return 25class FahrenheitAdapter:    def __init__(self, sensor):        self.sensor = sensor    def temperature(self):        return self.sensor.reading() * 9 / 5 + 32assert FahrenheitAdapter(CelsiusSensor()).temperature() == 77.0print("adapter verified")

The adapter owns the unit conversion and exposes the interface the application expects. The sensor remains unchanged and can be replaced by any object with a reading() method.

02

Add a caching proxy

Create a proxy around a slow report that renders only once and returns the cached string on subsequent calls.

your_turn_2.py🐍Python
Ctrl + ↵
Output
Click Run or press Ctrl+Enter
Output will appear here after you run the code.
Explore a worked solution
one_possible_solution.pyPython
class Report:    def render(self):        return "report"class CachedReport:    def __init__(self, report):        self.report = report        self._value = None    def render(self):        if self._value is None:            self._value = self.report.render()        return self._valuereport = CachedReport(Report())assert report.render() == report.render() == "report"print("cache verified")

The proxy preserves the render() contract while adding a cache. The first call populates the value; later calls return it without touching the wrapped object.

A SMALL QUESTION. A USEFUL NEXT STEP.

What stayed with you?

Check the ideas while they’re fresh. Your answers stay in this page and feedback appears as you go.

A LITTLE ACTIVE RECALL0 / 3 answered
01What problem does an adapter solve?
02Why use functools.wraps in a decorator?
03When is a proxy appropriate?
⚡CODING CHALLENGE
🔒Required to Mark Lesson Complete

Adapt a temperature sensor

Wrap a sensor that returns Celsius so callers see a temperature() method in Fahrenheit. Verify a reading of 25 becomes 77.0.

EXPECTED OUTPUT
adapter verified
main.pyPython 3
Ctrl + ↵
Execution Console
Click "Run & Verify" (or press Ctrl + Enter) to test your code against the challenge.

Keep exploring.

The official references behind this lesson, when you want a second angle or the full details.

One lesson closer.

Reading is a start. Trying it yourself makes the connection.

Solve the hands-on coding challenge in the knowledge section to verify your code and unlock lesson completion.