Lavorare con i design pattern

I design pattern sono approcci condivisi e ben definiti per la risoluzione di problemi nella programmazione, forniscono strumenti comuni agli sviluppatori per risolvere i problemi più comuni che si incontrano durante lo sviluppo di applicazioni.

Per la certificazione Java non è richiesta la conoscenza dei maggiori pattern, quali ad esempio Adapter, Strategy, Decorator, Visitor etc etc, l’esame di certificazione richiede solo la conoscenza ed il saper implementare 3 di questi, che sono : Sigleton, Builder e Factory che sono un sottoinsieme dei pattern detti creazionali.

Tutti sappiamo come creare oggetti in Java, tramite l’istruzione new :

Aereo md80 = new Md80();

Ma cosi, dobbiamo sapere esattamente che tipo di Aereo vogliamo creare, immaginiamo un torre di controllo, questa può creare diversi tipi di aereo a seconda dell’ordine di decollo, in questo caso dovremmo delegare la costruzione di oggetti in una logica ben precisa, oppure, in ogni aereoporto, vi è sempre e solo 1 torre di controllo operativa a gestire la coda di decollo, come possiamo garantire nella nostra applicazione, che sempre e solo 1 torre sia operativa ?

Singleton Pattern

Torniamo al problema della torre di controllo citata sopra, dobbiamo essere certi che solo 1 torre di controllo sia istanziata ed operativa, il pattern singleton fa proprio questo, ci garantisce di avere sempre e solo 1 istanza di un oggetto in memoria, vediamo un esempio pratico :

public class TorreDiControllo {
  private Queue<Aerei> codaDiDecollo;
  private TorreDiControllo() {}
  private static final TorreDiControllo instance = new TorreDiControllo();
  public static TorreDiControllo getInstance() {
    return instance;
  }
  public void autorizzaDecollo() {
    System.out.println("Decolla l'aereo : "+this.codaDiDecollo.poll());
  }
}

Vediamo in riassunto le particolarità di questa classe :

  • Ha un costruttore private per evitare di poter instanziare direttamente la classe
  • Ha una variabile di tipo static final di se stessa chiamata instance
  • Ha un metodo static per restituire la variabile instance
  • Avendo un costruttore private di fatto la rende final

L’utilizzo di un singleton nell’applicazione è decisamente intuitivo :

public class Aereoporto {
  private TorreDiControllo torre = TorreDiControllo.getInstance();
}

Possiamo richiamare in molte parti la torre di controllo, ma avremmo la certezza di utilizzare la stessa istanza in ogni punto dell’applicazione.

Analizziamo ora il nostro singleton, possiamo vedere che la variabile instance è stata inizializzata direttamente nella dichiarazione della variabile, ci sono altri modi per far questo, potremmo per esempio, utilizzare un inizializzatore static :

public class TorreDiControllo {
  private static final TorreDiControllo instance;
  static {
    instance = new TorreDiControllo();
  }
  ....
}

Questo altro modo è perfettamente equivalente al precedente, entrambe le istanze sono create durante il caricamento della classe, ma nel secondo caso, ci permette di poter gestire eventuali eccezioni durante la creazione dell’oggetto istanza cosa che non era possibile nel primo esempio.

Immaginiamo però che nella nostra applicazione abbiamo bisogno di moltissimi singleton che abbiano un tempo di caricamento molto lungo e che caricano molti dati dal database, avremmo in questo modo un tempo di avvio molto lungo con un consumo di risorse elevato anche se gli oggetti non sono mai effettivamente utilizzati.

Per ovviare a questo esiste un modo di istanziare i singleton , chiamato Lazy Inizialization

Consiste nel creare l’istanza della classe solo nel momento in cui viene utilizzata per la prima volta, vediamo un esempio :

public class TorreDiControllo {
  private static TorreDiControllo instance;
  private TorreDiControllo() { }
  public static TorreDiControllo getInstance() {
    if (instance == null)
      this.instance = new TorreDiControllo();
    return instance();
  }
  ..
}

Perfetto, ora l’istanza di TorreDiControllo verrà creata solo al primo utilizzo effettivo, ma attenzione, cosi come abbiamo scritto la nostra classe, non ci garantisce di avere sempre e solo 1 istanza, per prima cosa osserviamo che la variabile instance non è più final visto che viene instanziata all’interno del metodo non potrebbe esserlo, immaginiamo che due Thread invochino il metodo getInstance() nello stesso esatto momento, entrambe le chiamate troverebbero instance = null e creerebbero due oggetti, vediamo come ovviare a questo :

Una prima soluzione potrebbe essere quella di impostare il metodo getInstance come syncronized : 

public static syncronized TorreDiControllo getInstance() ...

Ora abbiamo risolto il problema di poter avere due istanze e abbiamo reso il nostro singleton thread-safe, ma abbiamo ancora un problema di performance, ogni chiamata al metodo getInstance richiederà la sincronizzazione, costando in termini di risorse, vediamo la soluzione definitiva :

public static volatile TorreDiControllo instance;
public static TorreDiControllo getInstance() {
  if (instace == null) {
    syncronized(TorreDiControllo.class) {
      if (instance == null) 
        instance = new TorreDiControllo();
    }
  }  
  return instance;
}

Abbiamo dichiarato la variabile instance come volatile per impedire al compilatore di ottimizzare l’accesso alla variabile, inoltre, la sincronizzazione è richiesta solo se la variabile instance è null

Creazione di classi immutabili

Come è possibile creare oggetti il cui stato non possa essere in nessun modo alterato dall’esterno ?

E’ possibile farlo tramite il pattern di immutabilità che ci consente di avere oggetti il cui stato non possa essere alterato, di fatto va a braccetto con l’incapsulamento visto in precedenza.

Vediamo la più comune strategia per rendere un oggetto immutabile :

  • Usare il costruttore per settare tutte le property
  • Impostare le property come private e final
  • Non definire alcun metodo setter
  • Non permettere ad property mutabili di essere accedute dall’esterno
  • Impedire l’override dei metodi

L’unica regola che necessita di spiegazione è la quarta, vediamo un esempio :

public final class TorreDiControllo {
  private final List<Aereo> codaDiDecollo:
  private final int totaleAereiDecollati = 0;
  public TorreDiControllo(List<Aereo> aereiInDecollo) {
    this.codaDiDecollo = aereiInDecollo;
  }
  public int getTotaleAereiDecollati() {
    return this.totaleAereiDecollati;
  }
  public List<Aerei> getCodaDiDecollo() {
    return this.codaDiDecollo();
  }
}

Sembra una classe ben fatta, ma non è cosi, vediamo che un nostro utilizzatore potrebbe fare :

List<Aerei> c = torreDiControllo.getCodaDiDecollo().clear();

Di fatto, ha modificato il contenuto della collection codaDiDecollo, quindi la nostra classe non è immutabile.

La soluzione sta nel non ritornare mai il riferimento ad oggetti mutabili, vediamo come risolvere nel nostro caso :

public List<Aerei> getCodaDiDecollo() {
  return new ArrayList<>(this.codaDiDecollo);
}

In questo modo ritorneremo una copia della nostra lista.

Un altro modo è quello di non esporre mai la collection, ma di fornire metodi per iterare su di essa e ritornare una copia dell’elemento richiesto :

public int getCodaDiDecolloCount() {
  return this.codaDiDecollo.count();
}
public Aereo getCodaDiDecollo(int index) {
  return this.codaDiDecollo.get(index).clone();
}

 

Il pattern Builder

Immaginiamo di avere una classe con moltissime property che debbono essere settate al momento della creazione dell’oggetto, potremmo certo utilizzare un costruttore, ma correremo il rischio di avere un costruttore enorme con molti parametri, rendendone veramente difficile l’utilizzo da parte di altri sviluppatori.

Inoltre, cosi facendo, se aggiungiamo property alla classe, saremmo costretti a modificare o dichiarare un altro costruttore, rendendo il codice non manutenibile e illeggibile.

La soluzione sta nell’utilizzare i metodi setter per impostare le property ma attenzione ! questo non funziona con classi immutabili !

Tramite il pattern builder tutti i parametri sono passati ad un oggetto particolare detto Builder che permette la concatenazione di chiamate e restituisce l’oggetto creato tramite il metodo build()

Vediamo un esempio :

public class AereoBuilder {
  private String costruttore;
  private int velocitaMassima;
  public AereoBuilder setCostruttore(String costruttore) {
    this.costruttore = costruttore();
    return this;
  }
  public AereoBuilder setVelocitaMassima(int velocita) {
    this.velocitaMassima = velocita;
    return this;
  }
  public Aereo build() {
    return new Aereo(this.costruttore, this.velocitaMassima);
  }
}

Vediamo che questa classe è mutabile, ma può tranquillamente restituire classi immutabili poichè l’oggetto finale viene effettivamente creato nel metodo build()

Vediamo inoltre che ogni metodo set non è void, ma restituisce se stesso, cosi da poter invocare metodi concatenati :

AereoBuilder builder = new AereoBuilder();
builder.setCostruttore("McDonnel Douglas").setVelocitaMassima(400);
Aereo md80 = builder.build();

Questo metodo ci permette anche di avere property facoltative, ovvero non strettamente necessarie per la creazione del nostro oggetto, però, potremmo incorrere nel problema di creare un oggetto nel metodo build() non avendo valorizzati parametri necessari, potremmo ovviare rilanciando un eccezione nel metodo build o fornire valori di default nel caso non siano stati impostati.

Il vantaggio maggiore è quello di poter aggiungere property non necessarie al nostro oggetto senza obbligare a correggere e ricompilare tutto il progetto, di fatto rende il codice molto più manutenibile.

Il pattern Factory

Cosa succede se non sappiamo con esattezza quale tipo di classe istanziare a runtime ? immaginiamo che il nostro programma abbia bisogno di un tipo o di un altro a seconda del caso, come possiamo ovviare a questo ?

Grazie al pattern factory, possiamo delegare la logica di creazione dell’oggetto ad una classe che implementa tale logica, è simile al pattern builder ma si focalizza sul polimorfirmo.

Di solito è implementato con un metodo statico e per convenzione, la sua denominazione termina con il postfisso Factory

vediamo un esempio :

public abstract class Aereo {
  private int velocita;
  public Aereo(int velocita) {
    this.velocita = velocita;
  }
  public int getVelocita() {
    return this.velocita;
  }
  abstract void decolla();
}
public class Boeing747 extends Aereo {
  public Boeing747(int velocita) {
    super(velocita);
  }
  public void decolla() {
    System.out.println("Boeing747 sta decollando");
  }
}
public class Md80 extends Aereo {
 public Md80 (int velocita) {
 super(velocita);
 }
 public void decolla() {
   System.out.println("Md80 sta decollando");
 }
}

Ora definiamo una classe AereoFactory che ci fornirà l’aereo giusto a seconda del caso  :

public class AereoFactory {
  public static Aereo getAereo(String costruttore) {
    switch(costruttore) {
      case "Boeing": return new Boeing747(450); 
      case "McDonnel Douglas": return new Md80(350);
    }
}

public class TorreDiControllo {
  public static void main(String... args) {
    final Aereo aereo = AereoFactory.getAereo("Boeing");
    aereo.decolla();
  }
}

Molto semplice, ora se conosciamo il nome del costruttore, otterremmo l’aereo giusto.

Solo ad una cosa bisogna stare attenti, ovviamente le nostre classi che estendono Aereo non possono avere costruttore privato, altrimenti il Factory non potrebbe crearle, facciamo attenzione quindi al costruttore, se lo definiamo public, altri sviluppatori potrebbero bypassare il Factory e creare i loro oggetti da soli, quello che è giusto fare, è definire i costruttori con default access e racchiudere questi ed il Factory nello stesso package, in modo che all’esterno del package non sia possibile creare questi oggetti