Adapter Pattern — Core Concepts

The problem adapters solve

In any non-trivial Python project, you’ll integrate code you didn’t write — third-party libraries, legacy modules, external APIs. These components rarely match your application’s expected interface perfectly. The Adapter Pattern bridges that gap by wrapping the foreign interface with one your code expects.

Without adapters, you face two bad options: modify the third-party code (fragile, upgrade-hostile) or scatter conversion logic throughout your codebase (messy, duplicated).

Two flavors of adapter

Object adapter (composition)

The adapter holds a reference to the adaptee and delegates calls:

  • Your code calls adapter.save(data)
  • The adapter internally calls legacy_system.write_record(transform(data))
  • Your code never knows the legacy system exists

This is the most common approach in Python because it’s flexible and doesn’t require inheritance.

Class adapter (inheritance)

The adapter inherits from both the target interface and the adaptee, overriding methods as needed. This is less common in Python because multiple inheritance adds complexity, but it works when you need to override specific behavior while keeping the rest.

When adapters shine

  • Swapping vendors: Your app uses Stripe for payments. You want to add PayPal as an option. Both have different APIs. Adapters give both the same interface, and your business logic doesn’t care which one is behind it.
  • Legacy integration: An old module uses XML. Your new code uses JSON. An adapter converts between them at the boundary.
  • Testing: You can adapt a real service into a fake one for testing by wrapping the same interface around mock data.

Adapter vs wrapper vs decorator

These terms overlap but have distinct intentions:

PatternIntent
AdapterChange the interface without changing behavior
DecoratorKeep the interface, add behavior
WrapperGeneric term for anything that wraps something else

An adapter translates. A decorator enhances. Both wrap, but for different reasons.

Signals you need an adapter

  • You’re writing conversion code in multiple places for the same external dependency
  • A library update changed its API and you need to shield your code from the change
  • You want to swap implementations (database, API provider, file format) without touching business logic
  • Tests are hard because you can’t isolate external dependencies

Common misconception

“Adapters add unnecessary abstraction.” In small scripts, yes — just call the library directly. But in applications that evolve over time, adapters save significant effort when dependencies change. The cost of writing a small adapter is almost always less than the cost of a scattered rewrite later.

The one thing to remember: An adapter is a thin translation layer that lets your code talk to any external component through a consistent interface — keeping your business logic clean and your dependencies swappable.

pythondesign-patternsoop

See Also

  • Python Bridge Pattern Why separating what something does from how it does it keeps your Python code from becoming a tangled mess.
  • Python Builder Pattern Why building complex Python objects step by step beats cramming everything into one giant constructor.
  • Python Composite Pattern How the Composite Pattern lets you treat a group of things the same way you'd treat a single thing in Python.
  • Python Facade Pattern How the Facade Pattern gives you one simple button instead of a confusing control panel in Python.
  • Python Flyweight Pattern How the Flyweight Pattern saves memory by sharing common data instead of copying it thousands of times.