Python
Completed
-
Section 1: Getting Started
-
Section 2: Core Syntax and Data Types
-
Section 3: Collections
-
39: Set Operations: Union, Intersection, Difference
-
Section 4: Control Flow
-
Section 5: Functions
-
Section 6: Turtle Graphics and Early Practice Projects
-
Section 7: Working with Files and I/O
-
Section 8: Regular Expressions
-
Section 9: Object-Oriented Python
-
Section 10: Error Handling
-
Section 11: Modules and Packages
-
Section 12: Iterators, Generators, and Functional Tools
-
Section 13: Decorators and Metaprogramming
-
Section 14: Concurrency and Parallelism
-
Section 15: Working with Dates, Times, and Numbers
-
Section 16: Standard Library Deep Dive I: Data Structures
-
Section 17: Standard Library Deep Dive II: System and Introspection
-
Section 18: Standard Library Deep Dive III: Security and Encoding
-
Section 19: Standard Library Deep Dive IV: Text and Data Utilities
-
Section 20: Networking and Web Basics
-
Section 21: Working with Databases
-
Section 22: Testing and Quality
-
Section 23: Advanced Typing
-
Section 24: Context Managers and Resource Handling
-
Section 25: Text, Unicode, and Binary Data
-
Section 26: More Functional and Iteration Tools
-
Section 27: Data Validation and Configuration
-
Section 28: Working with Images and Media
-
Section 29: Property-Based and Documentation Testing
-
Section 30: Packaging and Deployment
-
Section 31: Performance and Internals
-
Section 32: Design Patterns in Python
-
Section 33: GUI Programming
-
Section 34: Security Basics
-
Section 35: Data Structures and Algorithms
-
Section 36: Practical Projects
-
Section 37: Capstone Projects
-
Section 38: Interview and Algorithm Practice
-
Section 39: Writing Idiomatic Python
372: The Adapter Pattern
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:
- Create a
ShippingProviderabstract base class with thecalculate_costmethod. - Implement a
QuickShipAdapterthat wraps theQuickShipAPI. - Inside the adapter, convert the kilograms to pounds (1 kg ≈ 2.20462 lbs) before calling the API, and extract only the
pricevalue from the returned dictionary. - Demonstrate the adapter working by passing it into a function that expects a
ShippingProvider.
There are no comments for now.