December 20, 2025 (4mo ago)

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.

← Back to blog
Cover Image for 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.

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 sfide di progettazione ricorrenti. Non sono codice da copiare e incollare; sono modelli adattabili che ti aiutano a strutturare classi e oggetti in modo che il tuo codice sia più facile da mantenere, estendere e testare.

Una solida comprensione dei pattern comuni—creazionali, strutturali e comportamentali—ti permette di comunicare chiaramente le intenzioni di progettazione con i colleghi, rifattorizzare codice legacy in modo sicuro e scegliere lo strumento giusto per ogni problema architetturale.

Cosa sono i design pattern nella programmazione orientata agli oggetti

Hai mai provato a costruire qualcosa di complesso senza un piano? Programmare senza design pattern può portare a una base di codice aggrovigliata, difficile da scalare e mantenere. I design pattern sono come il ricettario di uno chef esperto: ricette testate che adatti per produrre risultati coerenti e manutenibili.

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

Il linguaggio condiviso degli sviluppatori

Uno dei benefici più preziosi dei pattern è un vocabolario condiviso. Pronuncia "Factory" o "Singleton" e gli sviluppatori esperti comprendono immediatamente l'intento e la struttura ad alto livello, il che accelera la collaborazione e le decisioni architetturali.

“Design Patterns: Elements of Reusable Object-Oriented Software,” pubblicato nel 1994 da Erich Gamma, Richard Helm, Ralph Johnson e John Vlissides, ha catalogato 23 pattern fondamentali che rimangono letture essenziali per i praticanti OOP2. Questi pattern si basano su lavori accademici precedenti che mostrano benefici misurabili in termini di produttività e riuso nei progetti di grandi dimensioni1.

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

Prendere confidenza con i principi di base dell'OOP come il polimorfismo e l'ereditarietà ti 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 ti aiuta a scegliere rapidamente l'approccio giusto per un problema.

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ù grandi e flessibiliAdapter, Decorator, Facade, Composite
ComportamentaliDefinire come gli oggetti interagiscono e comunicanoObserver, Strategy, Command, Iterator

Pattern creazionali: gli 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 ti permettono di gestire l'instanziazione (per esempio, facendo rispettare un'istanza singola con Singleton).

Pattern strutturali: la colla architettonica

I pattern strutturali ti aiutano ad assemblare gli oggetti in sistemi più grandi e robusti. Semplificano le relazioni tra i componenti così puoi cambiare parti senza rompere l'intero sistema. Pattern come Adapter permettono a interfacce incompatibili di cooperare, cosa indispensabile quando si integrano librerie di terze parti.

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

Pattern comportamentali: i direttori della comunicazione

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

Gestendo con cura i percorsi di comunicazione, riduci le dipendenze e rendi la base di codice più facile da comprendere e mantenere.

Creare oggetti con i pattern creazionali

I pattern creazionali introducono 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 vera

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 dipendenze3.

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ù facile 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 scelte pratiche 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 in modo che quando un Subject cambia stato, gli Observer vengano 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 che notificano.

Il pattern Strategy: incapsulare algoritmi

Strategy ti permette di sostituire algoritmi a runtime, evitando grandi blocchi condizionali. Si allinea al principio Open/Closed permettendo nuove strategie senza modificare il codice esistente.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 accaparra responsabilità e viola il principio della singola responsabilità. Cerca odori di codice—metodi lunghi, dipendenze eccessive o classi mostruose—e affrontali con un refactoring disciplinato.

Il refactoring riguarda il miglioramento della struttura interna senza cambiare il comportamento esterno. È il modo in cui domi i sistemi legacy in sicurezza.

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 per le strategie. Sostituisci il condizionale con una singola chiamata al metodo della strategia selezionata. Questo cambiamento migliora estensibilità, testabilità e l'allineamento con i principi SOLID4.

Risposte alle tue domande sui design pattern OOP

Quanti design pattern dovrei imparare?

Concentrati prima su 5–7 pattern essenziali: Singleton, Factory, Adapter, Decorator, Observer e Strategy. Impara il problema che ogni pattern risolve; una volta interiorizzato quello, imparare pattern aggiuntivi diventa molto più semplice.

Quando dovrei evitare di usare un design pattern?

Evita pattern che aggiungono complessità senza risolvere un problema reale. Non introdurre un pattern "per ogni evenienza". Segui YAGNI: aggiungi pattern quando un problema chiaro e presente lo giustifica.

Posso usare i design pattern con la programmazione funzionale?

Sì. Molti pattern si mappano naturalmente su tecniche funzionali. Strategy può essere parametri funzione, e Decorator può essere funzione di ordine superiore. La chiave è applicare il principio sottostante piuttosto che copiare rigidamente le forme 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.