Il Factory Method Γ¨ uno dei design pattern fondamentali per lβimplementazione del concetto di factories. Come altri pattern creazionali, esso indirizza il problema della creazione di oggetti senza specificarne lβesatta classe. Lβidea Γ¨ utilizzare un metodo factory che ritorna una interfaccia dellβoggetto che voglio creare invece di utilizzare direttamente il costruttore della classe. In questo modo la superclasse o il chiamante utilizzerΓ lβoggetto senza saperne il suo tipo specifico ma solo la sua interfaccia. Usare metodi factory spesso non Γ¨ una grande idea in quanto rompe la Principi SOLID. Liskov Substitution Principle.
Rapporto con altri pattern creazionali
Il Factory Γ¨ il pattern creazionale piΓΉ semplice e tendenzialmente si parte sempre da quello. Se il pattern genera troppe sottoclassi spesso conviene migrare verso lβAbstract Factory
, il Pattern Prototype o il Pattern Builder, che sono piΓΉ flessibili ma leggermente piΓΉ complessi.
Factory vs Abstract Factory
Spesso cβΓ¨ confusione sulla differenza tra i due pattern e quando usare uno invece che lβaltro.
Un primo punto fondamentale Γ¨ che nel Factory
ho un solo metodo, chiamato metodo factory, in una classe che potenzialmente puΓ² fare anche altro; lβAbstract Factory
invece prevede la creazione di piΓΉ classi complesse.
- Il metodo Factory si usa per creare un solo prodotto, lβAbstract Factory invece si occupa di creare famiglie di oggetti o comunque oggetti legati tra loro;
- Nel
Factory
il metodo che costruisce la classe Γ¨ un normale metodo (da overridare in una classe figlia per specificare lβistanza da creare), mentre nellβAbstract Factory
viene ddelegata tramite composizione ad una classe terza; Il primo usa quindi lβereditarietΓ mentre il secondo la composizione. - Tipicamente una classe contenente un metodo Factory puΓ² contenere anche altri metodi, non serve solo a creare oggetti. Una classe factory dellβ
Abstract Factory
invece serve solo a creare oggetti; - Il
Factory
nasconde la costruzione di un singolo oggetto, mentre lβAbstract Factory
di una serie di oggetti;
Factory
internal abstract class A
{
public abstract IProduct FactoryMethod();
public string SomeOperation()
{
var product = FactoryMethod();
return product.Operation();
}
}
internal class B : A
{
public override IProduct FactoryMethod()
{
return new ConcreteProduct1();
}
}
Abstract Factory
internal abstract class A
{
private Factory factory;
public A(Factory factory)
{
_factory = factory;
}
public string SomeOperation()
{
var product = _factory.FactoryMethod();
return product.Operation();
}
}
Esempio 1 - EreditarietΓ
Nellβesempio seguente abbiamo la creazione di oggetti IProduct
, in particolare ConcreteProduct1
e ConcreteProduct2
.
La classe che li crea Γ¨ la classe Creator
che definisce solo il metodo astratto, saranno le sue figlie a indicarne la classe concreta.
/// <summary>
/// Il metodo Factory fornisce un'interfaccia per creare un oggetto, ma lascia che le sottoclassi decidano quale
/// oggetto istanziare. La creazione dell'oggetto avviene tramite ereditaritΓ .
/// </summary>
internal abstract class Creator
{
/// <summary>
/// Metodo principale della classe che si occupa di creare l'istanza di <see cref="IProduct" />.
/// </summary>
public abstract IProduct CreateProduct();
/// <summary>
/// Potenzialmente il creator puΓ² anche eseguire delle operazioni sulla classe appena creata (un po' alla Builder)
/// </summary>
public string SomeOperation()
{
return CreateProduct().Operation();
}
}
/// <summary>
/// Factory concreta, overrida il metodo per creare <see cref="ConcreteProduct1" />
/// </summary>
internal class ConcreteCreator1 : Creator
{
public override IProduct CreateProduct()
{
return new ConcreteProduct1();
}
}
/// <summary>
/// Factory concreta, overrida il metodo per creare <see cref="ConcreteProduct2" />
/// </summary>
internal class ConcreteCreator2 : Creator
{
public override IProduct CreateProduct()
{
return new ConcreteProduct2();
}
}
/// <summary>
/// Oggetto generico che deve essere creato, fornisce le operazioni che tutti i prodotti concreti devono avere
/// </summary>
public interface IProduct
{
string Operation();
}
/// <summary>
/// Ho varie tipologie di prodotti concreti
/// </summary>
internal class ConcreteProduct1 : IProduct
{
public string Operation()
{
return "{Result of ConcreteProduct1}";
}
}
internal class ConcreteProduct2 : IProduct
{
public string Operation()
{
return "{Result of ConcreteProduct2}";
}
}
Client
Utilizzare il metodo Factory viene utilizzato principalmente perchè così il client non istanzia gli oggetti effettivi e non conosce nemmeno come sono fatti: delega alla factory la creazione e poi il client si occupa solo di creare gli oggetti specifici.
var product1 = new ConcreteCreator1().CreateProduct();
var product2 = new ConcreteCreator2().CreateProduct();
Console.WriteLine($"Product made by concrete creator 1: {product1.Operation()}"); // Product made by concrete creator 1: {Result of ConcreteProduct1}
Console.WriteLine($"Product made by concrete creator 2: {product2.Operation()}"); // Product made by concrete creator 2: {Result of ConcreteProduct2}
Esempio 2 - Switch Case
Una versione semplificata Γ¨ quella in cui non ho factory figlie della factory astratta principale ma Γ¨ tutto racchiuso un un metodo e la decisione se istanziare la classe A o B Γ¨ allβinterno di uno switch case. Un esempio banale Γ¨ il seguente:
internal class CreatorWithSwitchCase
{
public IProduct CreateProduct(string type)
{
switch (type)
{
case "Concrete1": return new ConcreteProduct1();
case "Concrete2": return new ConcreteProduct2();
default: throw new ArgumentException("type");
}
}
}
Client
Il client chiamerΓ la factory e in base al parametro in ingresso verrΓ fornita una istanza o lβaltra.
var factory = new CreatorWithSwitchCase();
var product1 = factory.CreateProduct("Creator1");
var product2 = factory.CreateProduct("Creator2");
Rider Live template
Allego il live template per utilizzare questo pattern in Rider o Visual Studio con Resharper.
Transclude of Factory-Rider-live-template.txt