Scopri come il pattern Adapter collega interfacce incompatibili con esempi in TypeScript, React e Node.js per modernizzare integrazioni e codice legacy.
December 19, 2025 (4mo ago) — last updated February 10, 2026 (2mo ago)
Adapter Pattern: esempi con TypeScript, React e Node.js
Scopri come il pattern Adapter collega interfacce incompatibili con esempi in TypeScript, React e Node.js per modernizzare integrazioni e codice legacy.
← Back to blog
Adapter Pattern: TypeScript, React & Node.js Examples
Summary: Scopri come il pattern Adapter collega interfacce incompatibili in TypeScript, React e Node.js con esempi pratici e reali.
Introduction
Ti è mai capitato di avere una libreria o un modulo legacy perfettamente funzionante che semplicemente non si integra con il resto del sistema? È come cercare di inserire un adattatore europeo in una presa nordamericana — entrambi funzionano, ma le loro interfacce non combaciano. Il pattern Adapter risolve questo traducendo un'interfaccia in un'altra così puoi riutilizzare il codice esistente senza modificarlo.
Questa guida spiega il pattern, mostra esempi in TypeScript e React, e dimostra un adapter in Node.js che modernizza moduli basati su callback. Si concentra su tecniche pratiche che puoi applicare immediatamente per pulire le integrazioni e migliorare la testabilità.
Why the Adapter Pattern Matters
Il pattern Adapter è un pattern strutturale che avvolge un oggetto incompatibile ed espone l'interfaccia che il tuo codice si aspetta. Fu documentato per la prima volta dalla Gang of Four nel 19941. Gli adapter sono essenziali per integrare API di terze parti, modernizzare codice legacy e unificare fonti di dati disparate.
Scenari comuni in cui gli adapter aiutano:
- Integrare API di terze parti che ritornano forme di dati diverse.
- Modernizzare API legacy basate su callback per funzionare con async/await.
- Unificare più fonti di dati in un'unica interfaccia per i componenti UI.
Usare un adapter mantiene la logica di business pulita e disaccoppiata dai sistemi esterni.
Adapter Pattern at a Glance
| Concept | Description |
|---|---|
| Type | Structural |
| Primary intent | Allow objects with incompatible interfaces to work together |
| Core idea | Wrap the adaptee to expose the target interface |
| Key problem solved | Reuse existing classes without changing their source code |
| Common use cases | Third-party libraries, legacy code, multiple data sources |
Structure and Roles
Il pattern ha quattro ruoli:
- The Client — il codice che necessita di una specifica interfaccia.
- The Target Interface — il contratto che il Client si aspetta.
- The Adaptee — la classe o il modulo incompatibile che possiede la funzionalità richiesta.
- The Adapter — implementa il Target e delega all'Adaptee, traducendo le chiamate secondo necessità.
Due stili comuni di adapter:
- Object adapter (composizione): l'Adapter contiene un'istanza dell'Adaptee. Questo è l'approccio più flessibile.
- Class adapter (ereditarietà): l'Adapter eredita dall'Adaptee e implementa il Target. Questo richiede l'ereditarietà multipla ed è meno comune nel JavaScript e TypeScript moderni.
Practical Example: TypeScript + React
Immagina una dashboard che riceve profili utente da due servizi con forme di risposta diverse. Senza un adapter, i componenti si riempiono di logica condizionale.
Incompatible API shapes
// Data from UserServiceA
interface UserA {
userId: number;
fullName: string;
emailAddress: string;
}
// Data from UserServiceB
interface UserB {
id: string;
name: string;
contact: {
email: string;
};
}
Target interface our app expects
interface UnifiedUser {
id: string;
name: string;
email: string;
}
TypeScript adapters
// Adapter for UserServiceA
function adaptUserA(userA: UserA): UnifiedUser {
return {
id: userA.userId.toString(),
name: userA.fullName,
email: userA.emailAddress,
};
}
// Adapter for UserServiceB
function adaptUserB(userB: UserB): UnifiedUser {
return {
id: userB.id,
name: userB.name,
email: userB.contact.email,
};
}
Centralizzare le trasformazioni mantiene i componenti puliti e resilienti. Se un'API rinomina un campo, cambia solo l'adapter.
React component that consumes unified data
interface UserProfileProps {
user: UnifiedUser;
}
const UserProfile: React.FC<UserProfileProps> = ({ user }) => {
return (
<div>
<h2>{user.name}</h2>
<p>ID: {user.id}</p>
<p>Email: {user.email}</p>
</div>
);
};
Questo componente si basa su una forma unica e prevedibile, il che rende i test e il riuso semplici.
Example: Modernizing a Callback-Based Node.js Module
I moduli legacy spesso usano callback con prima l'errore. Invece di modificare un modulo stabile, costruisci un adapter che espone un'API basata su Promise.
Legacy adaptee (do not modify)
// legacyFileProcessor.js
const fs = require('fs');
class LegacyFileProcessor {
processFile(filePath, callback) {
fs.readFile(filePath, 'utf8', (err, data) => {
if (err) {
return callback(err, null);
}
const processedContent = data.toUpperCase();
callback(null, processedContent);
});
}
}
module.exports = LegacyFileProcessor;
Adapter that returns a Promise
// FileProcessorAdapter.js
const LegacyFileProcessor = require('./legacyFileProcessor');
class FileProcessorAdapter {
constructor() {
this.legacyProcessor = new LegacyFileProcessor();
}
processFile(filePath) {
return new Promise((resolve, reject) => {
this.legacyProcessor.processFile(filePath, (err, data) => {
if (err) return reject(err);
resolve(data);
});
});
}
}
module.exports = FileProcessorAdapter;
Questo rispecchia il comportamento di util.promisify di Node ma mantiene la logica di adattamento esplicita e testabile3.
Using the adapter in application code
const FileProcessorAdapter = require('./FileProcessorAdapter');
const fileProcessor = new FileProcessorAdapter();
async function handleFileProcessing() {
try {
console.log('Processing file with modern async/await...');
const content = await fileProcessor.processFile('my-file.txt');
console.log('Processed Content:', content);
} catch (error) {
console.error('An error occurred:', error);
}
}
handleFileProcessing();
Questo mantiene il codice legacy intatto offrendo al resto della codebase un'interfaccia moderna.
When to Use an Adapter
Usa un adapter quando due componenti non possono comunicare direttamente perché le loro interfacce differiscono. Scenari tipici:
- Integrare una API di terze parti i cui input o output non corrispondono ai tuoi modelli.
- Wrappare API legacy basate su callback in modo che funzionino con async/await.
- Supportare più fonti di dati con formati diversi creando un adapter per ogni fonte.
Quando non usare un adapter:
- Se controlli entrambi i sistemi e una piccola refactor risolve la discrepanza, preferisci la refactor diretta.
- Se il tuo obiettivo è semplificare un sottosistema complesso, considera una Facade invece. Una Facade offre un punto d'ingresso semplificato e di alto livello; un Adapter si concentra solo sulla compatibilità.
Quick decision checklist
| Situation | Use Adapter? | Why |
|---|---|---|
| Need to use a third-party library with incompatible API | Yes | You can’t change the library, so adapt to it |
| Control both sides and change is small | No | Refactor directly to avoid extra indirection |
| Need a simplified high-level interface to a complex system | No | Facade is a better fit |
| Migrating legacy systems incrementally | Yes | Wrap old components to match new interfaces |
| Multiple differently structured data sources | Yes | Adapters unify them into one shape |
Testing and Performance
Gli adapter migliorano la testabilità disaccoppiando la logica core dai sistemi esterni. Puoi mockare l'interfaccia di un adapter per testare i componenti in isolamento e testare gli adapter separatamente per verificare la logica di traduzione.
Il sovraccarico prestazionale dovuto a un adapter è minimo — tipicamente una chiamata di funzione in più — ed è trascurabile rispetto a I/O di rete o query al database. Per la maggior parte delle applicazioni web, i benefici di manutenzione e disaccoppiamento superano di gran lunga il piccolo costo. JavaScript rimane il linguaggio più usato nello Stack Overflow Developer Survey, evidenziando quanto spesso gli sviluppatori affrontino lavori di integrazione che gli adapter risolvono4.
Frequently asked questions
Q: What problem does the Adapter pattern solve?
A: Risolve le incompatibilità di interfaccia traducendo le chiamate da un client in chiamate che l'adaptee capisce così da poter riutilizzare il codice senza modificarlo.
Q: How does an Adapter help with legacy code?
A: Un Adapter avvolge moduli legacy ed espone un'interfaccia moderna, permettendoti di integrare codice vecchio e stabile in nuove applicazioni senza rischiose riscritture.
Q: When should I choose an Adapter over other patterns?
A: Scegli un Adapter quando hai bisogno di compatibilità tra due interfacce non corrispondenti. Se vuoi semplificare un intero sottosistema, usa invece una Facade.
Further reading and internal links
- Deep dive on polymorphism vs inheritance: /blog/polymorphism-vs-inheritance
- Strategies for modernizing legacy systems: /blog/modernizing-legacy-systems
- Adapter pattern reference and examples: GeeksforGeeks2
At Clean Code Guy, we help teams implement practical design patterns that turn brittle, complex codebases into assets that are resilient, testable, and a pleasure to work on. If you’re wrestling with a legacy system or tricky integrations, our Clean Code Audits can give you a clear, actionable roadmap to a healthier architecture. Learn how we can help you ship better code, faster.
L'AI scrive codice.Tu lo fai durare.
Nell'era dell'accelerazione AI, il codice pulito non è solo una buona pratica — è la differenza tra sistemi che si scalano e codebase che collassano sotto il loro stesso peso.