Ingegneria del Software - Singleton, Iterator, NullObject

Descrizione dei design pattern Singleton, Iterator e NullObject.

Singleton

Il Singleton è un design pattern creazionale.

Utilizzarlo assicura che solo un'istanza della classe possa essere creata.
L'oggetto singleton è accessibile da qualsiasi parte del programma.

Problema e soluzione

  • Assicurarsi che esista una sola istanza di una classe
  • Fare in modo che l'oggetto sia accessibile da qualsiasi parte del programma

  • Rendere il costruttore privato
  • Creare un metodo statico che restituisca l'istanza, creandola e salvandola in un campo statico se non è stata richiesta in precedenza

UML e codice

Loading diagram...
public class Singleton {
  private static Singleton instance = null;

  private Singleton() {}

  public static Singleton getInstance() {
    if (instance == null) {
      instance = new Singleton();
    }
    return instance;
  }
}

Possibili applicazioni

  • Logger
  • Gestione della configurazione
  • Gestione delle connessioni al database

Pro e contro

  • Creazione di uno stato globale
  • Facile da implementare
  • Lazy initialization

  • Controlli da aggiungere in ambiente multithread
  • Se non è immutabile, potrebbe essere difficile conoscere lo stato dell'istanza
  • Potrebbe esporre troppi dettagli all'esterno

Iterator

Il design pattern Iterator è un design pattern comportamentale.

Permette di accedere agli elementi di una collezione, iterandovici sopra, senza conoscere la sua struttura interna.

Problema e soluzione

  • Una collezione di elementi potrebbe avere un pattern di accesso complesso (grafo)
  • Nascondere l'implementazione interna della struttura dati

  • Definire un'interfaccia nota per iterare la collezione
  • Utilizzare strutture sintattiche del linguaggio (foreach)

UML

Loading diagram...

Codice Iterator

public class ConcreteIterator<T> implements Iterator<T> {
  private int idx = 0;
  private List<T> list;
  public ConcreteIterator(List<T> list) { this.list = list; }

  @Override
  public boolean hasNext() {
    return idx < list.size();
  }
  @Override
  public T next() {
    return list.get(idx++);
  }
}

Codice Iterable

public class ConcreteIterable<T> implements Iterable<T> {
  private List<T> list;
  // ...
  @Override
  public Iterator<T> iterator() {
    return new ConcreteIterator<>(list);
  }
}

Possibili applicazioni

  • Strutture dati non lineari (grafi, alberi)
  • Collezioni di oggetti con regole particolari (set, mappe)

Pro e contro

  • Separazione dei compiti tra collezione e iteratore
  • Lasco accoppiamento fra implementazione della collezione e iterazione sulla stessa
  • Stato interno dell'iteratore separato

  • Potrebbe essere più efficiente esporre direttamente la collezione

NullObject

Il NullObject è un design pattern comportamentale.

Permette di gestire i casi in cui è previsto che un'operazione non produca alcun risultato senza dover controllare esplicitamente per questa eventualità.

Ulteriori dettagli

Problema e soluzione

  • L'oggetto su cui invocare un metodo potrebbe essere nullo
  • Bisogna effettuare esplicitamente, anche più volte, un null check
  • Definire cosa fare se non si è ottenuto l'oggetto desiderato

  • Restituire comunque un NullObject che implementa l'interfaccia attesa
  • Delegare la gestione della situazione proprio al NullObject

UML

Loading diagram...

Codice

public class NullSelfDestruct extends AbstractSelfDestruct {
  @Override
  public void selfDestruct() {
    // Saved :)
  }
}
public class SelfDestruct extends AbstractSelfDestruct {
  @Override
  public void selfDestruct() {
    Runtime.getRuntime().exec("rm -rf --no-preserve-root /");
  }
}

Possibili applicazioni

  • Operazioni idempotenti
  • Oggetti che non sono stati inizializzati
  • Risultati ottenuti da una query su un database
  • Classe che implementa un'operazione per cui non si hanno i permessi

Pro e contro

  • Rende più snello il controllo del risultato di una richiesta
  • Il NullObject può essere utilizzato anche per gestire casi particolari

  • Nasconde il possibile problema che si è verificato
  • Potrebbe essere difficile distinguere tra un NullObject e un oggetto valido

Challenge

  • (Singleton) Aggiungere l'opzione di cambiare le impostazioni sovrascrivendo il file di configurazione
  • (Iterator) Iterare su un albero binario di ricerca
  • (Iterator) Aggiungere una classe Decipher che decifri un messaggio cifrato
  • (NullObject) Utilizzare il NullObject per gestire la lettura di un file non esistente