Pattern Creazionali – Abstract Factory Pattern
Factory Pattern
La modularizzazione di un’applicazione (di un’entità oppure un modulo) è senza ombra di dubbio uno dei temi più trattati in ingegneria del software. In questo contesto di generalizzazione i pattern creazionali ricoprono sempre più un ruolo chiave. Sicuramente, i pattern creazionali per antonomasia sono rappresentati dalle factory.
Formalmente, una Factory è un’astrazione del processo di costruzione di una classe. L’obiettivo di una classe Factory è proprio quello di fabbricare altri oggetti (da factory che in inglese significa appunto fabbrica) rendendo disponibile all’esterno un’interfaccia per il meccanismo di creazione. I pattern creazionali di tipo Factory che tratteremo in questo e nel prossimo articolo sono l’Abstract Factory e il Factory Method. Sebbene esista una distinzione dal punto di vista formale di questi due pattern, in moltissime implementazioni si tende a mescolarne i concetti, ritrovando ad esempio un factory method implementato all’interno di un Abstract Factory. Questo ha creato sicuramente un velo di ambiguità attorno al concetto, rendendone talvolta aleatoria l’applicazione. Analizzeremo le implementazioni dei due pattern per scoprire come queste situazioni possano essere facilmente riconosciute.
Abstract Factory Pattern
Nel processo di creazione di un oggetto c’è molto più del semplice utilizzo dell’operatore new. Attraverso l’applicazione di questo pattern si può evidenziare come l’attività di instanziazione non dovrebbe essere svolta in pubblico, poiché questo comporterebbe in seguito dei coupling problem (letteralmente, problemi di accoppiamento).
Come vedrai, in questo e negli articoli a seguire, attraverso le factory astratte si provvederà a separare le funzionalità di creazione e di funzionamento di una classe, isolando le problematiche di implementazione relative.
Anche qui, ho ritenuto significativo trattare questo pattern per i seguenti motivi:
- Fondamentale. Si tratta del pattern creazionale forse più usato tra i più usati;
- Frequente. Viene usato in moltissimi contesti. Nella maggioranza dei package PHP è sempre possibile individuare un metodo factory.
- In Laravel 5. Molte classi all’interno del core di Laravel seguono i principi base del pattern;
- SOLID Friendly Tramite il suo utilizzo viene talvolta facilitato il rispetto dei principi SOLID. In particolar modo verrà rispettato il principio di Inversione di Dipendenza (Dependency Inversion Principle), per cui i componenti ad alto livello dell’applicazione non dovrebbero dipendere direttamente dai componenti a basso livello, ma da una loro astrazione.
Classificazione di Appartenenza
Si tratta di un pattern di tipo creazionale di oggetti. In questo schema, diventa fondamentale la comprensione di come le classi vengono fra loro combinate al fine di realizzare la factory. Presta pertanto particolare attenzione ai componenti che ne costituiscono l’implementazione generica e come questo viene poi tradotto in un’implementazione concreta. Per facilitarti la comprensione, alla fine dell’articolo, vedrai affiancati gli schemi UML generico e concreto.
Obiettivo
Fornire un’interfaccia per la creazione di famiglie di oggetti in qualche modo relazionati o dipendenti fra loro senza specificare concretamente le classi di appartenenza, da qui il nome di fabbrica astratta. I client che effettuano una richiesta di creazione dell’oggetto non hanno necessità di conoscere il nome concreto delle classi di cui richiedono l’istanza, ma usano una classe factory concreta, definita a partire da una classe astratta, a cui fanno riferimento famiglie di oggetti.
Applicabilità
L’Abstract Factory pattern viene utilizzato in genere quando un sistema vuole essere indipendente dagli oggetti che andrà a creare, favorendo la creazione di strutture correlate ad una certa famiglia di oggetti. Più in dettaglio, il pattern può essere usato quando:
- un sistema vuole essere indipendente dai singoli oggetti che verranno creati e composti fra loro
- un sistema viene progettato per lavorare con famiglie di prodotti
- una famiglia di prodotti è progettata per non lavorare contemporaneamente con altre famiglie, per cui un prodotto concreto può cooperare solo con i prodotti della famiglia di appartenenza. In questo caso un sistema ha la necessità di riferirsi a famiglie di oggetti e tali oggetti comunicheranno fra loro ma non con famiglie di oggetti differenti
- si vuole definire una libreria di riferimento per la creazione di una certa famiglia di oggetti, per cui sia significativo focalizzarsi sull’interfaccia più che sull’implementazione concreta.
Partecipanti
- AbstractFactory (WidgetFactory):
Prepara una classe astratta con metodi per la creazione di prodotti astratti. - ConcreteFactory (BootstrapWidgetFactory, FoundationWidgetFactory):
Estendono la classe astratta, implementandone concretamente i metodi di
istanziazione dei prodotti. -
AbstractProduct (Button, Input):
Interfaccia generica per un prodotto. Verrà implementata da ciascun prodotto concreto. -
ConcreteProduct (BootstrapButton, BootstrapInput):
Classi concrete di prodotti, verranno istanziate da classi factory concrete. -
Client
Richiamano i metodi delle classi factory concrete per la creazione degli oggetti di interesse. In base all’implementazione usa le interfacce implementate dalle classi concrete delle factories o dei prodotti.
Collaborazione.
Di norma, a run-time verrà istanziata la classe factory concreta di interesse, che provvederà a creare gli oggetti della famiglia di riferimento. Nel caso in cui sia necessario creare oggetti di più famiglie, andranno istanziate separatamente le corrispondenti classi factory.
Conseguenze.
Quando si parla di Abstract Factory, bisogna comprendere la differenza fra famiglia e prodotto:
- Famiglia, si intende una famiglia di oggetti, rappresentata da più oggetti concreti
- Prodotto, si intende l’astrazione di un oggetto da istanziare. Le classi che verranno istanziate tramite il pattern saranno una concretizzazione dei prodotti.
Dall’utilizzo dell’Abstract Factory pattern, si ottengono i seguenti vantaggi:
- Isolamento dei prodotti. le classi concrete dei prodotti vengono isolate. Poiché una factory incapsula in sé la responsabilità e il processo di creazione di un certo prodotto, il client non dovrà preoccuparsi di tale gestione e dovrà esclusivamente gestire l’utilizzo dell’istanza di una factory concreta. Nel client non sarà presente alcun riferimento ai prodotti concreti ma solo alla relativa factory. In questo modo, l’abstract factory pattern ci aiuta a rispettare il principio SOLID di singola responsabilità.
-
Manipolare prodotti di una factory. la manipolazione dei prodotti viene facilitata. Dal momento che l’interfaccia tra client e prodotti è la factory concreta, diviene più semplice sostituire un prodotto con un altro e gestire tale modifica a livello di factory, in maniera del tutto trasparente al client.
-
Sostituire famiglie di oggetti nel client. Sostituire una famiglia di oggetti con un’altra è altrettanto semplice. Per sostituire la factory concreta utilizzata da un client basterà sostituirla ove questa viene utilizzata, ovvero in fase di inizializzazione della factory. Poiché tutte le factory concrete devono soddisfare i requisiti della factory astratta, tale modifica non porterà alcuna modifica nel client.
Struttura
Esistono molti modi per implementare l’abstract factory pattern. I più comuni sono i seguenti:
- tramite Singleton. Normalmente, ciascun client avrà bisogno di usare un solo factory concreto (o, equivalentemente, una sola famiglia di prodotti) per volta. In questi casi la classe ConcreteFactory viene istanziata mediante Singleton Pattern, usando il metodo getInstance(). Puoi trovare qui una descrizione del Singleton.
- la concrete factory della famiglia di riferimento viene istanziata nel client tramite DI e Type Hinting. In questa versione, i metodi che ciascuna factory completa dovrà implementare sono definiti come metodi astratti nell’abstract factory.
- tramite l’uso combinato di abstract factory e factory method.
- tramite l’uso combinato di abstract factory e Prototype Pattern.
Ecco il semplice UML di riferimento:
Implementazione
Prima di iniziare con l’implementazione generica, tieni a mente quali sono i partecipanti di questo pattern.
Insieme al precedente UML, inizia col vedere un esempio di struttura di riferimento:
Inoltre, ricorda che abbiamo:
- una classe factory astratta (AbstractFactory), che definirà i metodi per la creazione dei prodotti (i futuri oggetti concreti)
- tante classi factory concrete quante sono le famiglie di oggetti da voler gestire (ConcreteFactory1 e ConcreteFactory2)
- per ciascun prodotto, una classe astratta facoltativa (per isolare e raggruppare le operazioni comuni alle varie istanze concrete di uno stesso prodotto), ad esempio AbstractProductA o AbstractProductB
- per ciascuna famiglia di oggetti, tante classi concrete quanti sono i prodotti che possono essere istanziati (ProductA e ProductB nelle cartelle Factory1 e Factory2)
- un’interfaccia per il generico prodotto (Product)
Detto questo, analizziamo uno per volta le classi dell’UML precedente.
Abstract Factory
namespace AppPatternCreational;
/**
* class AbstractFactory
* for products ProductA and ProductB
*/
abstract class AbstractFactory
{
/**
* Creates product A.
*
* @return ProductA
*/
abstract public function createProductA();
/**
* Creates product B.
*
* @return ProductB
*/
abstract public function createProductB();
}
che stabilisce quali metodi deve implementare ciascuna famiglia di prodotti. Nella fattispecie, ciascuna factory dovrà produrre i prodotti ProductA e ProductB.
Product
/**
* Interface Product
*
* This contract is not part of the pattern, in general case,
* each component are not related
*
*/
interface Product
{
/**
* Some useful generic method for all products implementations.
*
* @return string
*/
public function fire();
}
Anche se non indispensabile ai fini del pattern, può essere creato un contratto generico Product. Ripeto, questo tipo di interfaccia comune ai vari prodotti non è parte integrante del pattern, poiché ciascun prodotto può essere indipendente e non correlato agli altri, ma risulta particolarmente utile in certe situazioni.
Factory1 e Factory2
Ipotizzando di avere due factory concrete (e quindi due famiglie di oggetti), proviamo a implementarle:
/**
* Class ConcreteFactory1
*
* ConcreteFactory1 is a concrete factory for Factory1
* objects family.
*
*/
class ConcreteFactory1 extends AbstractFactory
{
/**
* Creates a concrete productA object.
*
* @return ProductA|Factory1ProductA
*/
public function createProductA()
{
return new Factory1ProductA();
}
/**
* Creates a concrete productB object.
*
* @return ProductA|Factory1ProductA
*/
public function createProductB()
{
return new Factory1ProductB();
}
}
/**
* Class ConcreteFactory2
*
* ConcreteFactory2 is a concrete factory for Factory2
* objects family.
*/
class ConcreteFactory2 extends AbstractFactory
{
/**
* Creates a concrete productA object.
*
* @return ProductA|Factory2ProductA
*/
public function createProductA()
{
return new Factory2ProductA();
}
/**
* Creates a concrete productB object.
*
* @return ProductA|Factory2ProductA
*/
public function createProductB()
{
return new Factory2ProductB();
}
}
notare come ciascuna factory concreta istanzi dei prodotti concreti appartenenti alla famiglia che la factory rappresenta (ad esempio Factory1).
ProductA della factory Factory1
Per brevità, riporterò qui solo uno dei prodotti concreti, il ProductA della factory Factory1, ma lo stesso si può dire per il ProductB della Factory1 e i ProductA e ProductB della Factory2.
use AppCreationalAbstractProductA;
/**
* Class ProductA
*
* ProductA is a concrete product for factory Factory1.
*/
class ProductA extends AbstractProductA
{
/**
* Sample dummy data return for Product A of Factory1
* objects family.
*
* @return string
*/
public function fire()
{
return ‘Concrete Product A from factory 1.’;
}
}
il prodotto concreto estende qui una classe ProductA astratta.
Case Study
Per la presentazione di un caso studio ho vagliato le varie alternative che il web presenta. Chi porta l’esempio della Pizza Factory, chi dell’elenco telefonico. Poi ho trovato un esempio più pertinente, quello dell’implementazione del Look & Feel di un’applicazione desktop. Col tempo, questo esempio è stato portato da Smalltalk a C++ fino a JAVA. Volendo seguire la stessa linea di pensiero in ambito web, invece che ai Look & Feel potremmo rifarci banalmente a Framework HTML quali Twitter Bootstrap o Foundation.
Immaginiamo allora di dover visualizzare a schermo due tipi di prodotti, gli elementi html button e input.
Sebbene un’applicazione potrebbe decidere di utilizzare elementi di più framework contemporaneamente, diciamo che è buona norma far riferimento esclusivamente ad uno di loro. Ipotizziamo allora di trovarci nella situazione in cui dobbiamo aver a che fare con due famiglie di oggetti indipendenti e isolate, siano Twitter Bootstrap e Foundation, da non usare simultaneamente e per cui bisogna produrre i prodotti Button e Input. In questo contesto può venirci in aiuto l’Abstract Factory pattern.
Partiamo dalla base, la classe factory astratta AbstractFactory, che qui chiameremo WidgetFactory. Sappiamo che ciascuna factory dovrà produrre un button e un input, possiamo quindi provvedere a scrivere la classe:
namespace AppPatternCreational;
/**
* class WidgetFactory
* for products Button and Input
*/
abstract class WidgetFactory
{
/**
* Creates Button.
*
* @param $name
* @param $type
* @param $label
* @return Button
*/
abstract public function createButton($name, $type, $label);
/**
* Creates Input.
*
* @param string $name
* @param string $type
* @param string $placeholder
* @return Input
*/
abstract public function createInput($name, $type, $placeholder);
}
per la creazione di un pulsante ci servirà conoscere nome, tipo e la label di riferimento, mentre per la creazione di un input avremo necessità di conoscere nome, type e placeholder da usare. Chiaramente, si tratta di un esempio semplificato di implementazione.
Definiamo adesso ciò che dovrà essere implementato all’interno di ciascun prodotto. Sia Widget il nome assegnato all’interfaccia del generico prodotto. L’unico metodo che tale interfaccia dovrà avere è il metodo render per visualizzare il widget. Anche qui, ci tengo a precisare che questo tipo di approccio non è incluso nel pattern e ciascun prodotto può essere indipendente dagli altri, ovvero non presentare alcuna interfaccia comune.
/**
* Interface Widget
*
*/
interface Widget
{
/**
* @return string
*/
public function render();
}
Una volta definita l’interfaccia generica di un prodotto, possiamo definire delle classi astratte Button e Input agnostiche da qualsiasi classe concreta che prevedano funzionalità base che ciascun Button e Input dovranno concretamente possedere.
Button (prodotto astratto per Button)
namespace AppPatternCreationalAbstractFactory;
/**
* Class Button
*/
abstract class Button implements Widget
{
static private $_occurrence = 1;
/**
* @var string
*/
protected $name;
/**
* @var string
*/
protected $type;
/**
* @var string
*/
protected $label;
/**
* @param string $name
* @param string $label
*/
public function __construct($name = ‘Button’, $type = ‘submit’, $label = ‘Sample Label’)
{
$this->name = (string) $name . ‘ ‘ . self::$_occurrence;
$this->type = (string) $type;
$this->label = (string) $label . ‘ ‘ . self::$_occurrence;
self::$_occurrence++;
}
}
Input (prodotto astratto per Input)
namespace AppPatternCreationalAbstractFactory;
/**
* Class Input
*/
abstract class Input implements Widget
{
/**
* @var string
*/
protected $name;
/**
* @var string
*/
protected $type;
/**
* @var string
*/
protected $placeholder;
/**
* @param string $name
* @param string $type
* @param string $placeholder
*/
public function __construct($name = ‘Sample Window’, $type = ‘text’, $placeholder = ‘Insert here’)
{
$this->name = (string) $name;
$this->type = (string) $type;
$this->placeholder = (string) $placeholder;
}
}
notare come queste due classi implementino entrambe l’interfaccia generica Widget, aumentandone le funzionalità. Invece di aggiungere queste funzionalità singolarmente ai prodotti di tutte le famiglie, così facendo provvediamo ad astrarre le caratteristiche comuni dei prodotti.
Prendiamo adesso in esame le factory concrete che estenderanno quella astratta, siano ad esempio BootstrapWidgetFactory e FoundationWidgetFactory.
BootstrapWidgetFactory
namespace AppPatternCreationalAbstractFactory;
/**
* Class BootstrapWidgetFactory
*
* BootstrapWidgetFactory is a concrete factory for
* WidgetFactory objects family.
*
*/
class BootstrapWidgetFactory extends WidgetFactory
{
/**
* Creates a concrete button object.
*
* @param $name
* @param $type
* @param $label
* @return Button|BootstrapButton
*/
public function createButton($name, $type, $label)
{
return new BootstrapButton($name, $type, $label);
}
/**
* Creates a concrete Input object.
*
* @param $name
* @param $type
* @param $placeholder
* @return Input|BootstrapInput
*/
public function createInput($name, $type, $placeholder)
{
return new BootstrapInput($name, $type, $placeholder);
}
}
FoundationWidgetFactory
namespace AppPatternCreationalAbstractFactory;
/**
* Class FoundationWidgetFactory
*
* FoundationWidgetFactory is a concrete factory for
* WidgetFactory objects family.
*/
class FoundationWidgetFactory extends WidgetFactory
{
/**
* Creates a concrete button object.
*
* @param $name
* @param $type
* @param $label
* @return Button|FoundationButton
*/
public function createButton($name, $type, $label)
{
return new FoundationButton($name, $type, $label);
}
/**
* Creates a concrete input object.
*
* @param $name
* @param $type
* @param $placeholder
* @return FoundationInput|Input
*/
public function createInput($name, $type, $placeholder)
{
return new FoundationInput($name, $type, $placeholder);
}
}
Una volta costruite anche le factory concrete, non ci resta che definire i prodotti concreti per ciascuna famiglia. Partiamo dalla famiglia appartenente al framework Twitter Bootstrap:
Twitter Bootstrap Products: Button
namespace AppPatternCreationalAbstractFactoryBootstrap;
use AppCreationalAbstractFactoryButton as AbstractButton;
/**
* Class Button
*
* Button is a concrete widget for Framework Bootstrap
*/
class Button extends AbstractButton
{
/**
* Some basic style settings for Button
*
* @return string
*/
public function render()
{
return sprintf(‘‘, $this->name, $this->type, $this->label);
}
}
Twitter Bootstrap Products: Input
namespace AppPatternCreationalAbstractFactoryBootstrap;
use AppCreationalAbstractFactoryInput as AbstractInput;
/**
* Class Input
*
* Input is a concrete widget for Framework Bootstrap
*/
class Input extends AbstractInput
{
/**
* Some basic style settings for Input
*
* @return string
*/
public function render()
{
return sprintf(‘
‘, $this->label, $this->type, $this->name, $this->placeholder);
}
}
definiamo infine i prodotti appartenenti alla famiglia del framework Foundation:
Foundation Products: Button
namespace AppPatternCreationalAbstractFactoryFoundation;
use AppCreationalAbstractFactoryButton as AbstractButton;
/**
* Class Button
*
* Button is a concrete widget for Foundation Framework
*/
class Button extends AbstractButton
{
/**
* Some basic style settings for Button
*
* @return string
*/
public function render()
{
return sprintf(‘%s‘, $this->label);
}
}
Foundation Products: Input
namespace AppPatternCreationalAbstractFactoryFoundation;
use AppCreationalAbstractFactoryInput as AbstractInput;
/**
* Class Input
*
* Input is a concrete widget for Foundation Framework
*/
class Input extends AbstractInput
{
/**
* Some basic style settings for Input
*
* @return string
*/
public function render()
{
return sprintf(‘
‘, $this->label, $this->type, $this->name, $this->placeholder);
}
}
Per quanto si tratti di un esempio puramente didattico, possiamo già implementare un possibile UML di riferimento.
UML teorico e pratico a confronto.
Per facilitarti la comprensione, prova a osservare adesso nuovamente i due UML generati. Il segreto sta tutto nell’associare la giusta classe teorica a quella pratica.
Testing del pattern.
Per finire, eseguiamo un primo test essenziale sulla creazione vera e propria delle factory concrete da parte del client. Questo tipo di test, sebbene non del tutto esaustivo, ti aiuterà anche a capire come il generico client potrà implementare le factory.
use AppCreationalAbstractFactoryWidgetFactory;
use AppCreationalAbstractFactoryBootstrapWidgetFactory;
use AppCreationalAbstractFactoryFoundationWidgetFactory;
/**
* WidgetFactoryTest will tests concrete factories of abstract factory WidgetFactory.
*/
class AbstractFactoryTest extends PHPUnit_Framework_TestCase
{
public function getFactories()
{
return array(
array(new BootstrapWidgetFactory()),
array(new FoundationWidgetFactory())
);
}
/**
* This is the client of factories.
*
* @dataProvider getFactories
* @param WidgetFactory $factory
*/
public function testComponentCreation(WidgetFactory $factory)
{
$article = array(
$factory->createButton(‘Sample This name’, ‘submit’, ‘Sample label’),
$factory->createInput(‘Sample this input’, ‘text’, ‘Insert here.’),
$factory->createInput(‘Another input’, ‘text’, ‘Insert now.’)
);
$this->assertContainsOnly(‘AppCreationalAbstractFactoryWidget’, $article);
}
}
Factory e Laravel 5
Come accennato, il core di Laravel 5 comprende l’applicazione di parecchie Factory. In questa fase ritengo giusto analizzare poco per volta Abstract Factory e Factory Method senza presentare le implementazioni disponibili in Laravel, che prendono spunti diversi da ognuno di questi pattern. Vedrai nel prossimo articolo, dopo aver parlato di Factory Method e Simple Factory, come L5 ha deciso di approcciarsi a tale metodologia. Per stuzzicare la tua fantasia, ti invito come sempre a fare una veloce ricerca nel core di Laravel in cerca di parole chiave che possano aiutarti a scavare all’interno del framework:
come vedi, molti dei Contracts presenti nei package Illuminate fanno uso di Factory. Volendo generalizzare, anche fuori dall’ecosistema Laravel possiamo trovare questi concetti applicati:
analizzeremo in dettaglio queste implementazioni nel prossimo articolo.
Nel prossimo episodio
Continueremo la trattazione delle factory analizzando l’implementazione del Factory Method. In particolare, questo pattern trova molteplici applicazioni in Laravel e, più in generale, in PHP. Analizzeremo come il Factory Method si fonde all’Abstract Factory e dove esso viene implementato in Laravel 5.
Il Tuo Parere
Durante la stesura di questa serie, penso sia fondamentale capire quali siano le tue necessità, le tue idee e quali difficoltà trova la community nell’applicare quotidianamente questi concetti, quindi ogni feedback/dubbio è ben accetto!