Il design pattern prototype Γ¨ uno dei pattern creazionali fondamentali introdotti dalla Gang of Four e permette di creare nuovi oggetti clonando un oggetto iniziale, detto appunto prototipo.
A differenza di altri pattern come Abstract factory o Factory permette di specificare nuovi oggetti a tempo dβesecuzione (run-time), utilizzando una PrototypeFactory
per salvare e reperire dinamicamente le istanze degli oggetti desiderati.
Questo pattern puΓ² rivelarsi utile quando:
- le classi da istanziare sono specificate solamente a tempo d'esecuzione, per cui un codice statico non puΓ² occuparsi della creazione dellβoggetto;
- quando ==le istanze di una classe possono avere soltanto un limitato numero di stati==, per cui puΓ² essere piΓΉ conveniente clonare al bisogno il prototipo corrispondente piuttosto che creare lβoggetto e configurarlo ogni volta. I vantaggi nel suo utilizzo sono i seguenti:
- Indipendenza dal metodo dβinstanziazione: analogamente ai pattern Abstract factory e Builder, Prototype permette di incapsulare al suo interno la modalitΓ di istanziazione degli oggetti, liberando i Client dalla necessitΓ di conoscere i nomi delle classi da instanziare.
- ModularitΓ a run-time: lβaggiunta di un prodotto richiede semplicemente la **registrazione dellβoggetto da clonare alla
PrototypeFactory
. - Definire nuovi oggetti modificando valori: Quando si devono definire numerosi oggetti differenziati tra loro solo dai valori che assumono le loro variabili interne Γ¨ piΓΉ comodo istanziare nuovi oggetti semplicemente clonando un prototipo iniziale e successivamente impostare la rappresentazione interna perchΓ© assuma la configurazione desiderata.
- Minore necessitΓ di sottoclassi: Il pattern Prototype permette di risolvere un problema di Factory relativo alla dimensione della gerarchia di classi necessarie: usando un metodo factory Γ¨ necessario creare sottoclassi per inserire un nuovo prodotto e, se si hanno numerosi prodotti molto simili tra di loro, la definizione di una nuova classe per ognuno puΓ² portare a grandi quantitΓ di codice duplicato.
La difficoltΓ principale nellβutilizzo del pattern Γ¨ lβimplementazione del metodo Clone
in quanto deve comportarsi come una copia in profonditΓ (deep copy): la copia di un oggetto composto implichi la copia delle sue sottoparti.
Title
In .NET il pattern Γ¨ disponibile utilizzando lβinterfaccia
ICloneable
.
Implementazione
Questo pattern Γ¨ formato dai seguenti oggetti:
Prototype
: Definisce unβinterfaccia per clonare sΓ© stesso.ConcretePrototype
: Le sottoclassiConcretePrototype
implementano lβinterfaccia diPrototype
, fornendo unβoperazione per clonare sΓ© stessi.Client
: crea un nuovo oggetto del tipo desiderato chiedendo a un prototipo di clonarsi, ovvero invocando il metodo clone definito daConcretePrototype
.
Ho creato lβinterfaccia IPrototype
che eredita da ICloneable
con i suoi prototype concreti e la classe factory.
/// <summary>
/// Interfaccia dell'oggetto che voglio clonare
/// </summary>
public interface IPrototype : ICloneable
{
/// <summary>
/// Guid: identificativo univoco di ogni singola istanza
/// </summary>
Guid Guid { get; }
/// <summary>
/// Property name public modificabile dall'esterno. Tipicamente verrΓ fornita una nuova istanza della classe e poi sarΓ
/// il chiamante
/// </summary>
string Name { get; set; }
}
/// <summary>
/// Oggetto concreto di tipo "Foo"
/// </summary>
public class ConcretePrototypeFoo : IPrototype
{
/// <summary>
/// Stringa costante interna
/// </summary>
private const string Status = "Foo Status";
/// <summary>
/// Identificativo univoco che cambia ad ogni clone
/// </summary>
public Guid Guid { get; private set; }
/// <summary>
/// Property modificabile dall'esterno
/// </summary>
public string Name { get; set; } = "Foo Name";
/// <summary>
/// Effettuo il Clone modificando il Guid interno
/// </summary>
public object Clone()
{
var clone = (ConcretePrototypeFoo)MemberwiseClone();
clone.Guid = Guid.NewGuid();
return clone;
}
public override string ToString()
{
return $"Name: {Name} - Status: {Status} - Guid: {Guid}";
}
}
/// <summary>
/// Oggetto concreto di tipo "Bar"
/// </summary>
public class ConcretePrototypeBar : IPrototype
{
/// <summary>
/// Stringa costante interna
/// </summary>
private const string Status = "Bar Status";
/// <summary>
/// Identificativo univoco che cambia ad ogni clone
/// </summary>
public Guid Guid { get; private set; }
/// <summary>
/// Property modificabile dall'esterno
/// </summary>
public string Name { get; set; } = "Bar Name";
/// <summary>
/// Effettuo il Clone modificando il Guid interno
/// </summary>
public object Clone()
{
var clone = (ConcretePrototypeBar)MemberwiseClone();
clone.Guid = Guid.NewGuid();
return clone;
}
public override string ToString()
{
return $"Name: {Name} - Status: {Status} - Guid: {Guid}";
}
}
/// <summary>
/// Factory che permette la creazioni di classi di tipo <see cref="IPrototype" /> clonando il prototype base ogni volta
/// che viene richiesta una nuova istanza. Invece di creare ogni volta una classe da zero parto giΓ da un prototipo con
/// tutti i suoi campi e poi eventualmente effettuo delle modifiche alle property che mi servono
/// </summary>
public class PrototypeFactory
{
/// <summary>
/// Dizionario contenente le istanze base dei prototipi che posso creare. Quando mi viene richiesto un nuovo prototipo
/// parto da questi e ne effettuo il Clone() senza che il mondo esterno se ne accorga
/// </summary>
private static readonly IDictionary<RecordType, IPrototype> Prototypes = new Dictionary<RecordType, IPrototype>();
/// <summary>
/// Costruttore: crea le istanze di tutti i prototipi che poi andrΓ² a creare
/// </summary>
public PrototypeFactory()
{
Prototypes.Add(RecordType.Foo, new ConcretePrototypeFoo());
Prototypes.Add(RecordType.Bar, new ConcretePrototypeBar());
}
/// <summary>
/// Fornisce una nuova istanza di una classe di tipo <see cref="IPrototype" /> partendo dal <paramref name="type" />
/// passato in ingresso
/// </summary>
public IPrototype CreatePrototype(RecordType type)
{
return Prototypes[type].Clone() as IPrototype;
}
}
/// <summary>
/// Tipologie di classi <see cref="IPrototype" /> che posso creare
/// </summary>
public enum RecordType
{
Foo,
Bar
}
Chiamiamo ora il pattern dallβesterno:
var prototypeFactory = new PrototypeFactory();
var foo = prototypeFactory.CreatePrototype(RecordType.Foo);
Console.WriteLine($"Creo il prototype Foo: {foo}");
foo = prototypeFactory.CreatePrototype(RecordType.Foo);
foo.Name = "NewName";
Console.WriteLine($"A partire dal clone dello standard ci cambio nome (e guid): {foo}");
Console.WriteLine($"Creo il prototype Bar: {prototypeFactory.CreatePrototype(RecordType.Bar)}");
Console.WriteLine($"Creo un secondo prototype Bar clonando il primo: {prototypeFactory.CreatePrototype(RecordType.Bar)}");
Che mostra questo:
Creo il prototype Foo: Name: Foo Name - Status: Foo Status - Guid: fb61fb21-4cea-45d0-ab2e-c0c1fc5e843e
A partire dal clone dello standard ci cambio nome (e guid): Name: NewName - Status: Foo Status - Guid: 327baa46-e4a8-4a25-8203-675a5834
2ca0
Creo il prototype Bar: Name: Bar Name - Status: Bar Status - Guid: 82a20aa0-71d8-423c-a8ee-8f16a8316cd0
Creo un secondo prototype Bar clonando il primo: Name: Bar Name - Status: Bar Status - Guid: 05b09aa8-990d-4d69-820d-6585f85944d6