December 20, 2025 (8mo ago) — last updated July 12, 2026 (1mo ago)

Design Pattern OOP: Guida Pratica e Esempi TypeScript

Impara i design pattern OOP principali con esempi TypeScript pratici: creazionali, strutturali e comportamentali per codice più mantenibile ed estendibile.

← Back to blog
Cover Image for Design Pattern OOP: Guida Pratica e Esempi TypeScript

I design pattern OOP sono soluzioni comprovate per problemi di progettazione ricorrenti. Questa guida spiega pattern creazionali, strutturali e comportamentali con esempi TypeScript pratici per migliorare manutenzione e estendibilità del tuo codice.

Una guida pratica ai design pattern OOP

Padroneggia i design pattern OOP con la nostra guida. Impara i pattern creazionali, strutturali e comportamentali con esempi di codice chiari e pratici per elevare le tue competenze.

Introduzione

I design pattern nella programmazione orientata agli oggetti sono schemi collaudati e riutilizzabili per risolvere problemi di progettazione ricorrenti. Non sono frammenti di codice da copiare, ma modelli che ti aiutano a organizzare classi e oggetti in modo che il codice sia più facile da mantenere, estendere e testare. Una solida comprensione dei pattern creazionali, strutturali e comportamentali ti permette di comunicare intenzioni di progettazione, rifattorizzare codice legacy in sicurezza e scegliere lo strumento giusto per ogni problema architetturale.1

Cosa sono i design pattern nella programmazione orientata agli oggetti

Programmare senza un progetto chiaro può portare a una base di codice difficile da scalare. I design pattern sono come ricette collaudate: soluzioni generalizzabili che puoi adattare ai tuoi casi d'uso. Conoscere i pattern principali migliora la qualità del codice e la collaborazione nel team.

Uno schizzo di un cappello da chef che lascia cadere lettere dorate in un ricettario aperto.

Il linguaggio condiviso degli sviluppatori

Un grande valore dei pattern è il vocabolario condiviso: pronuncia “Factory” o “Singleton” e molti sviluppatori capiranno subito l'intento e la struttura ad alto livello, accelerando la collaborazione e le decisioni architetturali. “Design Patterns: Elements of Reusable Object-Oriented Software,” pubblicato nel 1994 dai Gang of Four, ha catalogato 23 pattern fondamentali ancora rilevanti oggi.2

Un design pattern non è un progetto finito che può essere trasformato direttamente in codice. È una descrizione di come risolvere un problema che può essere applicata in molte situazioni.

Prendere confidenza con principi OOP come polimorfismo ed ereditarietà aiuta ad applicare i pattern in modo efficace. Per un ripasso pratico, vedi la nostra guida che confronta polimorfismo vs ereditarietà.

Le tre categorie principali dei design pattern

Il Gang of Four ha organizzato i pattern in tre categorie: creazionali, strutturali e comportamentali. Conoscere queste categorie aiuta a scegliere rapidamente l'approccio giusto.

Diagramma che illustra i pattern di design creazionali, strutturali e comportamentali con semplici illustrazioni a blocchi.

Panoramica delle categorie di design pattern OOP

Categoria di PatternScopo principaleEsempi comuni
CreazionaliGestire e astrarre la creazione di oggettiFactory, Builder, Singleton, Prototype
StrutturaliComporre classi e oggetti in strutture più grandiAdapter, Decorator, Facade, Composite
ComportamentaliDefinire come gli oggetti interagisconoObserver, Strategy, Command, Iterator

Pattern creazionali: specialisti della costruzione

I pattern creazionali controllano come gli oggetti vengono creati in modo che il codice client non sia strettamente accoppiato alle classi concrete. Nascondono la logica di creazione, aumentano la flessibilità e permettono di gestire l'instanziazione.

Pattern strutturali: la colla architettonica

I pattern strutturali aiutano ad assemblare gli oggetti in sistemi più robusti. Semplificano le relazioni tra i componenti così puoi cambiare parti senza rompere l'intero sistema.

I pattern strutturali semplificano il design del sistema identificando modi chiari per realizzare relazioni tra entità.

Pattern comportamentali: direttori della comunicazione

I pattern comportamentali governano l'interazione e le responsabilità degli oggetti. Creano canali di comunicazione puliti: Observer per sistemi event-driven e Strategy per sostituire algoritmi senza modificare i client.

Creare oggetti con i pattern creazionali

I pattern creazionali introdotto uno strato di astrazione attorno alla creazione degli oggetti, così puoi sostituire implementazioni senza cambiare il codice client. Di seguito due esempi pratici in TypeScript: Singleton e Factory Method.

Un'illustrazione che contrappone singoli oggetti a forma d'uovo con una linea di assemblaggio di una fabbrica che produce oggetti colorati.

Il pattern Singleton: garantire una sola istanza

Singleton garantisce che una classe abbia una sola istanza e fornisce un punto di accesso globale. È utile per risorse condivise come una connessione al database o un logger, ma può introdurre stato globale che complica i test e la gestione delle dipendenze.3

Esempio: un Singleton TypeScript per una connessione al database.

class DatabaseConnection {
  private static instance: DatabaseConnection;

  private constructor() {
    // A private constructor prevents external 'new' calls
    console.log("Connecting to the database...");
  }

  public static getInstance(): DatabaseConnection {
    if (!DatabaseConnection.instance) {
      DatabaseConnection.instance = new DatabaseConnection();
    }
    return DatabaseConnection.instance;
  }

  public query(sql: string): void {
    console.log(`Executing query: ${sql}`);
  }
}

// Usage
const db1 = DatabaseConnection.getInstance();
const db2 = DatabaseConnection.getInstance();

db1.query("SELECT * FROM users");
console.log(db1 === db2); // true

Il compromesso del Singleton: usa i Singleton per risorse veramente globali e preferisci l'injection delle dipendenze per una migliore testabilità e modularità.3

Il Factory Method: lasciare che le sottoclassi decidano cosa creare

Factory Method definisce un'interfaccia per creare un oggetto, mentre le sottoclassi decidono quale prodotto concreto istanziare. Disaccoppia il codice client dalle classi concrete, rendendo il sistema più semplice da estendere.

Esempio: rendering di pulsanti specifici per il sistema operativo in TypeScript.

interface Button {
  render(): void;
  onClick(f: () => void): void;
}

class WindowsButton implements Button {
  render() { console.log("Rendering a button in Windows style."); }
  onClick(f: () => void) { console.log("Windows button click event."); f(); }
}

class MacButton implements Button {
  render() { console.log("Rendering a button in macOS style."); }
  onClick(f: () => void) { console.log("Mac button click event."); f(); }
}

abstract class Dialog {
  abstract createButton(): Button;

  render() {
    const okButton = this.createButton();
    okButton.render();
  }
}

class WindowsDialog extends Dialog {
  createButton(): Button { return new WindowsButton(); }
}

class MacDialog extends Dialog {
  createButton(): Button { return new MacButton(); }
}

// Client
const os: string = "windows";
let dialog: Dialog;
if (os === "windows") dialog = new WindowsDialog(); else dialog = new MacDialog();
dialog.render();

Factory Method mantiene il creatore ignaro dei prodotti concreti, rendendo aggiunte come LinuxDialog semplici.

Costruire sistemi flessibili con i pattern strutturali

I pattern strutturali ti aiutano a comporre oggetti in sistemi robusti e adattabili. Due pattern pratici sono Adapter e Decorator.

Illustrazioni fatte a mano di un adattatore elettrico e di una pila di anelli che rappresentano i design pattern OOP.

Il pattern Adapter: colmare interfacce incompatibili

Adapter avvolge un'interfaccia incompatibile in modo che sia conforme a ciò che il tuo sistema si aspetta. Questo evita modifiche invasive al codice legacy.

Esempio: adattare un ModernLogger a un'interfaccia ILogger esistente.

class ModernLogger {
  public logInfo(message: string): void {
    console.log(`[INFO]: ${message}`);
  }
}

interface ILogger { log(message: string): void; }

class LoggerAdapter implements ILogger {
  private modernLogger: ModernLogger;
  constructor() { this.modernLogger = new ModernLogger(); }
  public log(message: string): void { this.modernLogger.logInfo(message); }
}

const logger: ILogger = new LoggerAdapter();
logger.log("User logged in successfully.");

Il pattern Decorator: aggiungere funzionalità dinamicamente

Decorator aggiunge responsabilità agli oggetti a runtime avvolgendoli. È più flessibile della sottoclasse e segue il principio della singola responsabilità.

Esempio: comporre un abbonamento con componenti aggiuntivi opzionali.

interface Subscription { getDescription(): string; getCost(): number; }

class BasicSubscription implements Subscription {
  getDescription(): string { return "Basic Plan"; }
  getCost(): number { return 10; }
}

abstract class SubscriptionDecorator implements Subscription {
  protected subscription: Subscription;
  constructor(subscription: Subscription) { this.subscription = subscription; }
  abstract getDescription(): string;
  abstract getCost(): number;
}

class PremiumSupportDecorator extends SubscriptionDecorator {
  getDescription(): string { return `${this.subscription.getDescription()}, Premium Support`; }
  getCost(): number { return this.subscription.getCost() + 5; }
}

class CloudStorageDecorator extends SubscriptionDecorator {
  getDescription(): string { return `${this.subscription.getDescription()}, 1TB Cloud Storage`; }
  getCost(): number { return this.subscription.getCost() + 7; }
}

let mySubscription: Subscription = new BasicSubscription();
mySubscription = new PremiumSupportDecorator(mySubscription);
mySubscription = new CloudStorageDecorator(mySubscription);
console.log(mySubscription.getDescription());
console.log(mySubscription.getCost());

Questo approccio compositivo mantiene le funzionalità modulari e facili da testare.

Gestire le interazioni con i pattern comportamentali

I pattern comportamentali organizzano la comunicazione tra oggetti in modo che i sistemi rimangano flessibili e manutenibili. Due esempi chiave sono Observer e Strategy.

Il pattern Observer: notificare le parti interessate

Observer stabilisce una relazione uno-a-molti: quando un Subject cambia stato, gli Observer vengono notificati automaticamente. È ideale per sistemi event-driven.

Esempio: un semplice servizio di notifiche.

interface Subject { attach(observer: Observer): void; detach(observer: Observer): void; notify(): void; }
interface Observer { update(subject: Subject): void; }

class NotificationService implements Subject {
  public state: string = '';
  private observers: Observer[] = [];
  attach(observer: Observer): void { this.observers.push(observer); }
  detach(observer: Observer): void { const i = this.observers.indexOf(observer); if (i !== -1) this.observers.splice(i, 1); }
  notify(): void { for (const o of this.observers) o.update(this); }
  public createNewPost(title: string): void { this.state = `New Post: ${title}`; console.log(`\nNotificationService: A new post was created.`); this.notify(); }
}

class EmailNotifier implements Observer { public update(subject: Subject): void { if (subject instanceof NotificationService) console.log(`EmailNotifier: Sending email about "${subject.state}"`); } }
class PushNotifier implements Observer { public update(subject: Subject): void { if (subject instanceof NotificationService) console.log(`PushNotifier: Sending push notification for "${subject.state}"`); } }

const notificationService = new NotificationService();
const emailer = new EmailNotifier();
const pusher = new PushNotifier();
notificationService.attach(emailer);
notificationService.attach(pusher);
notificationService.createNewPost("Understanding Observer Pattern");
notificationService.detach(pusher);
notificationService.createNewPost("Why Strategy is Awesome");

Observer produce un design poco accoppiato: i Subject non devono conoscere gli Observer concreti.

Il pattern Strategy: incapsulare algoritmi

Strategy consente di sostituire algoritmi a runtime, evitando grandi blocchi condizionali. Si allinea al principio Open/Closed e migliora estensibilità e testabilità.4

Esempio: strategie di pagamento per un carrello della spesa.

interface PaymentStrategy { pay(amount: number): void; }
class CreditCardStrategy implements PaymentStrategy { pay(amount: number): void { console.log(`Paying $${amount} with Credit Card.`); } }
class PayPalStrategy implements PaymentStrategy { pay(amount: number): void { console.log(`Paying $${amount} via PayPal.`); } }

class ShoppingCart {
  private paymentStrategy: PaymentStrategy;
  constructor(strategy: PaymentStrategy) { this.paymentStrategy = strategy; }
  public setPaymentStrategy(strategy: PaymentStrategy) { this.paymentStrategy = strategy; }
  public checkout(amount: number): void { this.paymentStrategy.pay(amount); }
}

const cart = new ShoppingCart(new CreditCardStrategy());
cart.checkout(150);
cart.setPaymentStrategy(new PayPalStrategy());
cart.checkout(150);

Strategy elimina la complessità condizionale e rende i sistemi più facili da estendere.

Errori comuni di progettazione e strategie di refactoring

Scegliere un pattern è solo metà della battaglia. Evita anti-pattern come il God Object, che accapara responsabilità e viola il principio della singola responsabilità. Cerca odori di codice—metodi lunghi, dipendenze eccessive o classi troppo grandi—e affrontali con refactoring disciplinato.

Il refactoring migliora la struttura interna senza cambiare il comportamento esterno. È il modo per domare sistemi legacy in sicurezza. Per approfondire tecniche di refactoring e casi pratici, consulta le nostre risorse.

Rifattorizzare verso il pattern Strategy

Un grande switch o una lunga catena if/else è un classico odore. Rifattorizza estraendo il comportamento variabile in un'interfaccia Strategy con classi concrete. Sostituisci il condizionale con una singola chiamata al metodo della strategia selezionata: migliorerai estensibilità, testabilità e l'allineamento ai principi SOLID.4

Domande frequenti sintetiche

Quanto tempo ci vuole per apprendere i pattern essenziali?

Concentrati prima su 5–7 pattern chiave: Singleton, Factory, Adapter, Decorator, Observer e Strategy. Con pratica applicata nei progetti reali, puoi ottenere familiarità pratica in poche settimane.

Quando è meglio evitare un design pattern?

Evita pattern che aggiungono complessità senza risolvere un problema reale. Non applicare un pattern “per ogni evenienza”. Segui YAGNI: introdurre un pattern solo quando un problema concreto lo giustifica.

I design pattern funzionano con la programmazione funzionale?

Sì. Molti pattern si mappano naturalmente su tecniche funzionali: Strategy può essere una funzione passata come parametro, Decorator può essere una funzione di ordine superiore. Conta il principio sottostante, non la forma OOP.


Presso Clean Code Guy, aiutiamo i team a costruire software che dura integrando principi fondamentali nel loro flusso di lavoro. Scopri come i nostri audit del codice e il refactoring pronti per l'AI possono far spedire il tuo team con fiducia su https://cleancodeguy.com.

\"1\".
W. G. Griswold et al., “A Formal Foundation for Design Pattern Abstraction and Reuse,” Proceedings of ECOOP ’93, University of California, San Diego. https://cseweb.ucsd.edu/~wgg/CSE210/ecoop93-patterns.pdf
\"2\".
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
\"3\".
Martin Fowler, “Singleton,” Bliki, discusses Singleton trade-offs and testing implications. https://martinfowler.com/bliki/Singleton.html
\"4\".
Robert C. Martin (Uncle Bob), “The SOLID Principles,” which explain Open/Closed and related design goals. https://8thlight.com/blog/uncle-bob/2012/08/13/the-s-o-l-i-d-principles.html
← 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.

Design Pattern OOP: Guida Pratica e Esempi TypeScript | Clean Code Guy