
L’Abstract Factory è un design pattern creazionale che permette di istanziare un set di oggetti in relazione tra loro o dipendenti tra loro. Questo pattern astrae il processo di creazione di queste famiglie di oggetti, fornendo un’interfaccia per creare oggetti correlati senza specificare le loro classi concrete. In pratica, l’Abstract Factory permette di definire un’interfaccia per la creazione di tutti gli oggetti di una certa famiglia, garantendo che gli oggetti prodotti siano compatibili e consistenti tra loro.
Per illustrare meglio la funzione dell’Abstract Factory, si può pensare alla produzione di automobili, dove una fabbrica astratta definisce la creazione di parti correlate come motore, carrozzeria e interni, assicurando la compatibilità tra i componenti specifici di un determinato modello.
Scenario
Nella progettazione e sviluppo del software, ci si trova spesso nella situazione di dover gestire insiemi di oggetti che sono anche strettamente correlati o dipendenti tra loro. Affidare al codice client la creazione diretta di questi gruppi di oggetti può portare a problematiche di accoppiamento elevato, rendendo il sistema rigido e difficoltoso da adattare a nuove esigenze.
Consideriamo lo stesso esempio visto nel post sul Factory Method Pattern: un’applicazione per il controllo dei forni industriali in un’azienda specializzata nei trattamenti termici dei metalli pensata per gestire remotamente forni che impiegano differenti protocolli di comunicazione. Questi forni sono di costruttori e di modelli differenti. Alcuni usano un collegamento seriale per comunicare, altri usano protocolli di rete. Alcuni parlano con comandi semplici, altri richiedono messaggi più strutturati. Per comunicare con essi, l’applicazione ha bisogno di sapere come comunicare con loro, come scrivere i comandi giusti e come interpretare le risposte ricevute dai forni stessi.
Per ogni tipo di forno, quindi, serve un piccolo gruppo di oggetti che lavorano insieme: uno che gestisce la comunicazione, uno che prepara i comandi e uno che interpreta le risposte. Il problema nasce quando questi oggetti devono essere creati nel codice: se ogni volta è l’applicazione a dover decidere quale tipo di oggetto usare, il risultato è un sistema complicato, difficile da modificare o estendere.
Per risolvere questa complessità, entra in gioco l’Abstract Factory: invece di creare direttamente questi oggetti, l’applicazione si affida a una “fabbrica astratta” che definisce quali elementi servono per ciascun tipo di forno e li fornisce tutti insieme, già compatibili tra loro. In questo modo, il codice rimane semplice e concentrato solo sulla logica del processo, senza doversi preoccupare di come funzionano i dettagli.
Nel prossimo capitolo vedremo nel dettaglio come funziona questo pattern e come aiuta a mantenere il codice flessibile, scalabile e più semplice da testare.
Soluzione
Come anticipato sopra, si può utilizzare l’Abstract Factory per gestire situazioni in cui è necessario istanziare oggetti di tipo diversi ma consistenti tra loro. L’Abstract Factory astrae cioè la creazione di una famiglia di oggetti tra loro dipendenti o coerenti senza specificare le loro classi concrete. L’Abstract Factory definisce un’interfaccia dichiarando diversi metodi di creazione astratti (i Factory Method), ognuno responsabile della creazione di un differente tipo di oggetto all’interno della famiglia.
Il codice client lavora solo con le interfacce comuni fornite dall’Abstract Factory e dalle classi base, senza mai istanziare direttamente le implementazioni. In questo modo, il client resta indipendente dalle classi concrete, il sistema diventa più flessibile ed è possibile introdurre nuove famiglie di oggetti semplicemente implementando una nuova classe concreta derivata dalla factory astratta.
In pratica, il client chiede all’Abstract Factory un insieme coerente di oggetti, li usa tramite le loro interfacce e non deve preoccuparsi dei dettagli di creazione. Questo approccio riduce l’accoppiamento, migliora la manutenibilità e apre la strada a un sistema più estendibile e più semplice da testare.
Struttura
Il diagramma seguente mostra classi e interfacce coinvolte nell’Abstract Factory.

I soggetti coinvolti sono:
- Le interfacce AbstractProductA e AbstractProductB. Definiscono i tipi di oggetto ritornati dall’Abstract Factory. AbstractProduct potrebbero anche essere delle classi. Ciò non cambia nulla ai concetti e alla struttura del pattern.
- ConcreteProductA1, A2, B1, B2, etc. Sono classi concrete che implementano l’interfaccia Product.
- AbstractFactory. Definisce (cioè contiene) i singoli factory method, uno per ogni tipo di Product.
- Concrete Factory 1, 2, etc. Sono le classi che implementano i metodi dell’AbstractFactory. Ciascuna ConcreteFactory corrisponde a una famiglia specifica di prodotti e crea solo quei prodotti correlati.
Vantaggi
I vantaggi derivanti dall’uso dell’Abstract Factory design pattern sono:
- Indipendenza dalle classi concrete: il codice client interagisce con un’interfaccia comune, senza conoscere le classi concrete, rendendolo più flessibile e indipendente da implementazioni concrete.
- Aderenza all’Open-Closed Principle: È possibile aggiungere nuovi tipi di prodotti senza modificare il codice esistente.
- Separazione delle responsabilità: Il codice client si concentra sull’utilizzo dell’oggetto, non sulla sua creazione.
- Coerenza tra i prodotti: Garantisce che i prodotti creati dalla stessa fabbrica concreta siano compatibili tra loro. Una fabbrica concreta produce un’intera famiglia di oggetti correlati. In questo modo, si evita il rischio di mescolare prodotti incompatibili.
- Incoraggiamento dell’uso di interfacce: L’Abstract Factory, per sua natura, spinge il programmatore a lavorare con interfacce invece che con classi concrete.
- Cambio di configurazione a runtime: il client può selezionare quale fabbrica concreta usare (es. in base a un file di configurazione o alla scelta dell’utente), ottenendo una famiglia coerente di oggetti senza cambiare codice.
- Scalabilità: quando il numero di famiglie di prodotti cresce, l’Abstract Factory rende più semplice mantenere e organizzare le varianti.
- Riduzione della duplicazione di codice di creazione: centralizza in un punto la logica di istanziazione, evitando di ripetere costruttori o logica di setup sparsa nel client.
Svantaggi
- Maggiore complessità: l’introduzione di interfacce e sottoclassi può essere sovrabbondante per semplici scenari.
- Frammentazione: la logica di creazione è sparsa tra diverse classi.
- Difficoltà ad aggiungere nuovi prodotti nella famiglia: se serve introdurre un nuovo tipo di prodotto (oltre a quelli previsti dall’interfaccia della factory), è necessario modificare tutte le fabbriche concrete. Questo riduce la flessibilità in quel senso.
- Curva di apprendimento più ripida: per team poco abituati ai design pattern, l’Abstract Factory può sembrare complicato e non intuitivo all’inizio.
Quando usare l’Abstract Factory
L’ Abstract Factory è particolarmente utile nei seguenti scenari:
- Quando si deve lavorare con famiglie di oggetti correlati che devono essere usati insieme (es. pulsanti + finestre, parser + comandi + connessione).
- Quando si vuole garantire consistenza tra gli oggetti creati, evitando combinazioni incompatibili.
- Quando si vuole disaccoppiare il client dalla conoscenza delle classi concrete, lavorando solo con interfacce o classi astratte.
- Quando si vuole poter cambiare facilmente l’intera famiglia di prodotti (es. passare da implementazione Serial a TCP, o da tema chiaro a tema scuro) senza modificare il codice client.
Quando evitare l’Abstract Factory
In generale, conviene evitare l’Abstract Factory quando:
- Le classi concrete dei prodotti non cambiano mai. Se hai una sola implementazione per ogni interfaccia e non prevedi di aggiungerne altre, introdurre un’Abstract Factory aggiunge solo complessità inutile. In questi casi un’istanza diretta della classe concreta è più semplice e chiara.
- Il problema è semplice e il pattern aggiunge solo prolissità. Se non ci sono famiglie di oggetti o rischi di incoerenza, l’Abstract Factory rischia di appesantire il design senza portare benefici reali.
- Ci sono vincoli di performance stringenti. L’indirezione introdotta dal pattern comporta un overhead minimo, ma in applicazioni con requisiti di latenza molto stringenti anche questa differenza può avere un peso. Nella maggior parte dei casi, però, si tratta di una micro-ottimizzazione trascurabile.
Esempio reale
Riprendiamo lo scenario dei forni industriali visto sopra: abbiamo modelli con protocolli di comunicazione diversi, ma comportamenti simili. Alcuni inviano e ricevono messaggi semplici, altri richiedono messaggi più strutturati. Per comunicare con essi, l’applicazione ha bisogno di sapere come comunicare con loro, come scrivere i comandi giusti e come interpretare le risposte ricevute dai forni stessi.
Vediamo ora come applicare l’Abstract Factory per gestire questa variabilità nel codice. Nel dettaglio, l’applicazione deve:
- Gestire forni di costruttori diversi, che utilizzano connessioni differenti: seriale e TCP/IP.
- Supportare messaggi con strutture differenti: XML e testo non formattato.
- Implementare la stessa logica comune per tutti i forni: apertura della connessione, invio del comando per il cambio temperatura, invio del comando per avviare il forno, lettura della temperatura corrente e chiusura della connessione.
- Permettere di aggiungere nuovi tipi di forno senza modificare il codice client esistente, favorendo al contempo il riuso del codice già sviluppato.
In altre parole, ogni forno non ha bisogno di un singolo oggetto, ma di un piccolo gruppo di oggetti che lavorano insieme: uno per la connessione, uno per la creazione dei comandi e uno per l’interpretazione delle risposte.
Questi oggetti devono essere coerenti tra loro: un forno che parla tramite connessione seriale e usa messaggi in testo semplice non può essere gestito con un parser XML, e viceversa.
È qui che entra in gioco l’Abstract Factory: invece di lasciare al codice client la responsabilità di decidere quali classi concrete usare, deleghiamo la creazione di questo gruppo di oggetti a una factory specializzata. In questo modo il client lavora solo con interfacce comuni e rimane del tutto indipendente dai dettagli implementativi.

Nel dettaglio, abbiamo:
- L’interfaccia IConnection, che rappresenta la connessione verso il forno (seriale, TCP, ecc.). Espone i metodi Open(), Send(), Read() e Close(), indipendentemente dal tipo di connessione utilizzato. Rispetto alla struttura generale dell’Abstract Factory, IConnection corrisponde a un AbstractProduct.
- L’interfaccia ICommandBuilder, che si occupa della creazione dei comandi da inviare al forno (BuildSetTemperature(), BuildStartCycle(), ecc.). Anche questa interfaccia è un AbstractProduct.
- L’interfaccia IResponseParser, che interpreta le risposte ricevute dal forno (ParseTemperature() e ParseStatus(), ecc.). Rappresenta anch’essa un AbstractProduct.
- L’interfaccia IOvenFactory dichiara i metodi CreateConnection(), CreateCommandBuilder e CreateResponseParser. Rispetto alla struttura generale del pattern, questa corrisponde alla classe AbstractFactory.
- Le classi LegacyOvenFactory e ModernOvenFactory, che implementano IOvenFactory creando famiglie coerenti di oggetti. Ad esempio, LegacyOvenFactory istanzia una SerialConnection, un PlainTextCommandBuilder e un PlainTextResponseParser. Rispetto alla struttura generale, corrispondono a ConcreteFactory 1 e ConcreteFactory 2.
- Le classi SerialConnection e TcpConnection, che implementano IConnection fornendo la logica specifica per l’apertura, la scrittura e la chiusura della comunicazione. Queste classi sono i ConcreteProduct associati a IConnection.
- Le classi PlainTextCommandBuilder e XmlCommandBuilder, che implementano ICommandBuilder e producono rispettivamente messaggi in testo semplice e in XML. Rappresentano i ConcreteProduct associati a ICommandBuilder.
- Le classi PlainTextResponseParser e XmlResponseParser, che implementano IResponseParser interpretando rispettivamente risposte testuali e risposte XML. Rappresentano i ConcreteProduct associati a IResponseParser.
Grazie a questa organizzazione, l’aggiunta di un nuovo tipo di forno non richiede modifiche alla logica principale, ma solo l’implementazione di una nuova factory e dei relativi prodotti concreti. In questo modo il sistema risulta estensibile, ordinato, più facile da mantenere e consente anche di riutilizzare meglio il codice già sviluppato.
L’implementazione completa dell’esempio è disponibile qui: LabVIEW e C#.
Conclusioni
L’Abstract Factory è un’evoluzione naturale rispetto al Factory Method. Se quest’ultimo ci aiuta a gestire la creazione di un singolo oggetto, l’Abstract Factory ci permette di creare intere famiglie di oggetti coerenti tra loro.
Nel nostro esempio dei forni industriali, questo approccio garantisce che la IConnection, l’ICommandBuilder e l’IResponseParser siano sempre compatibili, evitando combinazioni errate come una connessione seriale con un parser XML.
Un altro vantaggio importante è il riuso: le stesse implementazioni di connessione, builder e parser possono essere ricombinate per supportare nuovi modelli di forno semplicemente aggiungendo una nuova factory concreta, senza modificare il codice client esistente.
Ad esempio, immaginiamo che un produttore aggiorni il firmware dei propri forni, mantenendo la comunicazione via TCP/IP ma sostituendo i messaggi XML con messaggi JSON, più leggeri e diffusi. In questo caso non serve modificare né il client né la parte di connessione: possiamo continuare a usare la classe TcpConnection già sviluppata, creando soltanto un nuovo JsonCommandBuilder e un JsonResponseParser. L’unica aggiunta richiesta è una nuova factory concreta, incaricata di combinare questi oggetti.
Grazie a questa organizzazione, il passaggio a un nuovo formato di messaggi diventa molto meno invasivo e non intacca il resto del sistema, che rimane stabile e riutilizza gran parte del codice già scritto.
In sintesi, l’Abstract Factory ci aiuta a mantenere il codice più flessibile, ordinato e resistente ai cambiamenti, rendendo più semplice l’estensione del sistema e il riutilizzo del codice già sviluppato.
Per chi volesse approfondire, l’implementazione completa è disponibile qui: LabVIEW e C#.