Skip to Content
Course content

372: The Adapter Pattern

Click on the "Edit" button in the top corner of the screen to edit your slide content.

You're going to run into this eventually: you've built a clean system with a specific way of doing things, and then a manager tells you that you must integrate a third-party library that was clearly written by someone who hates everyone. The method names are weird, the data formats are non-standard, and it doesn't fit your existing architecture at all.

This is where the Adapter Pattern comes in. Instead of rewriting your entire codebase to accommodate a clunky library, or hacking the library's source code (which you can't do if it's a compiled package anyway), you build a "wrapper." This wrapper translates the library's interface into the one your application expects.

The interface we actually want

Let's say we're building a dashboard that displays weather. I want my app to be agnostic about where the weather comes from. Whether it's from a premium API or a free one, I want a consistent method to get the temperature in Celsius.

from abc import ABC, abstractmethod

class WeatherProvider(ABC):
    @abstractmethod
    def get_temperature_celsius(self) -> float:
        pass

class WeatherDashboard:
    def __init__(self, provider: WeatherProvider):
        self.provider = provider

    def display(self):
        temp = self.provider.get_temperature_celsius()
        print(f"The current temperature is {temp}°C")

Dealing with a stubborn external library

Now, here is the problem. I've found this great library called LegacyWeatherLib. It's reliable and fast, but it's old. It provides temperatures in Fahrenheit, and the method name is fetch_temp_f(). It doesn't know our WeatherProvider interface exists.

# Imagine this is inside a library we can't change
class LegacyWeatherLib:
    def fetch_temp_f(self) -> float:
        # Simulating an API call
        return 77.0 

The "quick fix" that creates a mess

When I first encountered this, my instinct was to just "fix" it in the dashboard. I thought, "It's just one line of math, why overcomplicate it?"

# WRONG WAY: Hardcoding the translation in the business logic
class WeatherDashboard:
    def __init__(self, provider):
        self.provider = provider

    def display(self):
        # I'm checking the type here... yuck.
        if isinstance(self.provider, LegacyWeatherLib):
            temp_f = self.provider.fetch_temp_f()
            temp = (temp_f - 32) * 5/9
        else:
            temp = self.provider.get_temperature_celsius()
        print(f"The current temperature is {temp}°C")

I immediately hated this. The moment I add a second legacy provider, my display method becomes a giant pile of if/elif statements. I've leaked the details of a third-party library directly into my core business logic. That's a one-way ticket to technical debt.

Wrapping it in a proper Adapter

The right move is to create an Adapter class. This class inherits from our WeatherProvider (so the dashboard stays happy) but internally holds an instance of the LegacyWeatherLib. It does the translation work behind the scenes.

class LegacyWeatherAdapter(WeatherProvider):
    def __init__(self, legacy_system: LegacyWeatherLib):
        self.legacy_system = legacy_system

    def get_temperature_celsius(self) -> float:
        # The adapter handles the translation logic
        temp_f = self.legacy_system.fetch_temp_f()
        return (temp_f - 32) * 5/9

# Now look how clean the execution is:
legacy_api = LegacyWeatherLib()
adapter = LegacyWeatherAdapter(legacy_api)
dashboard = WeatherDashboard(adapter)

dashboard.display() # Outputs: The current temperature is 25.0°C

The WeatherDashboard has no idea that LegacyWeatherLib even exists. It just knows it has a WeatherProvider. If we decide to swap to a newer API next month, we just write a new adapter and the dashboard code doesn't change by a single character. That's the power of this pattern.




📋 Practical Task

Exercise: Adapting a JSON Shipping API to a Logistics Interface

You are building a shipping management system. Your system expects all shipping providers to have a method calculate_cost(weight_kg) that returns a float.

You need to integrate a third-party provider called QuickShipAPI. However, QuickShipAPI has a method called get_quote(weight_lbs) which takes weight in pounds and returns a dictionary like {"price": 15.0, "currency": "USD"}.

Your Task:

  1. Create a ShippingProvider abstract base class with the calculate_cost method.
  2. Implement a QuickShipAdapter that wraps the QuickShipAPI.
  3. Inside the adapter, convert the kilograms to pounds (1 kg ≈ 2.20462 lbs) before calling the API, and extract only the price value from the returned dictionary.
  4. Demonstrate the adapter working by passing it into a function that expects a ShippingProvider.
Rating
0 0

There are no comments for now.

to be the first to leave a comment.