Prototype pattern

Da Wikipedia, l'enciclopedia libera.

Prototype (in italiano "prototipo") è un design pattern creazionale utilizzato in informatica nella programmazione orientata agli oggetti.

Prototype permette di creare nuovi oggetti clonando un oggetto iniziale, detto appunto prototipo. A differenza di altri pattern come Abstract factory o Factory method permette di specificare nuovi oggetti a tempo d'esecuzione (run-time), utilizzando un gestore di prototipi (prototype manager) per salvare e reperire dinamicamente le istanze degli oggetti desiderati.

Prototype è uno dei design pattern fondamentali definiti dalla cosiddetta gang of four.

Applicabilità[modifica | modifica sorgente]

Come altri pattern creazionali, ovvero che si occupano di istanziare oggetti, prototype mira a rendere indipendente un sistema dal modo in cui i suoi oggetti vengono creati.

Inoltre 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, oppure
  • per evitare di costruire una gerarchia di factory in parallelo a una gerarchia di prodotti, come avviene utilizzando Abstract factory e Factory method, oppure
  • 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.

Struttura[modifica | modifica sorgente]

Il seguente diagramma delle classi in UML è riferito a un semplice esempio in Java ma è facilmente applicabile a qualsiasi linguaggio orientato agli oggetti, come per esempio il C++.

Diagramma delle classi in UML per il pattern Prototype

Prototype[modifica | modifica sorgente]

Prototype definisce un'interfaccia per clonare sé stesso.

ConcretePrototype[modifica | modifica sorgente]

Le sottoclassi ConcretePrototype implementano l'interfaccia di Prototype, fornendo un'operazione per clonare sé stessi.

Client[modifica | modifica sorgente]

Client crea un nuovo oggetto del tipo desiderato chiedendo a un prototipo di clonarsi, ovvero invocando il metodo clone definito da ConcretePrototype.

Collaborazioni[modifica | modifica sorgente]

Lo schema delle collaborazioni è estremamente semplice: il cliente chiede a un prototipo di clonarsi, ottenendo una copia dell'oggetto desiderato.

Conseguenze[modifica | modifica sorgente]

Indipendenza dal metodo d'instanziazione[modifica | modifica sorgente]

Come i 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[modifica | modifica sorgente]

Prototype è più flessibile di altri pattern creazionali, perché l'aggiunta di un prodotto richiede semplicemente la registrazione dell'oggetto da clonare in un gestore di prototipi (prototype manager), descritto nella sezione implementazione. Questa caratteristica permette a un Client di aggiungere un prodotto a tempo d'esecuzione, e di renderlo disponibile per la clonazione ad altri Client.

Definire nuovi oggetti modificando valori[modifica | modifica sorgente]

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.

Per esempio un editor di spartiti musicali potrebbe istanziare un solo prototipo di nota, clonarlo e impostare altezza e durata invece di definire una classe per ogni nota.

Definire nuovi oggetti modificando la struttura[modifica | modifica sorgente]

Nelle applicazioni che aggregano oggetti diversi in strutture composte, magari utilizzando i pattern Composite o Decorator, l'utilizzo di prototipi può semplificare la gestione e la generazione di parti e sottoparti.

Per esempio, un programma di grafica vettoriale potrebbe permettere all'utente di salvare composizioni di oggetti per poi poterne generare copie al bisogno.

Minore necessità di sottoclassi[modifica | modifica sorgente]

Il pattern Prototype permette di risolvere un problema di Factory method 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.

Usando i prototipi non sono necessarie né una classe factory, né la gerarchia di classi associata ai prodotti: i nuovi oggetti vengono istanziati e inizializzati variando valori interni e struttura, come spiegato precedentemente.

Difficoltà legate alla clonazione[modifica | modifica sorgente]

L'unica difficoltà del pattern Prototype potrebbe risiedere nell'implementazione dell'operazione clone. Il metodo clone deve comportarsi come una copia in profondità (deep copy), in modo che la copia di un oggetto composto implichi la copia delle sue sottoparti.

Poiché molti linguaggi di programmazione utilizzando una copia semplice (shallow copy), l'operazione clone deve essere ridefinita dal programmatore e ciò può risultare particolarmente complesso in presenza di strutture dati con riferimenti circolari o nel caso alcuni oggetti non permettano la copia.

Implementazione[modifica | modifica sorgente]

L'implementazione del pattern Prototype può comprendere diverse necessità, come l'utilizzo di un gestore di prototipi, la definizione di un'operazione di copia in profondità e l'inizializzazione degli oggetti appena clonati.

Utilizzo di un gestore di prototipi[modifica | modifica sorgente]

Se il numero di prototipi utilizzati può variare a tempo d'esecuzione perché i Client possono registrarne di nuovi, è necessario l'utilizzo di un gestore (prototype manager) che si occupi di gestire i prototipi invece dei clienti. Un Client non utilizzarà i prototipi direttamente, ma li salverà e li recupererà utilizzando il gestore.

Il gestore di prototipi utilizza una struttura dati associativa, come per esempio una mappa (map), che permetta di identificare un prototipo a partire da una data chiave (key). Tra le varie operazioni che può definire le più basilari sono l'inserimento di un prototipo associato a una chiave, l'eliminazione di un prototipo a partire dalla chiave data e la possibilità di generare l'elenco delle chiavi memorizzate perché sia consultabile a tempo d'esecuzione.

Implementazione dell'operazione di copia[modifica | modifica sorgente]

L'implementazione dell'operazione clone è la parte più delicata del pattern. Nella grande maggioranza dei casi gli oggetti copiati devono essere indipendenti uno dall'altro, l'originale dalla copia. Ciò può portare a problemi nel caso di composizioni di oggetti.

Se gli oggetti sono semplici è sufficiente utilizzare qualche costrutto del linguaggio che permetta la copia di aree di memoria. C++ fornisce un costrutto per la copia, Java definisce un'operazione clone nell'oggetto predefinito Object e qualunque altro oggetto la eredita automaticamente.

Tuttavia, quando un oggetto contiene riferimenti interni ad altri oggetti, copiare l'oggetto contenitore utilizzando una copia semplice non clona anche gli oggetti contenuti, ma solo i loro riferimenti. Questo provoca una situazione di aliasing, ovvero di riferimenti multipli a un oggetto: poiché solo l'oggetto contenitore è stato duplicato, gli oggetti contenuti sono raggiungibili sia dall'originale che dalla copia, rendendo copia e originale non indipendenti tra loro.

Per risolvere questo problema è necessaria una copia in profondità, ovvero un'operazione di clonazione che duplichi l'oggetto su cui è invocata e che chiami le operazioni clone di tutti gli oggetti di cui esiste un riferimento interno, che a loro volta si devono comportare analogamente per garantire la copia di tutta la struttura.

Nel caso di riferimenti circolari è necessario un meccanismo aggiuntivo per evitare di clonare più volte lo stesso oggetto.

Inizializzazione delle copie[modifica | modifica sorgente]

Alcuni Client potrebbero avere la necessità di ricevere copie già inizializzate di prototipi, ovvero oggetti già impostati con i valori desiderati.

Poiché l'operazione clone deve avere una signature uniforme, mentre le richieste di inizializzazione dei Client possono richiedere i più disparati parametri, è impossibile sfruttare gli argomenti del metodo di clonazione per specificare lo stato del nuovo oggetto.

Se non si vuole esporre dei metodi di manipolazione dell'oggetto per permettere al Client di impostare direttamente i valori dopo aver ricevuto la copia, è possibile definire un'operazione Initialize ("inizializza") negli oggetti prototipo che riceva gli argomenti necessari per impostare lo stato dell'oggetto dopo la copia.

Esempi[modifica | modifica sorgente]

Python[modifica | modifica sorgente]

import copy
 
#
# Prototype Class
#
class Cookie:
    def __init__(self, name):
        self.name = name
 
    def clone(self):
        return copy.deepcopy(self)
 
#
# Concrete Prototypes to clone
#
class CoconutCookie(Cookie):
    def __init__(self):
        Cookie.__init__(self, 'Coconut')
 
#
# Client Class
#
class CookieMachine:
    def __init__(self, cookie):
        self.cookie = cookie
 
    def make_cookie(self):
        return self.cookie.clone()
 
if __name__ == '__main__':
    prot = CoconutCookie()
    cm = CookieMachine(prot)
 
    for i in xrange(10):
        temp_cookie = cm.make_cookie()

C++[modifica | modifica sorgente]

#include <iostream>
#include <map>
#include <string>
 
using namespace std;
 
enum RECORD_TYPE_en
{
  CAR,
  BIKE,
  PERSON
};
 
typedef unsigned int u_int32_t;
 
/**
 * Record is the Prototype
 */
 
class Record
{
  public :
 
    Record() {}
 
    ~Record() {}
 
    virtual Record* Clone()=0;
 
    virtual void Print()=0;
};
 
/**
 * CarRecord is Concrete Prototype
 */
 
class CarRecord : public Record
{
  private :
    string m_oStrCarName;
 
    u_int32_t m_ui32ID;
 
  public :
 
    CarRecord(string _oStrCarName,u_int32_t _ui32ID)
      : Record(), m_oStrCarName(_oStrCarName),
        m_ui32ID(_ui32ID)
    {
    }
 
    CarRecord(CarRecord& _oCarRecord)
      : Record()
    {
      m_oStrCarName = _oCarRecord.m_oStrCarName;
      m_ui32ID = _oCarRecord.m_ui32ID;
    }
 
    ~CarRecord() {}
 
    CarRecord* Clone()
    {
      return new CarRecord(*this);
    }
 
    void Print()
    {
      cout << "Car Record" << endl
        << "Name  : " << m_oStrCarName << endl
        << "Number: " << m_ui32ID << endl << endl;
    }
};
 
/**
 * BikeRecord is the Concrete Prototype
 */
 
class BikeRecord : public Record
{
  private :
    string m_oStrBikeName;
 
    u_int32_t m_ui32ID;
 
  public :
    BikeRecord(string _oStrBikeName,u_int32_t _ui32ID)
      : Record(), m_oStrBikeName(_oStrBikeName),
        m_ui32ID(_ui32ID)
    {
    }
 
    BikeRecord(BikeRecord& _oBikeRecord)
      : Record()
    {
      m_oStrBikeName = _oBikeRecord.m_oStrBikeName;
      m_ui32ID = _oBikeRecord.m_ui32ID;
    }
 
    ~BikeRecord() {}
 
    BikeRecord* Clone()
    {
      return new BikeRecord(*this);
    }
 
    void Print()
    {
      cout << "Bike Record" << endl
        << "Name  : " << m_oStrBikeName << endl
        << "Number: " << m_ui32ID << endl << endl;
    }
};
 
/**
 * PersonRecord is the Concrete Prototype
 */
 
class PersonRecord : public Record
{
  private :
    string m_oStrPersonName;
 
    u_int32_t m_ui32Age;
 
  public :
    PersonRecord(string _oStrPersonName, u_int32_t _ui32Age)
      : Record(), m_oStrPersonName(_oStrPersonName),
        m_ui32Age(_ui32Age)
    {
    }
 
    PersonRecord(PersonRecord& _oPersonRecord)
      : Record()
    {
      m_oStrPersonName = _oPersonRecord.m_oStrPersonName;
      m_ui32Age = _oPersonRecord.m_ui32Age;
    }
 
    ~PersonRecord() {}
 
    Record* Clone()
    {
      return new PersonRecord(*this);
    }
 
    void Print()
    {
      cout << "Person Record" << endl
        << "Name : " << m_oStrPersonName << endl
        << "Age  : " << m_ui32Age << endl << endl ;
    }
};
 
 
/**
 * RecordFactory is the client
 */
 
class RecordFactory
{
  private :
    map<RECORD_TYPE_en, Record* > m_oMapRecordReference;
 
  public :
    RecordFactory()
    {
      m_oMapRecordReference[CAR]    = new CarRecord("Ferrari", 5050);
      m_oMapRecordReference[BIKE]   = new BikeRecord("Yamaha", 2525);
      m_oMapRecordReference[PERSON] = new PersonRecord("Tom", 25);
    }
 
    ~RecordFactory()
    {
      delete m_oMapRecordReference[CAR];
      delete m_oMapRecordReference[BIKE];
      delete m_oMapRecordReference[PERSON];
    }
 
    Record* CreateRecord(RECORD_TYPE_en enType)
    {
      return m_oMapRecordReference[enType]->Clone();
    }
};
 
int main()
{
  RecordFactory* poRecordFactory = new RecordFactory();
 
  Record* poRecord;
  poRecord = poRecordFactory->CreateRecord(CAR);
  poRecord->Print();
  delete poRecord;
 
  poRecord = poRecordFactory->CreateRecord(BIKE);
  poRecord->Print();
  delete poRecord;
 
  poRecord = poRecordFactory->CreateRecord(PERSON);
  poRecord->Print();
  delete poRecord;
 
  delete poRecordFactory;
  return 0;
}

C#[modifica | modifica sorgente]

 public enum RecordType
 {
    Car,
    Person
 }
 
 /// <summary>
 /// Record is the Prototype
 /// </summary>
 public abstract class Record
 {
    public abstract Record Clone();
 }
 
 /// <summary>
 /// PersonRecord is the Concrete Prototype
 /// </summary>
 public class PersonRecord : Record
 {
    string name;
    int age;
 
    public override Record Clone()
    {
       return (Record)this.MemberwiseClone(); // default shallow copy
    }
 }
 
 /// <summary>
 /// CarRecord is another Concrete Prototype
 /// </summary>
 public class CarRecord : Record
 {
    string carname;
    Guid id;
 
    public override Record Clone()
    {
       CarRecord clone = (CarRecord)this.MemberwiseClone(); // default shallow copy
       clone.id = Guid.NewGuid(); // always generate new id
       return clone;
    }
 }
 
 /// <summary>
 /// RecordFactory is the client
 /// </summary>
 public class RecordFactory
 {
    private static Dictionary<RecordType, Record> _prototypes =
       new Dictionary<RecordType, Record>();
 
    /// <summary>
    /// Constructor
    /// </summary>
    public RecordFactory()
    {
       _prototypes.Add(RecordType.Car, new CarRecord());
       _prototypes.Add(RecordType.Person, new PersonRecord());
    }
 
    /// <summary>
    /// The Factory method
    /// </summary>
    public Record CreateRecord(RecordType type)
    {
       return _prototypes[type].Clone();
    }
 }

Java[modifica | modifica sorgente]

 /** Prototype Class **/
 public class Cookie implements Cloneable {
 
    public Object clone()
    {
        try{
            Cookie copy = (Cookie)super.clone();
 
            //In an actual implementation of this pattern you might now change references to
            //the expensive to produce parts from the copies that are held inside the prototype.
 
            return copy;
        }
        catch(CloneNotSupportedException e)
        {
           e.printStackTrace();
           return null;
        }
    }
 }
 
 /** Concrete Prototypes to clone **/
 public class CoconutCookie extends Cookie { }
 
 /** Client Class**/
 public class CookieMachine
 {
 
   private Cookie cookie;//could have been a private Cloneable cookie; 
 
     public CookieMachine(Cookie cookie) { 
         this.cookie = cookie; 
     } 
     public Cookie makeCookie() { 
       return (Cookie)cookie.clone(); 
     } 
     public Object clone() { } 
 
     public static void main(String args[]){ 
         Cookie tempCookie =  null; 
         Cookie prot = new CoconutCookie(); 
         CookieMachine cm = new CookieMachine(prot); 
         for(int i=0; i<100; i++) 
             tempCookie = cm.makeCookie(); 
     } 
 }

Voci correlate[modifica | modifica sorgente]

Altri progetti[modifica | modifica sorgente]