View Full Version : [OOP] Factory Method Design Pattern
Salve,
non riesco a capire bene questo semplice (apparentemente) design pattern. Fondamentalmente quello che io ho capito è che si usa per creare un unico punto centralizzato per la creazione di più oggetti simili fra loro, ma che al momento della creazione non si sa bene quale sia necessario creare. Gli esempi si sprecano. Ma un caso di utilizzo stupido e abbastanza chiaro è il seguente.
Voglio poter creare in un'applicazione varie pizze. A runtime però non so quale pizza potrei dover creare (in base a delle ordinazioni per esempio). Quindi l'idea è creare una classe superiore che rappresenti il concetti di "pizza", classe estesa poi da tante "pizze concrete". A questo punto mi serve centralizzare la creazione delle pizze con una "factory". Ma io qui mi perdo...
Come si implementa questa situazione con il Factory Method?
Da molti schemi (uml e non) trovati sul web sembra che gli elementi di questo design pattern applicati a questo preciso esempio dovrebbero essere:
- una classe AbstractFactoryPizza
- tante ConcreteFactoryPizza ciascuna per ogni tipo di pizza che si può creare (FunghiFactoryPizza, SalameFactoyPizza, MargheritaFactoryPizza...). Ognua di queste classe ovviamente estende la classe astratta AbstractFactoryPizza
- una interfaccia che rappresenti una generica pizza, esempio IPizza
- tante classi che implementano la suddetta inferaccia ciascuna per ogni pizza creabile (quindi parallelamente a prima ci sarebbero FunghiPizza, SalamePizza, MargheritaPizza...)
Io arrivo a capire fino a qui. Però non capisco come questi elementi interagiscano. Non capisco per esempio come potrei fare da un ipotetico main() a chiamare la creazione di una precisa pizza. Teoricamente credo che debba passare tramite l'AbstractFactoryPizza e in qualche modo decidere quale delle "factory concrete" chiamare. Poi queste istanzieranno in qualche modo l'opportuna pizza ok... però come faccio ad usare AbstractFactoryPizza? E' astratta!
Che confusione! Aiuto!
In un pezzo di codice scriverai
AbstractFactoryPizza ThisFactory = new PizzaMargheritaFactory();
e poi tipicamente andrai a richiamare un codice che accetti una AbstractFactoryPizza che crei l'istanza della pizza giusta, e che ci richimai qualcosa di comune a tutti gli abstract factory oppure a tutte le pizze (altrimenti questa similitudine tra pizze non ci starebbe).
P.es.
AbstractFactoryPizza ThisFactory = new PizzaMargheritaFactory();
AbstractPizza = ThisFactory.Creaecuoci();
....
public abstract class AbstractFactoryPizza
{
public AbstractPizza Creaecuoci()
{
AbstractPizza thisPizza= GiveMeInstance();
thisPizza.AggiungiSale();
thisPizza.Appiattisci();
thisPizza.Condisci();
thisPizza.Cuoci();
return thisPizza;
}
// questo e' un nodo chiave, ovvero il produttore della Factory
// Ogni implementazione dovra' ritornare l'oggetto del tipo giusto
// Grazie all'inheritance e' possibile, vedi dopo
// E' il metodo che lega il livello della singola Factory con
// Il livello della classe da produrre
abstract public AbstractPizza GiveMeInstance();
}
publci abstract class AbstractPizza
{
public void AggiungiSale()
{
//Qui scrivo direttamente il codice per aggiungere il sale
// su una qualsiasi pizza
// Dato che su tutte le pizze il sale si aggiunge nello stesso modo
}
// Qui invece non scrivo il codice per condire la pizza, dato
// che ogni pizza si condisce in modo diverso
// con abstract obbligo la singola implementazione di pizza a
// scrivere il codice di come verra' condita
public abstract void Condisci();
}
public class MargheritaFactory:AbstractPizzaFactory
{
// Metodo chiave, lega il livello di intheritance dei factory
// con quello degli oggetti creati
public override AbstractPizza GiveMeInstance()
{
return new PizzaMargherita();
// Possibile, una PizzaMargherita e' una AbstractPizza
}
}
Grazie per la risposta molto esaustiva...
Ci ho riflettuto un po' e diciamo che le cose ora mi sono più chiare per certi versi. Ma non riesco a capire bene come un eventuale client (una classe qualunque, un Main per esempio) possa usare questo pattern.
Nel codice che mi hai indicato per esempio, un eventuale client deve direttamente creare un'istanza di una precisa factory. Infatti nel tuo esempio il client chiama appunto:
AbstractFactoryPizza ThisFactory = new PizzaMargheritaFactory();
Pero' il fatto e' che questo pattern per quanto ho capito, deve prevenire situazioni simili in cui il client deve sapere esattamente che cosa creare.
Quindi diciamo che da qualche parte ci deve essere un metodo unico in cui inserire la scelta centralizzata di che tipo di pizza creare in base a qualche parametro. E il client, sapendo solo questo parametro, chiamerà questo metodo ottenendo quindi una generica IPizza oportunamente istanziata su una pizza concreta che pero' il client non sa assolutamente quale sia. Usera' i metodi dell'interfaccia appunto in maniera trasparente.
Il fatto da chiarire è dove mettere questo metodo unico centralizzato che effettua la scelta di quale Factory richiamare demandando a quella la creazione dell'opportuna pizza. Idealmente mi viene da pensare che questo metodo deve stare nell'AbstractFactoryPizza, così il client in ogni caso deve fare riferimento solo a quella. Però quella è una classe astratta e quindi non può essere istanziata... sono di nuovo confuso.
Per fare quello che dici si possono usare i metodi statici.
Per purezza di codice io pero' non legherei la decisione di quale factory restituire all'interno della classe astratta delle factory, quanto piu' aggiungerei una classe Helper statica, dotata (almeno) di un unico metodo statico che restituisce la classe factory sulla base di qualche input
public static class AbstractFactoryPizzaHelper
{
public AbstractFactoryPizza GetFactoryPizza(string input)
{
switch (input)
{
case "Margherita": return new MargheritaFactory(); break;
case "Napoli": return new NapoliFactory(); break;
//etc.
}
}
}
// Richiamabile con
AbstractFactoryPizza myFactoryPizza = AbstractFactoryPizzaHelper.GetFactoryPizza("Margherita");
AbstractPizza myPizza = myFactoryPizza.CreaECouci();
Come detto questo metodo puo' anche essere contenuto direttamente classe AbstractFactoryPizza, ma non glielo metterei, perche' anche se permesso introduce una circular reference. Una MargheritaFactory deriva da un AbstractFactoryPizza, mentre introducendo quel metodo AbstractFactoryPizza dipende dall'esistenza di una classe MargheritaFactory
Nulla che non si possa fare (a quel punto sono solo nomi, non sono istanze), ma la soluzione con l'Helper mi piace di piu'
banryu79
19-12-2008, 11:41
Io ti linko questo thread (http://www.hwupgrade.it/forum/showthread.php?t=1812258), magari ti è utile.
Per fare quello che dici si possono usare i metodi statici.
Per purezza di codice io pero' non legherei la decisione di quale factory restituire all'interno della classe astratta...
Ecco infatti anche io avevo pensato al metodo statico e ugualmente avevo storto il naso.
Comunque forse avete inteso male il mio post. La modellazione che ho proposto era solo una mia idea partorita da quello che avevo capito di questo pattern. Però se qualcuno di voi pensa che il pattern in questo caso debba essere utilizzato diversamente me lo dica!
Io ti linko questo thread, magari ti è utile.
Si mi sembra che in questo thread l'esempio riportato è esattamente come il mio. Però anche lì se notate, la classe User nel metodo use() istanzia direttamente due factory precise (Subclass1Factory() e Subclass2Factory()).
Pero' in generale una classe client come questa non dovrebbe trovarsi nella condizione di sapere esattamente qualche factory chiamare no?! Altrimenti scusate allora avrebbe chiamato direttamente i costruttori delle due classi Subclass1 e Subclass2 no?!
Nel mio esempio quindi un ipotetico client Cameriere prende l'ordinazione e riferisce al DirettoreDellaCucina i nomi delle pizze da creare. Ma non deve sapere esattamente ogni volta quali sono queste pizze. L'ordinazione può essere di 1 pizza come di 100, il cameriere prende nota solo del nome che è come se fosse un ID. Il DirettoreDellaCucina in sostanza dovrebbe essere una sorta di AbstractFactory che poi andrà dal cuoco opportuno (è una pizzeria di lusso!!) e gli dirà di cucinare la pizza per cui lui è specializzato. In questa visione quindi il client è il Cameriere che prende le ordinazioni e deve rivolgersi al DirettoreDellaCucina per ottenere la pizza da consegnare al tavolo. Per rivolgersi al DirettoreDellaCucina però deve usare appunto un metodo unico che dovrebbe essere una sorta di "dammiQuestaPizza(IDpizza)". Ma questo metodo unico dove sta? Teoricamente deve stare in una classe che abbia la visione completa di tutte le factory di pizze esistenti, creando un unico punto centralizzato per la creazione. E a me pare quindi che deve stare dentro DirettoreDellaCucina no?! Pero' si ritorna al discorso del metodo statico...
Sto andando a tentoni eh!?
banryu79
19-12-2008, 13:04
Sto andando a tentoni eh!?
No, va bene: stai ragionando, il che è ottimo, direi.
Aspettiamo di sentire GugoXX o qualcun'altro che ha già usato questo pattern nella pratica.
tomminno
19-12-2008, 13:05
Io farei qualcosa del genere (in C#):
class PizzaFactory
{
public PizzaFactory()
{
InitPizzaMaker();
}
public IPizza MakePizza(string pizzaName)
{
return pizzaMaker[pizzaName.ToLower()];
}
private void InitPizzaMaker()
{
pizzaMaker ["margherita"] = PizzaMaker<Margherita>;
pizzaMaker ["napoli"] = PizzaMaker<Napoli>;
//...
}
T PizzaMaker<T>() where T : IPizza, new()
{
return new T();
}
private delegate IPizza MakePizza();
private Dictionary<string, MakePizza> pizzaMaker = new Dictionary<string, MakePizza>();
}
Io ti linko questo thread (http://www.hwupgrade.it/forum/showthread.php?t=1812258), magari ti è utile.
L'importante e' non esagerare :asd:
http://discuss.joelonsoftware.com/default.asp?joel.3.219431.12
banryu79
19-12-2008, 14:02
L'importante e' non esagerare :asd:
http://discuss.joelonsoftware.com/default.asp?joel.3.219431.12
La conoscevo già, spassosissimo (la FactoryFactoryFactory è comodissima per costruire una AbstractToolFactoryFactory che verrà poi utilizzata per costruire una HammerFactory, giustappunto quello che ci serviva per avere un semplice, stramaledetto, Hammer) :asd:
I Design Pattern non sono legge, sono molto comodi e utili, ma non bisogna farsi prendere la mano :D
Se ho capito l'idea ti Tomminno, allora è un'approccio che ho già visto in qualche esempio. Sostanzialmente l'idea credo sia quella di dichiarare nelle Factory più generale un campo che rappresenti l'insieme delle Factory specializzate esistenti (nel caso viene implementato in C# con un Dictionary, ma può essere un qualunque array, arraylist, hashmap...).
Però credo che il punto centrale sia la dichiarazione della classe Factory generale come classe concreta e non come classe astratta o interfaccia, come credo sia indicato dalle linee guida per questo pattern.
Il fatto è che la factory generale deve fungere da factory generale appunto! Cioè deve contenere tutti i metodi che devono essere comunque reimplementati nelle factory concrete derivate da questa. Se non si dichiara la factory generale come astratta o interfaccia, le factory derivate credo siano svincolate da questo dovere di implemtare i metodi specificati come astratti. O no? Altrimenti in effetti che utilità avrebbe usare una classe astratta/interfaccia per la factory generale?
tomminno
19-12-2008, 16:01
Se ho capito l'idea ti Tomminno, allora è un'approccio che ho già visto in qualche esempio. Sostanzialmente l'idea credo sia quella di dichiarare nelle Factory più generale un campo che rappresenti l'insieme delle Factory specializzate esistenti (nel caso viene implementato in C# con un Dictionary, ma può essere un qualunque array, arraylist, hashmap...).
Però credo che il punto centrale sia la dichiarazione della classe Factory generale come classe concreta e non come classe astratta o interfaccia, come credo sia indicato dalle linee guida per questo pattern.
Quello a cui fai riferimento te è l'Abstract Factory, io ho postato un esempio di Factory Method.
L'Abstract Factory lo usi in un caso del genere (sempre prendendo spunto dalla pizza):
List<AbstractFactory> abstractFactory = new List<AbstractFactory>();
abstractFactory.Add(FactoryPizza);
abstractFactory.Add(FactoryStarter);
abstractFactory.Add(FactoryDessert);
Dinner dinner = new Dinner();
dinner.Cook(abstractFactory);
L'Abstract Factory serve per avere famiglie di Factory Method, cioè se vuoi creare delle pizze, degli antipasti, dei dolci,...
Ognuno di questi factory sarà internamente implementato come singolo Factory Method.
io l'ho reiterpretato così:
public interface IIdentificabile
{
Int32 ID
{
get;
set;
}
}
public static class FabbricaGenerica<T>() where T:IIdentificabile
{
private LinkedList<T> istanze = new LinkedList<T>();
public static T CreaIstanza(Int32 id)
{
T istanza = Activator.CreateInstance<T>();
istanza.ID = id;
FabbricaGenerica<T>.istanze.AddLast(istanza);
return istanza;
}
private static T CercaIstanza(Int32 id)
{
// Implementare l'algoritmo desiderato per cercare all'interno della LinkedList
}
public static T RecuperaIstanza(Int32 id)
{
T istanza = null;
istanza = FabbricaGenerica<T>.CercaIstanza(Int32 id);
if (istanza == null)
{
istanza = FabbricaGenerica<T>.CreaIstanza(id);
}
return istanza;
}
}
...che ne dite?
Consiglio per questo problema di non costruire un nuovo Generic, dato che in teoria se ne potrebbe fare a meno, sfruttando la sola ereditarieta' e polimorfismo.
Beh comunque vorrei chiarire che il problema delle pizze era solo per introdurre la questione e avere qualcosa su cui lavorare.
Il mio intento è capire come funzionano i pattern factory (method e abstract).
Se avete un esempio più calzante che chiarisca bene come funzionano questi pattern siete caldamente invitati a discuterne!!!
vBulletin® v3.6.4, Copyright ©2000-2025, Jelsoft Enterprises Ltd.