Java Design Pattern: Prototype Pattern

Quando si può anche copiare con classe



Andremo ora a parlare di un pattern creazionale che ci permette di "copiare con classe". Sì, anche se sembra strano, il compito fondamentale di questo pattern è copiare. Sto parlando del Prototype Pattern.

Chiariamo subito questo concetto.

Quando si parla di creazione di oggetti, il nostro immaginario comune da programmatori ci fa pensare ad operatori new ed in particolar modo pensiamo subito al miglior modo di definire un costruttore adeguato alla creazione dell'istanza in modo da fornire al client un meccanismo di creazione.

Il Prototype Pattern ci permette di costruire oggetti attraverso la clonazione di una classe presa come esempio.

Dando una definizione:

Il Prototype Pattern crea oggetti sulla base di un oggetto esistente mediante la clonazione.

Quando uso il Prototype Pattern?

  • quando la composizione, la creazione e la rappresentazione dell'oggetto devono necessariamente essere separate;
  • quando viene specificato quale classe creare a runtime;
  • quando c'è la necessità di nascondere la complessità della creazione dell'oggetto al client;
  • quando creare un oggetto è un'operazione alquanto complessa e quindi copiarlo è più conveniente;
  • quando gli oggetti necessari sono simili a quelli esistenti;
  • si vuole evitare la creazione di tante factory parallele come succede in Abstract Factory;

Diagramma UML

Prototype Pattern UML

Capiamo l'UML

  1. Prototype: è la classe astratta che definisce il comportamento delle classi che la useranno come esempio. Deve esporre un metodo per copiarla;
  2. ConcretePrototype: definisce le classi concrete che estendono Prorotype;
  3. Client: si occupa di richiamare il metodo di clonazione sui ConcretePrototype;

Esempio

Andremo ora a definire un piccolo esempio che si occupa di popolare e gestire un'hashmap di computer che possono essere desktop, laptop e server.

TipoComputer.java

public enum TipoComputer {
DESKTOP, LAPTOP, SERVER
}

Abbiamo definito un'enum che utilizzeremo per identificare i tre tipi di computer. E' stata definita per comodità e non è indispensabile.

ComputerPrototype.java

public abstract class ComputerPrototype implements Cloneable {
public String modello;
public ComputerPrototype(String modello) {
this.modello = modello;
}
public String getModello() {
return modello;
}
public void setModello(String modello) {
this.modello = modello;
}
@Override
public ComputerPrototype clone(){
try{
return (ComputerPrototype) super.clone();
}catch(CloneNotSupportedException e){
System.out.println("Something went wrong!");
return null;
}
}
public abstract void printModel();
}

Questa è la classe che rappresenta il Prototype ed espone un metodo per poter clonare le classi che la estenderanno. Il design originale di questo pattern creazionale prevede il fatto che venga definito appunto un metodo per la clonazione degli oggetti. In Java questo meccanismo è fornito dal metodo clone() della superclasse Object.
Talvolta viene sconsigliato l'utilizzo di questo metodo, ma cercando di astrarre nella maniera migliore possibile il concetto di Prototype Pattern è possibile rendere la classe astratta al più sottoclasse di Object e quindi sfruttare senza problemi il metodo di clonazione. Infatti i problemi sorgono quando le gerarchie di classi sono piuttosto lunghe.

Desktop.java

public class Desktop extends ComputerPrototype {

    public Desktop(String modello) {
        super(modello);
    }

    @Override
    public void printModel() {
        System.out.println(modello);
    }
}

Laptop.java

public class Laptop extends ComputerPrototype {

    public Laptop(String modello) {
        super(modello);
    }

    @Override
    public void printModel() {
        System.out.println(modello);
    }
}

Server.java

public class Server extends ComputerPrototype {

    public Server(String modello) {
        super(modello);
    }

    @Override
    public void printModel() {
        System.out.println(modello);
    }
}

Queste tre classi rappresentano le entità concrete che verranno realmente copiate.

Client.java

import java.util.HashMap;
import java.util.Map;

public class Client {
    private Map<Enum,ComputerPrototype> computers = new HashMap<>();

    public ComputerPrototype getComputer(TipoComputer tipo){
        ComputerPrototype computer=computers.get(tipo);
        if(computer==null){
            return null;
        }
        return computer.clone();
    }

    public void populateMap(){
        computers.put(TipoComputer.DESKTOP, new Desktop("MSI"));
        computers.put(TipoComputer.LAPTOP, new Laptop("Asus"));
        computers.put(TipoComputer.SERVER, new Server("Microsoft"));
    }
}

In questa classe è definito un oggetto map che contiene oggetti di tipo ComputerPrototype identificati da un membro dell'enum dando origine così alla coppia <Enum, ComputerPrototype>.

Il metodo populateMap() popola la mappa con tre oggetti, uno per ogni elemento dell'enum. Il metodo getComputer() invece restituisce la copia di un oggetto che stiamo cercando in base al suo identificatore nell'hashmap se e solo se esiste, altrimenti restituisce null.

Main.java

public class Main {
    public static void main(String[] args){

        Client client=new Client();
        client.populateMap();

        ComputerPrototype desktop=client.getComputer(TipoComputer.DESKTOP);
        System.out.println(desktop.getModello());
        desktop.printType();
        ComputerPrototype server = client.getComputer(TipoComputer.SERVER);
        System.out.println(server.getModello());
        server.printType();

    }
}

Nel main ci limitiamo a creare un costruttore e a popolare l'hashmap. Dopodichè sfruttiamo i metodi definiti nel client per estrarre dati dalla mappa.

Osservazioni

Prima di tutto, precisiamo subito che l'esempio non è particolarmente significativo in quanto a funzionalità, ma si presta bene a mostrare la struttura del pattern.

Il metodo clone è sovrascritto rispetto a quello della classe Object in modo da fornire una copia adatta alle nostre esigenze, copiando l'oggetto nella maniera che riteniamo più opportuna. Nell'esempio ho sovrascritto il metodo andando a controllare l'esistenza dell'oggetto estratto. In base al risultato del controllo viene restituita una copia dell'oggetto estratto se esiste, null altrimenti.

Una cosa importante da capire, e questo discorso vale per qualsiasi design pattern, è che nella stragrande maggioranza dei casi non sono legati al linguaggio. Infatti in questo particolare caso si sfrutta il metodo clone() della classe Object, ma in un ipotetico linguaggio X che non dispone di questo metodo, il pattern è comunque applicabile usando un qualsiasi modo, purchè efficace, di copiare l'oggetto.  L'unica limitazione evidente è che il linguaggio deve supportare l'OOP, in caso contrario non è il pattern specifico a non essere applicabile, ma è proprio la logica dei design patterns a non essere fruibile.

Conclusioni

Abbiamo visto come il Prototype Pattern permetta di creare istanze di oggetti a partire da istanze esistenti evitando soprattutto la costruzione di factory parallele.

Un'applicazione che ho trovato interessante, è quella in cui le classi vengono create a runtime. Utilizzando un meccanismo di RTTI insieme ad un corretto uso di Prototype siamo in grado di sviluppare applicazioni che creino ed utilizzino dinamicamente le istanze.

Il meccanismo RTTI è un meccanismo che permette di determinare il tipo di oggetto da creare durante l'esecuzione, senza la necessità di saperlo prima.

Attenzione, il meccanismo di RTTI deve essere usato con cautela, perchè si potrebbe sfruttare per caricare un ConcretePrototype con codice malevolo.

Per qualsiasi approfondimento, consiglio la lettura del testo sacro dei design patterns: Design Patterns: Elements of Reusable Object-Oriented Software by Erich Gamma, John Vlissides, Ralph Johnson, and Richard Helm.

 
 
 
 
 

Articoli correlati

    Java algoritmi di ordinamento: Merge Sort

    Andiamo oggi ad analizzare uno tra i migliori algoritmi di ordinamento: il Merge Sort. Detto anche algoritmo per fusione, fa parte della famiglia dei Divide and Conquer proprio come il Quick Sort. A differenza del…

    Java algoritmi di ordinamento: Quick Sort

    Bentornati in questa nostra panoramica sul mondo Java! Oggi andremo a parlare di un algoritmo di ordinamento tra i più celebri. Il Quick Sort. A differenza del precedentemente trattato Bubble Sort, Quick…

    Flexbox, breve guida pratica

    In questo articolo impareremo a gestire gli elementi del nostro sito web in modo più efficiente ed efficace, grazie alla proprietà Flexbox. Ma attenzione, non stiamo parlando di una semplice proprietà…

    Come creare un aplicazione Vue.js in 5 minuti

    Vue.js sta acquisendo sempre più popolarità, diventando un importante concorrente di framework come Angular o React.js. Come framework per i principianti, conquista i cuori dei giovani sviluppatori front-end e delle…

    Java algoritmi di ordinamento: Bubble Sort

    Programmando, nasce spesso la necessità di ordinare le collezioni di dati o oggetti che devono poi essere manipolate. Ordinare una lista può essere utile nei casi in cui si debbano…

    Java Design Pattern: Builder Pattern

    Andiamo oggi a parlare di un pattern creazionale che in molte situazioni può rappresentare una valida alternativa alla costruzione di oggetti mediante costruttori: il Builder Pattern. La necessità di introdurre meccanismi…

    Java Design Pattern: Strategy Pattern

    Uno dei pattern che gode di una notevole popolarità ed è al contempo piuttosto semplice è lo Strategy Pattern. Membro della famiglia dei pattern comportamentali, ha il compito di gestire algoritmi,…

    Java Design Pattern: Factory Pattern

    Continuando il discorso sui design pattern iniziato precedentemente, andiamo oggi a vedere un altro pattern molto utilizzato: il Factory Method Pattern. Il GoF (Gang of Four Design Patterns) lo definisce così: Definisce…

    Java: Introduzione ai design pattern: Singleton

    Chiunque abbia anche una minima esperienza di programmazione, si sarà reso conto di come i problemi sianoricorrenti. Infatti troviamo spesso problemi con uno stesso pattern ma con contesti differenti. Ad esempio, un…

    Java 12, finalmente meno prolisso?

    Conosciamo tutti Java per le sue caratteristiche grazie alle quali, nonostante siano passati più di 20 anni dalla prima versione,è tutt'oggi uno dei linguaggi più studiati e più utilizzati, malgrado…

    Wi-Fi 6, il Wi-Fi orientato anche all'IoT

    Nel terzo trimestre del 2019, la Wi-Fi Alliance ha finalmente deciso di rilasciare la nuova versione del protocollo Wi-Fi, innovativo sin dal nome. Infatti, se tutti gli standard precedenti avevano nomi pressochè…

    JQuery morirà nel 2019?

    Per un po' di tempo, la centralità di JQuery è stata oggetto di dibattito tra gli sviluppatori web. Come programmatori web interessati a Javascript, eravamo curiosi di sapere che opinioni…