L'Abstract Factory è uno dei principali pattern creazionali il cui scopo è quello di fornire un'interfaccia per creare famiglie di oggetti interconnessi fra loro, in modo che non ci sia necessità di specificare i nomi delle classi concrete all'interno del proprio codice. In questo modo si facilita la creazione di un sistema indipendente dall'implementazione degli oggetti concreti, infatti, l'utilizzatore (Client) conosce solo l'interfaccia per creare le famiglie di prodotti ma non la sua implementazione concreta.
L'Abstract Factory è costituito da 5 elementi:
- AbstractFactory: interfaccia che definisce i metodi mediante i quali sarà possibile ottenere gli AbstractProduct;
- ConcreteFactory: nel sistema possono essere create n ConcreteFactory, ciascuna delle quali dovrà implementare l'interfaccia AbstractFactory e quindi implementare i metodi mediante i quali sarà possibile ottenere i ConcreteProduct. Per garantire che nel sistema esiste un'unica istanza di ciascuna ConcreteFactory, è buona norma definire ciascuna di esse come Singleton;
- AbstractProduct: interfaccia che definisce la struttura base dei prodotti che la factory può instanziare;
- ConcreteProduct: nel sistema possono essere creati n ConcreteProduct ciascuno dei quali dovrà implementare l'interfaccia AbstractProduct;
- Client: classe che utilizza l'AbstractFactory per generare i prodotti concreti all'interno del sistema;
Naturalmente il Pattern offre dei vantaggi ma anche degli svantaggi.
I vantaggi principali sono i seguenti:
- il pattern permette di isolare i punti di creazione degli oggetti di una classe. La Factory incapsula tutti i meccanismi di creazione. Le classi concrete si trovano specificate soltanto all'interno della factory, il resto si affida alla definizione delle interfacce. Il client può ottenere l'istanza di un prodotto concreto esclusivamente mediante l'interfaccia AbstractFactory;
- il client può cambiare la famiglia di prodotti utilizzata semplicemente cambiando la linea di codice che riguarda la creazione della factory;
- promuove la consistenza tra i prodotti che sono organizzati in famiglie. I prodotti di una famiglia sono coordinati per lavorare insieme.
Lo svantaggio principlamente è uno: aggiungere un nuovo prodotto richiede la modifica dell'interfaccia AbstractFactory ma la modifica si ripercuote a cascata nelle factory concrete e in tutte le sottoclassi, rendendo laboriosa l'operazione.
Il pattern AbstractFactory può essere utilizzato in un gran numero di situazioni reali. Per cercare di acquisire una certa dimestichezza con questo pattern e capirne meglio il funzionamento illustriamo un esempio di utilizzo in un contesto reale. Il nostro esempio simula la renderizzazione di una figura geometrica. Per semplicità implementiamo un'unica ConcreteFactory e soltanto due prodotti che non fanno altro che stampare una stringa a video.
Analizziamo ora in dettaglio le singole interfacce/classi necessarie per implementare il pattern. Partiamo da FiguraFactory che rappresenta la nostra AbstractFactory. Definisce i metodi che ciascuna ConcreteFactory deve implementare: createRettangolo()
e createCerchio()
. Entrambi i metodi restituiscono un'istanza della classe Figura.
Listato 1. FiguraFactory
public abstract class FiguraFactory {
public abstract Figura createRettangolo();
public abstract Figura createCerchio();
}
MiaFiguraFactory rappresenta la nostra unica ConcreteFactory. Tale classe deve estendere la classe astratta FiguraFactory e quindi, implementare i due metodi definiti: createRettangolo()
e createCerchio()
che restituiscono rispettivamente, un'istanza della classe MioRettangolo e un'istanza della classe MioCerchio.
Listato 2. MiaFiguraFactory
public class MiaFiguraFactory extends FiguraFactory {
public Figura createCerchio() {
return new MioCerchio();
}
public Figura createRettangolo() {
return new MioRettangolo();
}
}
Figura rappresenta il nostro AbstractProduct che definisce la struttura base di un generico prodotto della famiglia. Per semplicità definiamo esclusivamente il metodo disegna()
.
Listato 3. Classe Figura
public abstract class Figura {
public abstract void disegna();
}
MioCerchio e MioRettangolo sono i nostri ConcreteProduct che estendono la classe astratta Figura. Per semplicità i due metodi stampano soltanto una stringa a video.
Listato 4. ConcreteProduct: MioCerchio e MioRettangolo
public class MioCerchio extends Figura {
public void disegna() {
System.out.println("Io sono il cerchio");
}
}
public class MioRettangolo extends Figura {
public void disegna() {
System.out.println("Io sono il rettangolo");
}
}
Test rappresenta il nostro Client, cioè la classe che utilizza l'AbstractFactory per ottenere un'istanza dei nostri prodotti concreti. È possibile vedere come il Client non deve sapere nulla delle classi concrete che deve utilizzare.
Listato 5. Client, Classe Test
public class Test {
public static void main(String[] args) {
FiguraFactory factory = new MiaFiguraFactory();
Figura c = factory.createCerchio();
Figura r = factory.createRettangolo();
c.disegna();
r.disegna();
}
}