Skip to main content

What It Is

The Adapter pattern makes a class with the wrong interface fit the interface your code expects — like a travel plug between a foreign socket and your charger.

You cannot change the old class (it comes from a library, or other code depends on it). So you wrap it in a small class that implements the interface you need and translates the calls.


Example: An Old Printer as a Logger

Our code wants a Logger with log(message). The legacy class only offers writeLine(prefix, text). The adapter holds the old object and translates one call into the other.

The adapter translates the call

run() works with any Logger; the adapter makes the legacy printer one of them without touching its code. Both printed lines start with LOG | — that prefix is supplied by the adapter, not by the caller.

Prompt
Write a TypeScript program with a Logger interface (a log method) that a run function expects, a LegacyPrinter class with a different writeLine(prefix, text) method that cannot be changed, and a PrinterAdapter class that implements Logger by calling writeLine with the prefix LOG. Run the service with the adapter so it logs two messages.
Output: TypeScript file and captured output
The adapter translates the call

When to Use It

Use it when working code and a needed interface do not match, and you cannot (or should not) change either side — third-party libraries, legacy code, generated code.

Skip it when you own both sides — then just change one of them to match. An adapter between two classes you control is a workaround for a decision you could simply make.


Pros and Cons

Pros: old and new code cooperate without changes to either; the translation lives in one small, testable class.

Cons: one more hop to read through; many adapters stacked on each other are a sign the interfaces themselves need rework.