← Back to list

Abstract Factory Pattern (Creational Design Patterns in Java, Serie per Dev Italiani #5)

Introduzione

Leonardo Percoco in Coffee☕ And Code💚 · 2026-05-16 10:01 · 82 claps · 4.3 min read
#abstract-factory-pattern #software-engineering #design-patterns-in-java #uml-diagrams #desgin-patterns
Open on Medium ↗
Wiki topics: TLS · Design Tools & Workflow

Abstract Factory Pattern (Creational Design Patterns in Java, Serie per Dev Italiani #5)

Introduzione

L’Abstract Factory Pattern è un creational pattern che permette di creare famiglie di oggetti correlati senza dipendere dalle loro classi concrete.

Fornisce un’interfaccia comune per generare gruppi di oggetti coerenti appartenenti alla stessa famiglia.

A differenza del Simple Factory e del Factory Method, che si occupano della creazione di singoli oggetti, l’Abstract Factory opera a un livello di astrazione superiore, gestendo intere famiglie di prodotti correlati.

Il Problema alla Base dell’Abstract Factory

In molti sistemi software non è sufficiente creare singoli oggetti, ma interi gruppi di componenti correlati che devono collaborare in modo coerente.

Il problema nasce quando la loro creazione è distribuita nel codice o gestita in modo non strutturato: diventa difficile garantire compatibilità e coerenza tra le varie componenti.

L’Abstract Factory risolve questo problema fornendo un meccanismo per creare famiglie di oggetti correlati senza esporre le classi concrete e senza vincolare il client alle specifiche implementazioni.

Struttura del pattern

  • AbstractFactory: Definisce l’interfaccia per la creazione delle famiglie di prodotti correlati, dichiarando i metodi necessari per istanziare ciascun tipo di prodotto senza specificarne le classi concrete.
  • ConcreteFactory1: Implementa l’interfaccia AbstractFactory creando una specifica famiglia di prodotti concreti coerenti tra loro.
  • ConcreteFactory2: Implementa l’interfaccia AbstractFactory creando una diversa famiglia di prodotti concreti coerenti tra loro.
  • AbstractProductA: Definisce l’interfaccia per una specifica tipologia di prodotto, mentre il tipo concreto da utilizzare viene determinato dalla factory che lo istanzia.
  • AbstractProductB: Definisce l’interfaccia per una seconda tipologia di prodotto, mentre il tipo concreto da utilizzare viene determinato dalla factory che lo istanzia.
  • ConcreteProductA1: Implementa l’interfaccia AbstractProductA e rappresenta la prima variante concreta del prodotto A, appartenente alla famiglia creata da ConcreteFactory1.
  • ConcreteProductB1: Implementa l’interfaccia AbstractProductB e rappresenta la prima variante concreta del prodotto B, appartenente alla famiglia creata da ConcreteFactory1.
  • ConcreteProductA2: Implementa l’interfaccia AbstractProductA e rappresenta la seconda variante concreta del prodotto A, appartenente alla famiglia creata da ConcreteFactory2.
  • ConcreteProductB2: Implementa l’interfaccia AbstractProductB e rappresenta la seconda variante concreta del prodotto B, appartenente alla famiglia creata da ConcreteFactory2.
  • Client: Utilizza le astrazioni del pattern (AbstractFactory e AbstractProduct) per creare e lavorare con famiglie di oggetti correlati senza dipendere dalle loro implementazioni concrete.

Figura 1 - UML dell’Abstract Factory Pattern (Clicca per ingrandire)

Figura 1 - UML dell’Abstract Factory Pattern (Clicca per ingrandire)

Esempio Pratico: Implementazione dell’Abstract Factory Pattern

Quando un’applicazione deve integrare più provider di pagamento, come Stripe e PayPal, il rischio è quello di spargere nel codice controlli condizionali, dipendenze concrete e logiche specifiche per ogni provider. Questo approccio rende il sistema più difficile da mantenere, estendere e testare nel tempo.

L’Abstract Factory Pattern aiuta a risolvere questo problema introducendo un livello di astrazione nella creazione degli oggetti. Invece di istanziare direttamente classi concrete come StripePaymentRequest, StripePaymentResponse, PayPalPaymentRequest o PayPalPaymentResponse, il client lavora esclusivamente con interfacce astratte come PaymentRequest e PaymentResponse, delegando la creazione degli oggetti a factory specializzate.

Nel nostro esempio, ogni provider di pagamento espone una propria famiglia coerente di oggetti, come richieste e risposte di pagamento. La StripePaymentProviderFactory crea esclusivamente componenti compatibili con Stripe, mentre la PayPalPaymentProviderFactory produce gli equivalenti per PayPal. In questo modo il client rimane disaccoppiato dalle implementazioni concrete e il sistema può essere esteso con nuovi provider senza modificare il codice esistente.

Il diagramma UML seguente mostra come l’Abstract Factory Pattern organizza le factory e i prodotti del nostro esempio, mantenendo separata la logica di utilizzo dalla logica di creazione degli oggetti.

Figura 2 - Abstract Factory applicato al sistema di pagamento (Ingrandisci)

Figura 2 - Abstract Factory applicato al sistema di pagamento (Ingrandisci)

Implementazione in Java

Vediamo ora come la struttura descritta nel diagramma UML si traduce in codice Java. L’obiettivo è mantenere lo stesso livello di astrazione visto nel modello, utilizzando interfacce per i prodotti e factory concrete per la loro creazione. In questo modo il client rimane completamente indipendente dalle implementazioni specifiche dei vari provider di pagamento.

Definiamo l’Abstract Factory:

public interface PaymentProviderFactory {
    PaymentRequest createPaymentRequest();
    PaymentResponse createPaymentResponse();
}

Implementiamo la factory concreta responsabile della creazione degli oggetti di pagamento compatibili con Stripe.

public class StripePaymentProviderFactory implements PaymentProviderFactory {
    @Override
    public PaymentRequest createPaymentRequest() {
        return new StripePaymentRequest();
    }

    @Override
    public PaymentResponse createPaymentResponse() {
        return new StripePaymentResponse();
    }
}

Implementiamo la factory concreta responsabile della creazione degli oggetti di pagamento compatibili con PayPal.

public class PayPalPaymentProviderFactory implements PaymentProviderFactory {
    @Override
    public PaymentRequest createPaymentRequest() {
        return new PayPalPaymentRequest();
    }

    @Override
    public PaymentResponse createPaymentResponse() {
        return new PayPalPaymentResponse();
    }
}

Definiamo il prodotto astratto relativo alla richiesta di pagamento.

public interface PaymentRequest {
    void process();
}

Definiamo il prodotto astratto relativo alla risposta di pagamento.

public interface PaymentResponse {
    void handle();
}

Implementiamo la richiesta di pagamento concreta compatibile con Stripe.

public class StripePaymentRequest implements PaymentRequest {
    @Override
    public void process() {
        System.out.println("Processing Stripe payment request");
    }
}

Implementiamo la richiesta di pagamento concreta compatibile con PayPal.

public class PayPalPaymentRequest implements PaymentRequest {

    @Override
    public void process() {
        System.out.println("Processing PayPal payment request");
    }
}

Implementiamo la risposta di pagamento concreta gestita dal provider Stripe.

public class StripePaymentResponse implements PaymentResponse {
    @Override
    public void handle() {
        System.out.println("Handling Stripe payment response");
    }
}

Implementiamo la risposta di pagamento concreta gestita dal provider PayPal.

public class PayPalPaymentResponse implements PaymentResponse {

    @Override
    public void handle() {
        System.out.println("Handling PayPal payment response");
    }
}

Il client utilizza la factory per creare dinamicamente i componenti di pagamento senza conoscere le implementazioni concrete dei singoli provider.

public class Client {
    private final PaymentRequest request;
    private final PaymentResponse response;

    public Client(PaymentProviderFactory factory) {
        this.request = factory.createPaymentRequest();
        this.response = factory.createPaymentResponse();
    }

    public void execute() {
        request.process();
        response.handle();
    }

    public static void main(String[] args) {
        PaymentProviderFactory factory = new StripePaymentProviderFactory();

        Client client = new Client(factory);
        client.execute();
    }
}

Conclusione

L’Abstract Factory Pattern risulta particolarmente utile quando un sistema deve gestire famiglie di oggetti correlati senza vincolare il client a implementazioni concrete.

Nel nostro esempio, questo approccio separa la creazione dei componenti di pagamento (Stripe e PayPal) dalla loro logica di utilizzo, riducendo l’accoppiamento e semplificando l’estensione del sistema.

Il vero valore emerge quando aumentano le varianti: aggiungere un nuovo provider significa introdurre una nuova factory e un nuovo insieme di prodotti, senza modificare il codice esistente. Questo è uno dei principi fondamentali della progettazione software: estendere senza rompere ciò che già funziona.


메타데이터
post_id
24fd002ead29
slug
creational-design-patterns-in-java-serie-per-dev-italiani-5-abstract-factory-pattern-24fd002ead29
url
https://medium.com/techtrends-digest/creational-design-patterns-in-java-serie-per-dev-italiani-5-abstract-factory-pattern-24fd002ead29
canonical_url
https://medium.com/techtrends-digest/creational-design-patterns-in-java-serie-per-dev-italiani-5-abstract-factory-pattern-24fd002ead29
author_url
https://medium.com/@leonardo.percoco_65603
status
ok
fetched_at
2026-06-27 10:07:59