Adapter Pattern Guide: How Do You Connect Mismatched Interfaces?
Dev

Adapter Pattern Guide: How Do You Connect Mismatched Interfaces?


In real projects, the interface you want and the interface you actually receive are often different. This happens especially with external libraries, legacy systems, and third-party APIs. The adapter pattern is a classic way to connect those mismatched shapes without rewriting everything.

In this post, we will cover:

  • what the adapter pattern is
  • why it is useful
  • when it appears often
  • how it differs from a simple wrapper

The key idea is that the adapter pattern converts an incompatible interface into the form the caller expects, reducing the need to change the rest of the system.

What is the adapter pattern?

The adapter pattern keeps the original implementation in place and adds a conversion layer in front of it so that the rest of the code can see a more suitable interface.

In simple terms:

  • keep the real implementation
  • wrap it in a shape the caller expects
  • connect the two sides

Why is it useful?

If you respond to every interface mismatch by rewriting all callers:

  • change impact grows
  • external detail spreads everywhere
  • testing and maintenance get harder

An adapter centralizes the translation responsibility.

When does it fit well?

  • integrating external libraries
  • connecting to legacy APIs
  • bridging old and new code
  • fitting external tools into an internal standard shape

So it is especially useful when the real need is connection, not a total rewrite.

How is it different from a wrapper?

They overlap, but the adapter pattern is especially focused on making two mismatched interfaces fit together.

So compared with:

  • simple packaging
  • add-on behavior

the core emphasis is interface translation.

Common misunderstandings

1. If interfaces differ, just change the callers

That may work in small cases, but once the impact grows, an adapter is often safer.

2. An adapter is just a temporary patch

A well-designed adapter can become a very healthy boundary that isolates change.

3. Adapter and decorator are basically the same

Both can wrap objects, but adapter is about translation while decorator is more about behavior extension.

Simple Example

class LegacyMailer {
  sendMail(text: string): void {
    console.log(`legacy mail: ${text}`);
  }
}

interface Notifier {
  send(message: string): void;
}

class MailAdapter implements Notifier {
  constructor(private legacyMailer: LegacyMailer) {}

  send(message: string): void {
    this.legacyMailer.sendMail(message);
  }
}

class NotificationService {
  constructor(private notifier: Notifier) {}

  sendWelcome(): void {
    this.notifier.send('Welcome!');
  }
}

const service = new NotificationService(new MailAdapter(new LegacyMailer()));
service.sendWelcome();

The main code expects Notifier, and the adapter translates that expectation into the legacy sendMail interface.

FAQ

Q. Where should beginners look for adapter opportunities?

Start where a library or legacy interface feels awkward compared with the style of the surrounding code.

Q. Does adapter help testing too?

Yes. It can isolate external details and make replacement easier during tests.

Q. Can too many adapters become messy?

They can, which is why they work best when placed at meaningful boundaries.

Start Here

Continue with the core guides that pull steady search traffic.