Factory Method e Abstract Factory: differenze e analogie

In questo post cercherò di spiegare le differenze e le analogie tra due dei più noti design pattern creazionali: Factory Method e Abstract Factory.

Prima di entrare nello specifico di ciascun pattern rinfreschiamo la definizione di design pattern.

Un design pattern è una soluzione pronta all’uso e adattabile per specifici problemi applicativi.
Quando si parla di un pattern è necessario sempre tenere a mente 4 cose:

  1. il nome del pattern;
  2. il problema che risolve;
  3. la soluzione al problema proposta;
  4. l’implementazione.

L’esigenza generale a cui mirano i pattern è quella di isolare gli aspetti che cambiano per rispettare il principio open/closed. In breve, seguire il principio open/closed significa essere aperti ai cambiamenti (openness) con l’aggiunta di nuove entità lasciando invariate le entità preesistenti (closeness). Senza voler fare filosofia ciò si traduce nella possibilità di estendere, ovvero modificare, il comportamento di un’entità senza modificare l’entità stessa, quindi non mettendo mano al codice che definisce quella entità. Per fare ciò bisogna sfruttare tutte le opportunità della programmazione agli oggetti, in primis ereditarietà e polimorfismo.
Quando parliamo di entità software intendiamo classi, metodi, funzioni, moduli, package, etc.

Factory Method

Nome: Factory method.
Problema: Creare oggetti senza dover specificare la classe esatta dell’oggetto che verrà creato.
Soluzione:

Factory Method

Di norma la soluzione è rappresentata sui libri in UML, perché è più facile capire tutto dal diagramma delle classi. Nel post trascuro volutamente l’implementazione.

Prima di andare avanti facciamo alcune considerazioni:

  • AbstractProduct “is-a” IProduct;
  • ConcreteProduct1 “is-a” AbstractProduct;
  • ConcreteProduct2 “is-a” AbstractProduct;
  • ConcreteProduct1 “is-a” IProduct;
  • ConcreteProduct2 “is-a” IProduct.

Ritorniamo al diagramma delle classi di prima e ripuliamolo lasciando solo questi quattro componenti.

Factory Method (Zoom 1)

Possiamo osservare che:

  • IProduct è una interfaccia;
  • AbstractProduct è una classe astratta;
  • ConcreteProduct1 e ConcreteProduct2 sono classi concrete. Nella classe astratta AbstractProduct possiamo iniziare a definire quei comportamenti comuni degli oggetti di tipo IProduct, ma la classe rimane astratta poiché non abbiamo tutti gli elementi per istanziare un oggetto concreto. Ora guardiamo la seconda parte del class diagram iniziale.

Factory Method (Zoom 2)

Client è la classe che ha necessità di avere un oggetto di tipo IProduct e quindi è un’entità che richiede la creazione di un oggetto di tipo IProduct. Il Client è il “creator” ed è una classe astratta perché il Client ha bisogno di un oggetto di tipo IProduct concreto ma non sa quale di preciso. Anziché cablare all’interno del Client la dipendenza con uno specifico prodotto (ConcreteProduct1, ConcreteProduct2, etc.) si estende Client definendo ConcreteCreatorX, il quale sa come e quale specifico oggetto di tipo IProduct deve creare. Ad esempio ConcreteCreator1 crea oggetti di tipo IProduct che sono istanze concrete di ConcreteProduct1. Avrò quindi tanti ConcreteCreatorX per ogni IProduct che voglio utilizzare.
In altre parole l’onere di istanziare un particolare oggetto di tipo IProduct viene spostata nell’erede concreto di Client. Questo modo di fare corrisponde alla massima di Erich Gamma: “Program to an interface, not an implementation”.

Per chi non lo sapesse Erich Gamma insieme a Richard Helm, Ralph Johnson e John Vlissides è uno degli autori del libro: “Design Patterns: Elements of Reusable Object-Oriented Software”, una raccolta dei 23 design pattern fondamentali tra cui Factory Method e Abstract Factory.

Riepilogando parliamo di factory method quando:

  • Una classe A (Client) non deve conoscere quale classe di un tipo B (IProduct) creare;
  • La sottoclasse di A (ConcreteCreatorX) specifica quale oggetto deve essere creato (ConcreteProductX);
  • La classe padre A (Client) sposta l’onere della creazione alle sue sottoclassi (ConcreteCreatorX).

Abstract Factory

Nome: Abstract Factory
Problema: Creare famiglie di oggetti tra di loro correlate senza dover specificare la classe esatta dell’oggetto che verrà creato
Soluzione:

Abstract Factory

Abstract Factory è una generalizzazione del Factory Method.

Senza fare giri di parole, guardiamo IFactory e ConcreteFactoryX. IFactory è l’interfaccia che definisce una serie di Factory Method, ciò metodi che costruiscono oggetti. IFactory definisce il concetto di “famiglia” senza specificare di quale famiglia stiamo parlando. ConcreteFactoryX è una famiglia (“is-a” IFactory) perché implementa l’interfaccia IFactory ma è una specifica famiglia, non una in generale.
Il Client in questo caso usa un erede di IFactory (quindi ConcreteFactoryX) per ottenere un prodotto di tipo IProductA e un prodotto IProductB. Si noti che IProductA e IProductB non sono classi “parenti” (non hanno un erede in comune, però potrebbero) ma sono semplicemente correlati.

Ecco un riepilogo delle differenze tra Factory Method e Abstract Factory.

Factory Method Abstract Factory
E’ un metodo. E’ un’interfaccia per creare oggetti correlati. Abstract factory è implementato spesso (non per forza) attraverso factory method.
Gli oggetti vengono creati attraverso l’ereditarietà (vertical indirection). Gli oggetti vengono creati attraverso la composizione (horizontal indirection).
Crea un solo tipo di oggetto. Crea famiglie di oggetti.
Per creare nuovi oggetti dello stesso tipo è sufficiente creare una sottoclasse del Client. Per creare nuovi oggetti della stessa famiglia è necessario ridefinire l’interfaccia di IFactory.
Per creare nuove famiglie di oggetti è necessario creare una sottoclasse di IFactory.

Questions?

Have a question about this post or anything else? Ask away on Twitter or in my AMA repo.

Leave a Reply

Your email address will not be published. Required fields are marked *