Continuando il discorso sui design pattern iniziato precedentemente, andiamo oggi a vedere un altro pattern molto utilizzato: il Factory Method Pattern.
Il GoF (Gang of Four Design Patterns) lo definisce così:
Definisce un’interfaccia per creare oggetti, ma lascia alle sottoclassi la
decisione del tipo di classe a istanziare.
Già dalla definizione, si può notare come il problema riguardante la parentela tra classi discusso per il Singleton non sia più presente. Infatti, si decide di ricorrere al Factory Method Pattern nel momento in cui non siamo in grado di conoscere a priori il tipo esatto dell'oggetto da creare oppure quando si vuole delegare ad altre entità il compito della creazione degli oggetti.
Consultando gran parte della letteratura, il pattern viene rappresentato con un diagramma UML simile a questo:

Elementi fondamentali del pattern
- Creator: ha il compito di dichiarare la Factory che si occuperà poi di restituire l'oggetto appropriato;
- ConcreteCreator: effettua l'override del metodo di Factory al fine di restituire l'implementazione adeguata dell'oggetto;
- Product: definisce l'interfaccia dell'oggetto che Factory deve ritornare;
- ConcreteProduct: implementa l'oggetto in base a Product;
La prima osservazione, se pur banale che va fatta è che l'implementazione è nettamente più complessa rispetto a quella quasi elementare del Singleton.
Qualche esempio
Partendo da un esempio piuttosto elementare, andiamo a definire una CatFactory:
//Product
interface Cat{
public void speak ();
}
Ogni elemento restituito dalla factory dovrà implementare questa interfaccia.
Andiamo ora a creare le classi concrete:
//ConcreteProduct
class Abissino implements Cat{
public void speak(){
System.out.println("Abissino");
}
}
class AmericanCurl implements Cat{
public void speak(){
System.out.println("American Curl");
}
}
class Asian implements Cat{
public void speak(){
System.out.println("Asian");
}
}
Ho deciso di implementare tutte le classi in maniera non pubblica, ipotizzandone il posizionamento in uno stesso file. Infatti, come sappiamo, Java vieta più di una classe pubblica in uno stesso file, in virtù del fatto che il file deve avere lo stesso nome della classe pubblica.
Il concetto fondamentale di questo pattern è proprio il fatto che ogni classe concreta sia un derivato di un tipo base. In questo caso specifico, ogni classe implementa Cat.
La classe Factory
L'elemento a cui è demandata la creazione degli oggetti è proprio la cosiddetta classe Factory, che avrà un metodo statico con al suo interno questa funzionalità.
Vediamo un'ipotetica struttura:
public class Factory{
public static Cat generateCat(String criteria){
if(criteria.equals("Abissino"))
return new Abissino();
if(criteria.equals("Curl"))
return new AmericanCurl();
if(criteria.equals("Asian"))
return new Asian();
return null;
}
}
Notiamo che al momento il codice della classe Factory è relativamente semplice, perchè accetta solo tre stringhe. In un'ipotetica situazione reale, il codice sarebbe ben più lungo e complesso ma questo è sufficiente a dare un'idea su come il pattern funzioni.
Il Factory Method Pattern presenta diversi vantaggi e svantaggi, tra cui:
- rappresenta un collegamento alle sottoclassi: tramite il creator è possibile scegliere dinamicamente quale classe concreta utilizzare senza alcun impatto sull'utilizzo dell'utente finale;
- collega gerarchie di classi parallelamente: i ConcreteCreator possono collegarsi con i ConcreteProduct e generare un collegamento parallelo tra gerarchie diverse;
Perchè utilizzare il Factory Method Pattern?
Come detto in precedenza, ci sono diversi casi in cui non possiamo conoscere a priori quale sarà il tipo concreto degli oggetti che verranno istanziati. In realtà la questione è ben più ampia.
Si potrebbe incorrere in casi nei quali conosciamo il tipo esatto degli oggetti, ma quest'ultimo potrà cambiare in futuro. Segue che il client sia libero dall'onere di conscere che tipo di oggetti istanziare e il pattern restituisce un oggetto astratto che si realizza poi mediante le classi ereditate dall'entità astratta. Spesso il client guida la creazione dell'oggetto, come nell'esempio mostrato prima, ma ignora i dettagli della sua costruzione.
Il pattern Iterator
Per quanto possa sembrare strano, il pattern Iterator è assimilabile alla famiglia dei Factory Method. Infatti Iterator ci da la possibilità di accedere sequanzialmente agli elementi di una lista, sollevando il chiamante dalla necessità di conoscere quali siano le classi istanziate.
Iterator è un'interfaccia che mette a disposizione tre metodi:
- boolean hasNext() che restituisce true se e solo se è ancora possibile effettuare un'iterazione;
- Object next() che restituisce l'elemento successivo a quello corrente;
- void remove() che si occupa di rimuovere l'elemento corrente dalla lista;
Vediamo un esempio:
import java.io.*;
import java.util.*;
class Test {
public static void main(String[] args)
{
ArrayList
list = new ArrayList
();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
list.add("5");
// Iterator per attraversare la lista
Iterator iterator = list.iterator();
System.out.println("List elements : ");
while (iterator.hasNext())
System.out.print(iterator.next() + " ");
System.out.println();
}
}
Otterremo come output:
List elements : A B C D E
Java mette a disposizione una classe ListIterator che permette di attraversare le collezioni di oggetti in entrambe le direzioni, quindi da testa a coda e da coda a testa.
Sintesi
Utilizziamo il Factory Method Pattern quando:
- Una classe non può conoscere in anticipo il tipo esatto degli oggetti da creare;
- La classe conosce il tipo esatto degli oggetti, ma ha bisogno di delegare ad un'entità esterna la loro creazione;
Partecipanti:
- Product: è l'interfaccia dell'oggetto creato dal Factory Method;
- ConcreteProduct: implementa Product;
- Creator: dichiara il Factory Method e restituisce un oggetto di tipo Product;
- ConcreteCreator: specifica il Factory Method e restituisce l'istanza corretta;
Segue che:
- Il codice ha un livello di flessibilità e riusabilità più alto;
- Un uso inappropriato può portare alla generazione di troppe classi;
Ponendo l'attenzione su alcuni dettagli implementativi:
- Creator può essere concreta o astratta, e creare una versione di default del Factory Method;
- Una Factory può costruire oggetti di tipo diverso mediante istruzioni di tipo if-then-else;
Tipicamente viene utilizzato per Logger e applicativi di questo genere.