December 19, 2025 (3mo 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
Cover Image for 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.

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

ConceptDescription
TypeStructural
Primary intentAllow objects with incompatible interfaces to work together
Core ideaWrap the adaptee to expose the target interface
Key problem solvedReuse existing classes without changing their source code
Common use casesThird-party libraries, legacy code, multiple data sources

Structure and Roles

Il pattern ha quattro ruoli:

  1. The Client — il codice che necessita di una specifica interfaccia.
  2. The Target Interface — il contratto che il Client si aspetta.
  3. The Adaptee — la classe o il modulo incompatibile che possiede la funzionalità richiesta.
  4. 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

SituationUse Adapter?Why
Need to use a third-party library with incompatible APIYesYou can’t change the library, so adapt to it
Control both sides and change is smallNoRefactor directly to avoid extra indirection
Need a simplified high-level interface to a complex systemNoFacade is a better fit
Migrating legacy systems incrementallyYesWrap old components to match new interfaces
Multiple differently structured data sourcesYesAdapters 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.


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.

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, a common approach to convert callbacks to Promises: https://nodejs.org/api/util.html#utilpromisifyoriginal
4.
Stack Overflow Developer Survey 2023, showing the prevalence of JavaScript and web technologies that commonly require integration work: 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.