Adapter Pattern — FixIt Pro Series #06
FixIt Pro inherited a legacy SMS service with an incompatible interface. Learn how the Adapter pattern bridges old and new without touching legacy code — in C# and TypeScript.
Series: Design Patterns with FixIt Pro · Episode 06 / 22 · Structural Pattern
Previous: #05 — Singleton Pattern
Welcome to Structural Patterns
The first five episodes covered Creational patterns — how objects are created. We now move into Structural patterns — how objects are composed and connected.
Structural patterns are about building larger structures from smaller pieces in a way that keeps those pieces flexible and independent of each other.
The Scenario
FixIt Pro just acquired a smaller competitor. Along with the customer base came a piece of legacy infrastructure: an SMS gateway class called LegacySmsGateway. It works. It's battle-tested. The business doesn't want it rewritten.
The problem: FixIt Pro's modern notification system is built around an INotifier interface. Every notifier — email, push, in-app — implements SendNotification(string jobId, string message). The LegacySmsGateway doesn't. It has its own method signature: Transmit(string phoneNumber, string text).
You can't change the legacy class — it's used by the acquired company's systems. You can't change INotifier — it's used across FixIt Pro's entire codebase.
You need something in between. That's the Adapter.
What Is the Adapter Pattern?
Convert the interface of a class into another interface that clients expect. The Adapter lets classes work together that couldn't otherwise because of incompatible interfaces.
It's exactly what it sounds like — a power socket adapter. Your laptop charger (the client) expects a specific plug shape. The wall socket (the adaptee) has a different shape. The adapter sits in between and makes them compatible.
The four participants
| Role | FixIt Pro equivalent |
|---|---|
| Target interface | INotifier — what the FixIt Pro system expects |
| Adaptee | LegacySmsGateway — the incompatible legacy class |
| Adapter | SmsNotifierAdapter — wraps the adaptee, implements the target |
| Client | JobDispatcher — uses INotifier, unaware of the legacy class |
Two variants exist: the Object Adapter (uses composition — recommended) and the Class Adapter (uses inheritance — C# only, limited). We'll implement both in C# and the object adapter in TypeScript.
C# Implementation
// ── Target Interface ───────────────────────────────────────
public interface INotifier
{
void SendNotification(string jobId, string message);
}
// ── Existing implementations (already work fine) ──────────
public class EmailNotifier : INotifier
{
public void SendNotification(string jobId, string message) =>
Console.WriteLine($"📧 Email | Job #{jobId}: {message}");
}
public class PushNotifier : INotifier
{
public void SendNotification(string jobId, string message) =>
Console.WriteLine($"🔔 Push | Job #{jobId}: {message}");
}
// ── Adaptee — the legacy class we cannot change ────────────
public class LegacySmsGateway
{
// Incompatible method signature — different name, different params
public void Transmit(string phoneNumber, string text)
{
Console.WriteLine($"📱 SMS → {phoneNumber}: {text}");
}
}
// ── Object Adapter (composition) ✅ Recommended ────────────
// Wraps the legacy class and implements INotifier.
// The client uses INotifier — it never knows LegacySmsGateway exists.
public class SmsNotifierAdapter : INotifier
{
private readonly LegacySmsGateway _gateway;
private readonly string _phoneNumber;
public SmsNotifierAdapter(LegacySmsGateway gateway, string phoneNumber)
{
_gateway = gateway;
_phoneNumber = phoneNumber;
}
public void SendNotification(string jobId, string message)
{
// Translate the INotifier call into what the legacy class expects
var smsText = $"[FixIt Pro] Job #{jobId}: {message}";
_gateway.Transmit(_phoneNumber, smsText);
}
}
// ── Class Adapter (inheritance) — C# only ─────────────────
// Inherits from the adaptee AND implements the target interface.
// Less flexible — tightly couples adapter to the concrete legacy class.
// Use the object adapter unless you have a specific reason not to.
public class SmsNotifierClassAdapter : LegacySmsGateway, INotifier
{
private readonly string _phoneNumber;
public SmsNotifierClassAdapter(string phoneNumber)
{
_phoneNumber = phoneNumber;
}
public void SendNotification(string jobId, string message)
{
var smsText = $"[FixIt Pro] Job #{jobId}: {message}";
Transmit(_phoneNumber, smsText); // calls inherited method
}
}
// ── Client — JobDispatcher ─────────────────────────────────
// Works entirely against INotifier — no knowledge of legacy classes.
public class JobDispatcher
{
private readonly IReadOnlyList<INotifier> _notifiers;
public JobDispatcher(IEnumerable<INotifier> notifiers)
{
_notifiers = notifiers.ToList().AsReadOnly();
}
public void DispatchJob(string jobId, string title)
{
Console.WriteLine($"\n--- Dispatching Job #{jobId}: {title} ---");
foreach (var notifier in _notifiers)
notifier.SendNotification(jobId, $"Your job '{title}' has been assigned.");
}
}
// ── Program ────────────────────────────────────────────────
class Program
{
static void Main()
{
var legacyGateway = new LegacySmsGateway();
var notifiers = new INotifier[]
{
new EmailNotifier(),
new PushNotifier(),
new SmsNotifierAdapter(legacyGateway, "+264-81-000-0000"), // adapted!
};
var dispatcher = new JobDispatcher(notifiers);
dispatcher.DispatchJob("JC-101", "Burst pipe in kitchen");
dispatcher.DispatchJob("JC-102", "Faulty circuit breaker");
}
}
Output:
--- Dispatching Job #JC-101: Burst pipe in kitchen ---
📧 Email | Job #JC-101: Your job 'Burst pipe in kitchen' has been assigned.
🔔 Push | Job #JC-101: Your job 'Burst pipe in kitchen' has been assigned.
📱 SMS → +264-81-000-0000: [FixIt Pro] Job #JC-101: Your job 'Burst pipe in kitchen' has been assigned.
--- Dispatching Job #JC-102: Faulty circuit breaker ---
📧 Email | Job #JC-102: Your job 'Faulty circuit breaker' has been assigned.
🔔 Push | Job #JC-102: Your job 'Faulty circuit breaker' has been assigned.
📱 SMS → +264-81-000-0000: [FixIt Pro] Job #JC-102: Your job 'Faulty circuit breaker' has been assigned.
TypeScript Implementation
// ── Target Interface ───────────────────────────────────────
// Notifier/INotifier.ts
export interface INotifier {
sendNotification(jobId: string, message: string): void;
}
// ── Existing Implementations ───────────────────────────────
// Notifier/EmailNotifier.ts
import { INotifier } from "./INotifier";
export class EmailNotifier implements INotifier {
sendNotification(jobId: string, message: string): void {
console.log(`📧 Email | Job #${jobId}: ${message}`);
}
}
// Notifier/PushNotifier.ts
import { INotifier } from "./INotifier";
export class PushNotifier implements INotifier {
sendNotification(jobId: string, message: string): void {
console.log(`🔔 Push | Job #${jobId}: ${message}`);
}
}
// ── Adaptee — legacy class we cannot change ────────────────
// Legacy/LegacySmsGateway.ts
export class LegacySmsGateway {
// Incompatible method signature
transmit(phoneNumber: string, text: string): void {
console.log(`📱 SMS → ${phoneNumber}: ${text}`);
}
}
// ── Object Adapter ─────────────────────────────────────────
// Adapter/SmsNotifierAdapter.ts
import { INotifier } from "../Notifier/INotifier";
import { LegacySmsGateway } from "../Legacy/LegacySmsGateway";
export class SmsNotifierAdapter implements INotifier {
constructor(
private readonly gateway: LegacySmsGateway,
private readonly phoneNumber: string
) {}
sendNotification(jobId: string, message: string): void {
// Translate INotifier call into what the legacy class expects
const smsText = `[FixIt Pro] Job #${jobId}: ${message}`;
this.gateway.transmit(this.phoneNumber, smsText);
}
}
// ── Client — JobDispatcher ─────────────────────────────────
// Dispatcher/JobDispatcher.ts
import { INotifier } from "../Notifier/INotifier";
export class JobDispatcher {
constructor(private readonly notifiers: INotifier[]) {}
dispatchJob(jobId: string, title: string): void {
console.log(`\n--- Dispatching Job #${jobId}: ${title} ---`);
for (const notifier of this.notifiers)
notifier.sendNotification(jobId, `Your job '${title}' has been assigned.`);
}
}
// ── App.ts ─────────────────────────────────────────────────
import { EmailNotifier } from "./Notifier/EmailNotifier";
import { PushNotifier } from "./Notifier/PushNotifier";
import { LegacySmsGateway } from "./Legacy/LegacySmsGateway";
import { SmsNotifierAdapter } from "./Adapter/SmsNotifierAdapter";
import { JobDispatcher } from "./Dispatcher/JobDispatcher";
const legacyGateway = new LegacySmsGateway();
const dispatcher = new JobDispatcher([
new EmailNotifier(),
new PushNotifier(),
new SmsNotifierAdapter(legacyGateway, "+264-81-000-0000"), // adapted!
]);
dispatcher.dispatchJob("JC-101", "Burst pipe in kitchen");
dispatcher.dispatchJob("JC-102", "Faulty circuit breaker");
C# vs TypeScript — Key Differences
| Aspect | C# | TypeScript |
|---|---|---|
| Target | public interface INotifier |
export interface INotifier |
| Implements | : INotifier |
implements INotifier |
| Class Adapter | Supported — inherit adaptee + implement target | Not recommended — TypeScript supports single inheritance only via extends |
| Constructor params | Explicit types in signature | private readonly in constructor params |
| Collection type | IEnumerable<INotifier> → IReadOnlyList |
INotifier[] array |
TypeScript technically supports a Class Adapter via extends, but since TypeScript also supports multiple interface implementations, the Object Adapter (composition) is always the cleaner choice. Prefer composition over inheritance — in any language.
Object Adapter vs Class Adapter
| Object Adapter | Class Adapter | |
|---|---|---|
| Mechanism | Composition — holds a reference to the adaptee | Inheritance — extends the adaptee |
| Flexibility | Can adapt any subclass of the adaptee | Locked to one concrete adaptee class |
| Overriding | Cannot override adaptee behaviour | Can override adaptee methods |
| Languages | Works in all OO languages | Requires multiple inheritance or interface + class combo |
| Recommendation | ✅ Use this | ⚠️ Only when override access is needed |
When to Use the Adapter
Use it when:
- You want to use an existing class but its interface doesn't match what you need
- You're integrating a third-party library or legacy system into a modern codebase
- You want to create a reusable class that cooperates with classes that don't share a common interface
Avoid it when:
- You can change the source — if you own the legacy class and can refactor it, do that instead
- The translation logic is so complex it warrants a full rewrite rather than a thin wrapper
Real-World Takeaway
The Adapter is one of the most commonly used patterns in real codebases — you just don't always recognise it. In .NET, StreamReader adapts a Stream to the TextReader interface. In JavaScript, wrapper libraries around fetch (like axios) adapt the browser's native API to a more convenient interface. Every time you write a wrapper class that makes one thing look like another — that's an Adapter.
In FixIt Pro, the SmsNotifierAdapter means the dispatcher stays clean. It knows nothing about phone numbers, legacy method names, or SMS formatting. It just calls SendNotification on whatever notifiers it's given. The adapter handles the translation invisibly.
New notifier? Write a class that implements INotifier. Plug it in. The dispatcher never changes.
Repo Structure for This Episode
github.com/antonlungameni/fixit-pro-design-patterns
fixit-pro-design-patterns/
├── csharp/Structural/06-Adapter/
│ ├── Target/INotifier.cs
│ ├── Notifiers/EmailNotifier.cs
│ ├── Notifiers/PushNotifier.cs
│ ├── Legacy/LegacySmsGateway.cs
│ ├── Adapter/SmsNotifierAdapter.cs
│ ├── Adapter/SmsNotifierClassAdapter.cs
│ ├── Dispatcher/JobDispatcher.cs
│ └── Program.cs
└── typescript/Structural/06-Adapter/
├── Notifier/INotifier.ts
├── Notifier/EmailNotifier.ts
├── Notifier/PushNotifier.ts
├── Legacy/LegacySmsGateway.ts
├── Adapter/SmsNotifierAdapter.ts
├── Dispatcher/JobDispatcher.ts
└── App.ts
Previous: #05 — Singleton Pattern
Next up: #07 — Bridge Pattern
Separating job urgency levels from notification channels — so you can mix and match any urgency with any channel without an explosion of subclasses.