December 19, 2025 (8mo ago) — last updated August 26, 2026 (11d ago)

Adapter Pattern con TypeScript, React e Node.js

Guida pratica all’Adapter Pattern con esempi in TypeScript, React e Node.js per integrare API, modernizzare codice legacy e migliorare la testabilità.

← Back to blog
Cover Image for Adapter Pattern con TypeScript, React e Node.js

Ti è mai capitato di dover integrare un modulo legacy o una libreria esterna che non si adatta alla forma dei tuoi dati? L’Adapter Pattern traduce un’interfaccia nell’altra, permettendoti di riutilizzare codice esistente senza modificarlo. Questa guida presenta il pattern con esempi pratici in TypeScript, React e Node.js per modernizzare integrazioni, migliorare la testabilità e ridurre il debito tecnico.

Adapter Pattern con TypeScript, React e Node.js

Summary: Scopri come l’Adapter Pattern collega interfacce incompatibili in TypeScript, React e Node.js con esempi pratici e reali.

Introduzione

Ti è mai capitato di dover integrare un modulo legacy o una libreria esterna che non si adatta alla forma dei tuoi dati? È come provare a inserire una spina europea in una presa nordamericana: entrambi funzionano, ma le interfacce non coincidono. L’Adapter Pattern risolve questo traducendo un’interfaccia nell’altra, permettendoti di riutilizzare codice esistente senza modificarlo.

Questa guida mostra il pattern, esempi pratici in TypeScript e React e un adapter per Node.js che modernizza moduli basati su callback. Troverai tecniche pratiche per pulire integrazioni, migliorare la testabilità e ridurre il debito tecnico.

Perché l’Adapter Pattern è importante

L’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 fondamentali per integrare API di terze parti, modernizzare codice legacy e unificare fonti di dati diverse.

Situazioni tipiche in cui gli adapter sono utili:

  • Integrare API che ritornano formati diversi.
  • Modernizzare API legacy basate su callback in modo che funzionino con async/await.
  • Unificare più fonti di dati in un’unica interfaccia usata dai componenti UI.

Usare un adapter mantiene la logica di business pulita e disaccoppiata dai sistemi esterni, facilitando test e manutenzione.

Panoramica rapida

ConcettoDescrizione
TipoStrutturale
Intento primarioPermettere a oggetti con interfacce incompatibili di lavorare insieme
Idea chiaveAvvolgere l’adaptee per esporre l’interfaccia target
Problema risoltoRiutilizzare classi esistenti senza modificarne il codice
Casi d’uso comuniLibrerie di terze parti, codice legacy, più fonti di dati

Struttura e ruoli

Ruoli principali:

  1. Client — il codice che richiede una specifica interfaccia.
  2. Target Interface — il contratto che il Client si aspetta.
  3. Adaptee — la classe o il modulo incompatibile che possiede la funzionalità.
  4. Adapter — implementa il Target e delega all’Adaptee traducendo le chiamate.

Stili comuni:

  • Object adapter (composizione): l’Adapter contiene un’istanza dell’Adaptee.
  • Class adapter (ereditarietà): l’Adapter eredita dall’Adaptee; meno comune in JavaScript/TypeScript.

Esempio pratico: 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 condizioni e mapping sparsi.

Forme dati incompatibili

// Dati da UserServiceA
interface UserA {
  userId: number;
  fullName: string;
  emailAddress: string;
}

// Dati da UserServiceB
interface UserB {
  id: string;
  name: string;
  contact: {
    email: string;
  };
}

Interfaccia target che l’app si aspetta

interface UnifiedUser {
  id: string;
  name: string;
  email: string;
}

Adapter in TypeScript

// Adapter per UserServiceA
function adaptUserA(userA: UserA): UnifiedUser {
  return {
    id: userA.userId.toString(),
    name: userA.fullName,
    email: userA.emailAddress,
  };
}

// Adapter per 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 cambia campo, modifichi solo l’adapter.

Componente React che consuma i dati unificati

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, semplificando test e riuso.

Esempio: modernizzare un modulo Node.js basato su callback

I moduli legacy spesso usano callback con errore come primo argomento. Invece di riscrivere il modulo, costruisci un adapter che espone un’API basata su Promise.

Adaptee legacy (non modificare)

// 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 che ritorna una 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 approccio è simile a util.promisify di Node.js ma mantiene la logica di adattamento esplicita e testabile3.

Uso dell’adapter nell’applicazione

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.

Quando usare un Adapter

Usa un adapter quando due componenti non possono comunicare direttamente perché le loro interfacce differiscono. Scenari tipici:

  • Integrare un’API di terze parti i cui input o output non corrispondono ai tuoi modelli.
  • Wrappare API legacy basate su callback per funzionare con async/await.
  • Supportare più fonti di dati con formati diversi creando un adapter per ogni fonte.

Quando non usare un adapter:

  • Se controlli entrambe le parti e una piccola refactor risolve la discrepanza, preferisci la refactor diretta.
  • Se vuoi semplificare un sottosistema complesso, considera una Facade: la Facade offre un punto d’ingresso semplificato; l’Adapter si concentra solo sulla compatibilità.

Checklist decisionale rapida

SituazioneUsare Adapter?Perché
Usare libreria di terze parti con API incompatibileNon puoi cambiare la libreria, quindi adattala
Controlli entrambe le parti e la modifica è minimaNoRifattorizza direttamente per evitare indirection
Servono interfacce semplificate di alto livelloNoFacade è più adatta
Migrare sistemi legacy gradualmenteWrappa componenti vecchi per adattarli ai nuovi
Più fonti di dati con strutture diverseGli adapter le unificano in un’unica forma

Test e prestazioni

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 introdotto da un adapter è minimo: normalmente una chiamata di funzione in più, 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 il piccolo costo. JavaScript resta tra le tecnologie più diffuse secondo il sondaggio Stack Overflow 20234, rendendo le problematiche di integrazione molto comuni.


At Clean Code Guy, aiutiamo i team a implementare pattern pratici che trasformano codebase fragili in asset resilienti, testabili e piacevoli da mantenere. Se stai lottando con un sistema legacy o integrazioni complesse, i nostri Clean Code Audits offrono una roadmap chiara e azionabile. Scopri come possiamo aiutarti a rilasciare codice migliore, più velocemente.

Domande e risposte rapide

Che problema risolve l’Adapter Pattern?

L’Adapter traduce le chiamate dal client in chiamate che l’adaptee capisce, permettendo di riutilizzare codice senza modificarlo.

Come aiuta l’Adapter con il codice legacy?

Un Adapter avvolge moduli legacy ed espone un’interfaccia moderna, così puoi integrare codice stabile in nuove applicazioni senza riscritture rischiose.

Quando scegliere un Adapter rispetto ad altri pattern?

Scegli un Adapter quando ti serve compatibilità tra due interfacce non corrispondenti. Se vuoi semplificare un sottosistema intero, considera invece una Facade.

1.
Erich Gamma, Richard Helm, Ralph Johnson, and John Vlissides, Design Patterns: Elements of Reusable Object-Oriented Software (Addison-Wesley, 1994). [https://en.wikipedia.org/wiki/Design_Patterns_(book)](https://en.wikipedia.org/wiki/Design_Patterns_(book))
2.
Adapter pattern overview and examples: https://www.geeksforgeeks.org/adapter-pattern/
3.
Node.js documentation for util.promisify: https://nodejs.org/api/util.html#utilpromisifyoriginal
4.
Stack Overflow Developer Survey 2023: tecnologie più diffuse e trend degli sviluppatori. https://survey.stackoverflow.co/2023/
← Back to blog
🙋🏻‍♂️

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.