mailwal

The Adapter Design Pattern

The Coding Gopher

The Adapter Design Pattern

Bridging the Gap Between Incompatible Interfaces

If you have ever traveled internationally, you know the frustration of staring at a wall outlet that looks completely different from the plug on your laptop charger. You don’t throw away your laptop, and you certainly don’t rewire the hotel’s electrical grid. Instead, you use a travel adapter—a small device that sits between your plug and the wall, translating the interface of one to match the other.

Understanding the Adapter Design Pattern in C#
The Adapter Design Pattern is a structural design pattern that acts as a bridge to allow objects with incompatible interfaces to collaborate. It wraps an existing class with a new interface so that it matches the format expected by the client, similar to how a physical laptop charger adapts a wall outlet's voltage to fit your device.

In software engineering, the Adapter Pattern (sometimes called the Wrapper pattern) does exactly the same thing. It is a structural design pattern that allows objects with incompatible interfaces to collaborate. It acts as a “universal translator” between your application’s clean internal code and messy external dependencies, legacy systems, or third-party libraries.

Join a community of senior engineers. Subscribe and get FREE access to System Design deep dives.

The Problem: The Square Peg and the Round Hole

Imagine you are building a modern alert system for your company. Your core application uses a standard Notification interface that expects every service to implement a send() method.

You start with email, which works perfectly. But then, the business asks you to integrate a third-party Slack SDK. You download the SDK, but you discover that the Slack library doesn’t have a send() method. Instead, it has a post_to_channel() method, and it expects a completely different data format.

Adapter
The Adapter design pattern is a structural tool that lets two mismatched software parts work together. It wraps an incompatible class (the square peg) so it fits the interface expected by the client (the round hole).

Without the Adapter pattern, you might be tempted to scatter if/else checks throughout your core application:

def trigger_alert(service, title, message):
    # BAD: The core app is now coupled to the Slack SDK's specific API!
    if isinstance(service, SlackAPI):
        formatted_text = f"*{title}*\n{message}"
        service.post_to_channel("C12345", formatted_text)
    else:
        service.send(title, message)

Why is this bad?
Your core application is now deeply entangled with the specifics of the Slack SDK. If Slack updates their API tomorrow and renames the method to publish_message(), you have to hunt down and change your core business logic.

Build Redis, Git, Docker & Claude Code From Scratch

Want to become a better software engineer?

Trusted by engineers at Google, NVIDIA, and Anthropic, CodeCrafters helps you rebuild technologies like Redis, Git, Docker, and Claude Code from scratch so you can deeply understand how real systems work.

We’ve partnered with CodeCrafters to give all readers an exclusive 40% lifetime discount.

💡 Engineers may expense platforms like this through their team’s learning budget.

Start Building & Unlock 40% Off

The Solution: The Wrapper

Adapter
An XML to JSON adapter pattern is a structural design tool. It lets a system that needs JSON talk to an old service that only gives out XML. The adapter wraps the XML code and turns it into JSON data. You do not need to change your main app or the old service.

The Adapter Pattern solves this by introducing a middleman class (the Adapter). This class implements your application’s expected interface but quietly translates the calls under the hood into a format the incompatible service (the Adaptee) understands.

The Anatomy of the Pattern

  1. The Target Interface: The domain-specific interface that your core application expects to work with.

  2. The Adaptee: The existing, incompatible class or third-party library that contains the useful behavior.

  3. The Adapter: The class that bridges the gap. It implements the Target Interface and holds a reference to the Adaptee.

Refactoring to the Adapter Pattern

Let’s clean up our notification system using Python:

from abc import ABC, abstractmethod

# 1. The Target Interface
# This is the "round hole" our application expects.
class NotificationProvider(ABC):
    @abstractmethod
    def send(self, title: str, message: str) -> None:
        pass

# A compatible class that already fits the Target Interface
class EmailService(NotificationProvider):
    def send(self, title: str, message: str) -> None:
        print(f"Sending Email -> Subject: {title} | Body: {message}")

# 2. The Adaptee
# This is the third-party Slack SDK. We CANNOT change this code. It's the "square peg".
class ExternalSlackAPI:
    def post_to_channel(self, channel_id: str, text: str) -> None:
        print(f"Slack API posting to {channel_id}: {text}")

# 3. The Adapter
# This class implements our expected interface, but wraps the Adaptee.
class SlackAdapter(NotificationProvider):
    def __init__(self, slack_api: ExternalSlackAPI, channel_id: str):
        self.slack_api = slack_api
        self.channel_id = channel_id

    def send(self, title: str, message: str) -> None:
        # Translate the data format
        formatted_markdown = f"*{title}*\n> {message}"
        
        # Translate the method call
        self.slack_api.post_to_channel(self.channel_id, formatted_markdown)

Join a community of senior engineers. Subscribe and get FREE access to System Design deep dives.

Adapter Pattern | Programming With Wolfgang

See It in Action

Now, your core application can treat everything like a standard NotificationProvider. It doesn’t know (and doesn’t care) that Slack uses a completely different API under the hood.

# The core system just wants a list of compatible providers
def broadcast_alert(providers: list[NotificationProvider], title: str, message: str):
    for provider in providers:
        # The core app blindly calls .send() on everything
        provider.send(title, message)

# Setup
email = EmailService()
slack_sdk = ExternalSlackAPI()

# We wrap the incompatible SDK in our Adapter
slack_adapter = SlackAdapter(slack_sdk, "C999_ALERTS")

# Execute
broadcast_alert([email, slack_adapter], "System Outage", "Database is down!")

# Output:
# Sending Email -> Subject: System Outage | Body: Database is down!
# Slack API posting to C999_ALERTS: *System Outage*
# > Database is down!

Enterprise Use Cases

The Adapter pattern is a lifesaver in large codebases, particularly when dealing with system migrations and external boundaries:

  • Third-Party APIs and SDKs: Instead of letting Stripe, Twilio, or AWS SDK objects bleed into your core business logic, you create a PaymentProcessorAdapter or SMSAdapter. If you ever switch vendors, you only have to write a new adapter; your core app remains untouched.

  • Legacy Code Migration: When incrementally strangling a legacy system, you can write an adapter that makes the old legacy database look like the shiny new ORM interface your modern code expects.

  • Data Format Translation: Adapting an older service that returns XML into a format that satisfies a modern component expecting JSON.

Adapter in Ruby / Design Patterns
An adapter design pattern works like a power plug converter. It lets two different systems work together even when their interfaces do not match.

Key Takeaway: Use the Adapter Pattern as a defensive barrier around your core application. By forcing external libraries and legacy code to adapt to your interfaces (rather than the other way around), you protect your application from vendor lock-in and sweeping, breaking changes.

Join a community of senior engineers. Subscribe and get FREE access to System Design deep dives.

Invite your friends and earn rewards
If you enjoy The Coding Gopher, share it with your friends and earn rewards when they subscribe.

Invite Friends

© 2026 The Coding Gopher
548 Market Street PMB 72296, San Francisco, CA 94104