Categorie
PHP

PHP -Design Pattern Factory Method

Nella programmazione OOP quando si parla di pattern ci si riferisce semplicemente alle pratiche migliori per risolvere un determinato e noto problema. Ciò significa che anche se è possibile risolvere il problema con metodi alternativi, un pattern sarà il modo più efficace per raggiungere l’obiettivo.

Il pattern factory method (detto anche come Virtual Constructor) è probabilmente uno dei più utilizzati e conosciuti. Nella vita reale, una fabbrica crea oggetti.  Allo stesso modo, in un pattern factory, consiste nella ‘fabbrica’ con la quale si creano oggetti.
[box type=”note”]In generale si parla di fabbriche quando si vuole confinare da qualche parte i dettagli della creazione di oggetti.[/box]

Quando è utile

L’implementazione di questo pattern risulta particolarmente indicata quando abbiamo un set di classi ma non sappiamo fino a runtime quale dobbiamo concretamente istanziare. E’ chiamato factory proprio perché crea vari tipi di oggetti  senza necessariamente sapere che tipo di oggetto sta creando o come lo sta facendo.

Struttura

Le classi (e/o interfacce) che partecipano alla creazione di questo pattern sono le seguenti:

  • Prodotto: è l’interfaccia implementata da tutti gli oggetti creati dal CreatoreConcreto (che vedremo dopo). Spesso è rappresentato anche con una classe astratta.
  • ProdottoConcreto: implementa l’intefaccia definita da Prodotto. Il ProdottoConcreto sarà generato dal CreatoreConcreto.
  • Creatore:  è l’interfaccia implementata dal CratoreConcreto. Definisce un metodo (metodo factory) che si occupa di creare un ProdottoConcreto. Molte volte questa interfaccia è definita come classe astratta con un metodo factory che restituisce un prodotto di default.
  • CreatoreConcreto: implementa l’override del metodo factory del Cratore per restituire ProdottiConcreti specifici.

Implementazione

Di solito al fine di evitare la proliferazione di classi, in PHP si opta per non utilizzare il Creatore ma implementare direttamente  il CreatoreConcreto dando un comportamento parametrizzato al metodo factory che restituirà l’istanza di uno dei ProdottiConcreti definiti in base ad un parametro passatogli.

Esempio

Supponiamo di avere una fabbrica che genera qualsiasi tipo di macchina. Quando creiamo una nuova macchina non abbiamo bisogno di sapere come è fatta o quanto è diversa da un’altra.
Il pattern incapsula come le cose sono fatte, quindi noi non abbiamo bisogno di conoscerne i dettagli. Se un tipo di macchina cambia il modo con cui è fatta, non ce ne dobbiamo preoccupare.
La classe della macchina cambierà ma come si accede a quella classe rimane invariato.

<?php
//Creatore Conreto.
class CarFactory {   
    //Matodo factory, che si occupa di creare gli oggetti.
    public static function createCar($carType){
        if (class_exists($carType)) {
            return new $carType;
        } else {
            die("Tipo di macchina sconosciuto");
        }
    }   
}

//Prodotto. Classe astratta da cui deriveranno tutti Prodotti Concreti.
abstract class Car {
    public function getType(){
        echo $this->_type . PHP_EOL;
    }
}

//Classe che contiene tutti i dettagli su come fare una Ferrari.
class Ferrari extends Car{
    protected $_type = "Ferrari";

    //Altro codice che rende questa vettura diversa dalle altre.
}

//Classe che contiene tutti i dettagli su come fare una Lamborghini.
class Lamborghini extends Car{
    protected $_type = "Lamborghini";

    //Altro codice che rende questa vettura diversa dalle altre.
}

//Classe che contiene tutti i dettagli su come fare una Bianchina.
class Bianchina extends Car{
    protected $_type = "Bianchina";

    // overidde del metodo
    public function getType(){
        echo $this->_type . ' ma esiste ancora?';
    }
    //Altro codice che rende questa vettura diversa dalle altre.
}

//Creo "fabbrica di macchine"
$carFactory = new CarFactory();

//Creo una macchina
$Ferrari = $carFactory->createCar('Ferrari');
//Ottengo il tipo.
$Ferrari->getType();

$Lamborghini = $carFactory->createCar('Lamborghini');
$Lamborghini->getType();

$Bianchina = $carFactory->createCar('Bianchina');
$Bianchina->getType();
?>

Il metodo factory createCar() è la chiave di tutto, questi si occupa di creare le macchine (istanziare le classi) in base al parametro passatogli. Se il parametro non corrisponde a nessuna classe allora sarà generato un messaggio di errore.
Ognuno degli oggetti creati dal factory method (ConcreteProduct) è di classe differente. La soluzione per poter ignorare questo aspetto è che tutti implementino la stessa classe astratta (Product).
[note]Il metodo factory dovrebbe essere statico così da consentire di istanziare classi velocemente.[/note]

Come possiamo notare siamo in grado di creare ed utilizzare i prodotti creati dal factory senza doverci preoccupare della loro implementazione specifica. In questo modo qualsiasi modifica apportata direttamente sul ProdottoConcreto non genererà nessun problema all’interno dell’applicazione.

11 risposte su “PHP -Design Pattern Factory Method”

Sono un programmatore piuttosto datato ( lavoro in php fin dalla versione 3 ) e da circa un anno buono lavoro ad oggetti però purtroppo alcuni concetti mi sono ancora un po’ oscuri.
Quali siano i vantaggi di una di questo Design pattern direi che sono abbastanza evidnti, quello che non capisco è come questa classe possa svolger la funzione di recupero e crazione di liste di istanze di oggetti ( come molte volte ho visto citare in forum e libri ) in poche parole negli esempi che ho trovato sulla rete ho spesso visto cose di questo genere:

Voglio selezionare l’elenco degli studenti di una scuola che hanno effettuato un dato esame in un dato periodo e la funzione che dovrbbe svolgr il compito è:

StudentFactory::getStudent();

Un oggetto come quello qui sopra spiegato non mi sembra che svolga questo compito, non ha alcun filtro all’interno.
Per chiarire, se io ho una classe Student che rappresenta un singolo studente presente sul DB e da un Controller mi serve la lista di questi studenti che architettura dovrei utilizzare?
Grazie

M ROSSI

Maurizio,
riguardo la tua necessità l’utilizzo del pattern factory method non ha nessuna utilità.
Quando si parla di servizi inerenti alla persistenza dei dati, nel tuo caso DB (relazionale suppongo) allora devi prendere in considerazione altri concetti, tipo ORM (Object-relational mapping).
E’ una tecnologia studiata appositamente per favorire l’integrazione tra DB relazionali e codice OOP.
Si tratta di un argomento vasto, su cui anch’io ancora devo imparare tanto, quindi mi limito a segnalarti qual’è la via secondo me + giusta.
In circolazione ci sono diversi ORM PHP. Doctrine e Propel sono sicuramente i più famosi ed utilizzati (soprattutto il primo visto che è integrato in Symfony ed ora penso anche ZF2).
Comunque in rete troverai tantissima documentazione al riguardo.
Buono studio!

Ciao Grimax,

ti ringrazio per la risposta prima di tutto.
In realtà io nei miei progetti utilizzo già una classe del genere, io uso una classe astratta home-made che ho chiamato dataBoundObject.php (ho preso spunto e anche parecchio codice da PHP6 Professional, libro Wrox incasinato nella costruzione ma utile), una volta estesa fornisce alle classi che la estendono tutte le funzioni C.R.U.D. (Create, Read, Update, Delete) per operare con un record di una tabella, si tratta di qualcosa di molto ridotto rispetto a on ORM come doctrine o propel ovviamente ma per le mie esigenze è più che ottimo.
La mia domanda ridotta all’osso è questa:
Immaginiamo di avere la classe Student che estende la classe astratta di cui sopra e quindi ha tutte le funzioni C.R.U.D. per operar con la tablla del DB student nella quale un record rappresnta uno studente.
A questo punto quando io creo un istanza della classe Student o gli passo un ID, nel qual caso sto modificando, leggendo o cancellando un record già esistente o non lo passo (quindi verrà autognerato) e sto creando un nuovo record studente.
I metodi per avere l’elenco degli studenti che, per esmpio, si sono iscritti dal.. al.. è un tipo di funzione che non deve stare come metodo all’interno di questa classe in quanto non è logico che un oggetto studnt abbia il metodo per elencr tutti gli studenti ( lui rpprsnta una sola row della tabella) qual’è quindi il giusto modo di procedre ( io spesso in questo contesto ho visto menzionare la classe e il design pattern factory, da qui la domanda che ti avevo posto)?
COme struttureresti un’applicazione per gestire questa situazione ( peraltro frequntissima )?
Grazie del tempo dedicato.
M ROSSI

Io non lavoro con questo approccio, quindi mi risulta difficile aiutarti.
Di solito si lavora con i design pattern Table e Row Data Gateway (all’interno di un framework meglio).
La classe Table Data Gateway rappresenta una tabella.
La classe Row Data Gateway rappresenta una riga della tabella.
All’interno della prima implementerai i metodi relativi alla tabella (quindi una select), dove per esempio implementerai il metodo getStudentsByExamDate($dateStart, $dateEnd).
Naturalmente questo metodo dovrà restituire una collezione (array o oggetto iterator) di oggetti Row Data Gateway.
Questo è secondo me il modo giusto per lavorare lato OOP con DB relazionali.
Giusto come spunto:
http://framework.zend.com/manual/1.12/en/zend.db.table.html
http://framework.zend.com/manual/1.12/en/zend.db.table.row.html

Grazie mille, la tua ultima risposta è stata illuminante, ora credo di avere tutto chiaro e mi stupiso di non averci pensato prima.
Confesso che non ho mai sentito nominare design pattern dal nome Table Data Gateway e Row Data Gateway però ora mi documenterò.
Una cosa l’ho compresa: quello che io ho chiamato dataBoundObject è assimilabile più o meno alla classe Row Data Gateway e a questo punto devo crearmi una classe astratta Table Data Gateway con i metodi base necessari per la gestione di una table generica (listAll, List con condizione etc etc) e poi, fatto ciò per ogni tablla del mio DB creo una clase concreta che estende Table Data Gateway e una classe concreta che estende Row Data Gateway con queste due posso realizzare più o meno tutte le operazioni che mi necessitano direi.
Purtroppo io Zend non l’ho mai utilizzato ( carnza lo ammetto ) e sono poco ferrato con i framework, ho utilizzato un po’ codeigniter ma pochissimo per questa ragione certi conctti non mi sono granchè familiari.
Grazie mille dll’aiuto.
Buona serata.

Ciao Grimax,

ho implementato un intero progetto con le classi di cui abbiamo parlato in questo post e mi sono trovato molto bene, ho preso anche molti spunti da Design Patter for Enterprise applications, un libro spettacolare per questo argomento.
Ora, visto la disponibilità dimostrata da te, ti sottopongo un nuovo questito che mi sta dando qualche problema.
Lavorando con questa metodologia l’accesso alle tabelle e alle singole righe è ottimo e molto semplice, si evita praticamente del tutto di scrivere sql al di fuori delle classi “finder” che servono per recuperare i record (poi trasformati in oggetto Row Data Gateway).
Ho un solo problema: procedendo in questo modo quando devo mostrare un elenco ordinato di dati ordinandoli, per esempio, in ordine alfabetico secondo un campo che non è presente nell’oggetto come testo ma come chiave esterna di altra tabella la cosa risulta impossibile.
Mi spiego.
Immaginiamo di avere un oggetto State che rappresenta uno degli stati del mondo e un altro oggetto City che rappresenta una città di un particolare stato che nella tabella è indicato con la sua chiave esterna.
Se io recupero semplicemente l’elenco delle città non posso far altro che ordinarle secondo il loro nome oppure secondo l’id dello stato, che però è numerico.
Ovviamente facendo una join nella query di recupero dei dati delle città il problema è subito risolto però in questo modo procederei secondo il vecchio paradigma procedurale, recuperare tutti i dati da query e poi riempire un array e non costruire degli oggetti.
Non so se il problema esposto è chiaro, forse sono stato un po’ prolisso e confusionario ma spero di aver reso l’idea.
Grazie del tempo dedicato.
Buona giornata

Ciao Maurizio
hai proprio toccato il “dilemma” su cui batto la testa ultimamente con ZF2.
Prima di tutto un po’ di chiarezza, visto che ho imparato ad usare questi patterns con ZF1 ti ho consigliato il suo modello di implementazione, anche se, come avrai imparato dal bellissimo libro che hai citato, Table Data Gateway (TG) e Row Data Gateway (RG) sono 2 soluzioni diverse ed alternative. In realtà TG andrebbe implementato con le entità che rappresentano le righe delle tabelle in ottica OOP, qualcosa tipo:
namespace User\Model;

class User {
private $id;
private $username;
private $password;

public function exchangeArray(array $data) {
$this->id = (isset($data[‘id’])) ? $data[‘id’] : null;
$this->username = (isset($data[‘username’])) ? $data[‘username’] : null;
$this->password = (isset($data[‘password’])) ? $data[‘password’] : null;
}

public function extract() {
$arr = array();

$arr[‘id’] = $this->id;
$arr[‘username’] = $this->username;
$arr[‘password’] = $this->password;

return $arr;
}
}
Il metodo exchangeArray() mappa le colonne della tabella nelle proprietà dell’entità.
Mentre Table Data Gateway avrà metodi find che ritornano un RecordSet, con Row Data Gateway, è necessaria una classe Finder separata che restituisce una classe Row Data Gateway.

Ma veniamo al tuo quesito, quando si arriva ad aver a che fare con progetti più grandi ci si accorge che il TG è limitato… la soluzione è il Data Mapper (che trovi sul libo).
Purtroppo quello che posso fare è solo indicarti la strada dato che anche io non ho ancora compreso bene come implementarlo .
Ricapitolando:
Il Table Gateway viene utilizzato principalmente per una singola tabella o viste. Contiene tutte le select, insert, update e delete di una tabella. Quindi, un’ istanza di un oggetto table gateway gestisce tutte le righe della tabella. Di solito questo è legato ad un oggetto per ogni tabella del database.
Mentre Data Mapper è più indipendente da qualsiasi logica di dominio ed è meno accoppiato. È soltanto uno strato intermedio per trasferire i dati tra oggetti e un database mantenendoli indipendenti l’uno dall’altro.
Non aggiungo altro xchè ho ancora grossi dubbi riguardo alla sua concreta implementazione.

Spero di esserti stato di aiuto. Ciao
https://github.com/licpro/php-slides/blob/master/src/1/09_databases.md

Ciao,

prima di tutto grazie per la risposta, come sempre rapida e molto utile.
Vedo con piacere e anche con dispiacere al tempo stesso che questi problemi non li ho solamente io, purtroppo.
Ho letto un po’ la spiegazione di quel pattern all’epoca, devo dire che non ho compreso granchè della spiegazione, a mio avviso il libro che ho citato è molto completo ma poco chiaro per quanto riguarda gli schemi e per gli esempi, essendo in Java, spesso non si adattano al mondo PHP.
In conclusione mi spiace doverlo dire ma penso sempre più che PHP sia e per ora rimarrà un linguaggio per “lavoretti” di bassa qualità per mancanza di documentazione chiara ed esaustiva.
Molti programmatori rispondono dicendo che su internet si trova tutto e di più e forse è proprio questo il limite, si trova tutto e il contrario di tutto. Forse la programmazione procedurale aveva molti più limiti, è vero, però spesso a quei tempi trovavi documentazione chiara ed esaustiva. Ora tutto è fumoso e rapido, il risultato sono applicazioni sempre più accattivanti ma sempre meno efficenti e precise.
I Design Patter e la OOP è una grande materia ma come trattata oggi mi sembra sempre più un gran parlare e niente altro.
Grazie comunque per la tua spiegazione, ti auguro, in questo campo di avere più fortuna di me negli studi.

P.S.
Il mio ultimo commento è stato più uno sfogo che un commento costruttivo, me ne rendo conto.
Ti indico questa slide che da un po’ di spunti su come implementare un Data Mapper in PHP5:

http://www.slideshare.net/vlucas/building-data-mapper-php5-presentation

Resta comunque a me ignoto come superare il problema esposto nella prima domanda dopo la lettura della slide e anche del capitolo del libro di Martin Fowler sul DM.

Forse sono io che non ci arrivo ma non vedo, con nessuno dei quattro design patter presentati, come aggirare gli ostacoli esposti.
Per quanto mi riguarda continuerò à fare le mie query con join varie e buona notte, non saranno stilisticamente perfette, saranno “fuori moda” perchè purtroppo ormai anche la programmazione è diventata un fatto di moda, però funzionano benissimo e sono efficenti sotto il profilo prestazionale.

Il giorno che queste divinità dei design pattern si degneranno di esporre i concetti (se ne sono capaci) in maniera chiara, esaustiva e con esempi un po’ più completi e utili del noto hello world!!!! inizierò a utilizzare con profitto queste meraviglie di DP, per ora differentemente da quanto affermano tutti a me l’SQL non sembra assolutamente ne superabile ne sostituibile con questi “acrocchi”.
Buon studio.

Lascia un commento

Il tuo indirizzo email non sarà pubblicato. I campi obbligatori sono contrassegnati *

Questo sito usa Akismet per ridurre lo spam. Scopri come i tuoi dati vengono elaborati.