Descrizione dei design pattern Facade, Adapter, Prototype e State.
Facade
Il facade è un design pattern strutturale.
Definisce un'interfaccia semplificata verso una libreria, un framework o qualsiasi altro sistema complesso.
Problema e soluzione
- Compiere una macro operazione richiede molte chiamate a metodi di diverse classi
- Le operazioni di basso livello dovrebbero essere nascoste al client che non le deve utilizzare
- Il facade fornisce un'interfaccia semplificata che raggruppa internamente le sotto-operazioni
- Vengono esposti solo i metodi che interessano al client
UML
Loading diagram...
Diagramma di sequenza
Loading diagram...
Codice
public class VideoConverter {
public VideoFile convert(String inputFile, Codec codec) {
VideoFile file = new VideoFile(inputFile);
Codec ogCodec = file.getCodec();
Converter converter = new Converter(ogCodec, codec);
return converter.convert(file);
}
}
Possibili applicazioni
- Creare un'interfaccia di alto livello per un sistema complesso
- Nascondere una serie di richieste REST
- Nascondere una serie di query ad un database
Pro e contro
- Rende più semplice l'utilizzo di un sistema complesso
- Se il facade inizia a nascondere troppe sotto-operazioni, viola il principio di responsabilità singola
Adapter
L'adapter è un design pattern strutturale.
Agevola l'interoperabilità tra interfacce diverse.
Problema e soluzione
- È necessario cambiare una dipendenza, cercando di minimizzare le modifiche al codice esistente
- L'api esterna non è direttamente compatibile con le interfacce del sistema
- Nascondere le interfacce indesiderate dietro un oggetto adapter
- Sarà compito dell'adapter tradurre le richieste del client in chiamate comprensibili all'api esterna
UML e diagramma di sequenza
Loading diagram...
Loading diagram...
Codice
// JSList<T> è l'interfaccia target, JSListAdapter<T> è l'adapter
public class JSListAdapter<T> implements JSList<T> {
// List è l'adaptee
private List<T> list;
public JSListAdapter(List<T> list) { this.list = list; }
public int length() { return list.size(); }
public T at(int index) { return list.get(index); }
public void push(T item) { list.add(item); }
}
Varianti
Adapter a due vie
Nell'adapter a due vie, l'adapter, oltre l'interfaccia target, implementa anche quella dell'adaptee. Può essere utilizzato sia come adapter che come adaptee.
Class adapter vs Object adapter
Nel class adapter, l'adapter estende l'adaptee.
Nell'object adapter, l'adapter contiene un'istanza dell'adaptee.
Possibili applicazioni
- Tradurre le richieste di un client in chiamate comprensibili ad un'api esterna
- Cambiare una libreria esterna senza modificare il codice del client
- Semplificare un interfaccia limitandosi a mostrare solo le funzionalità che interessano al client
Pro e contro
- Minore dipendenza da una specifica interfaccia
- Aggiunta di controlli e validazioni personalizzati sulle richieste
- Possibile peggioramento delle prestazioni
Prototype
Il prototype è un design pattern creazionale.
Permette di creare nuovi oggetti clonandone uno esistente.
Problema e soluzione
- È necessario creare un oggetto con lo stesso stato di un altro già esistente
- Bisogna includere anche campi privati e protetti
- È necessario assicurarsi che eventuali riferimenti diventino indipendenti
- Delegare all'oggetto stesso la sua clonazione
- Assicurarsi che la clonazione sia profonda
UML
Loading diagram...
Codice
public class User implements Cloneable {
private String name;
private int age;
private Address address;
@Override
public User clone() {
User clone = (User) super.clone();
clone.address = this.address.clone();
return clone;
}
}
In java tutti gli oggetti possiedono un metodo clone()
.
Per poterlo utilizzare però, bisogna implementare l'interfaccia Cloneable
e cambiare la visibilità del metodo a public
.
Usando il metodo di default, la copia è shallow.
Possiamo fare un override che faccia il clone in modo ricorsivo.
Possibili applicazioni
- Creare due oggetti con lo stesso stato, ma con riferimenti diversi
- Assicurarsi che la clonazione sia profonda
Pro e contro
- Possibilità di clonare oggetti complessi
- Previsto dalla libreria standard
- Si potrebbe incappare in dipendenze circolari
State
Lo state è un design pattern comportamentale.
Consente di cambiare radicalmente il comportamento di un oggetto in base allo stato interno, in maniera simile ad un automa a stati finiti.
Problema e soluzione
- Il comportamento dell'oggetto dipende dal suo stato interno, definito a runtime
- Le operazioni eseguite variano notevolmente in base allo stato interno
- Il numero di stati è finito e vi sono delle regole di transizione tra stati
- Inserire ogni ramo condizionale in una classe separata
- Definire un contesto che utilizzi le classi dello stato per gestire le richieste
- Modificare lo stato del contesto in base alle regole di transizione
UML e diagramma di sequenza
Loading diagram...
Loading diagram...
Codice context
class MusicPlayer {
private PlayerState state;
public MusicPlayer() { state = new PlayerStopState(); }
public void play() { state = state.play(); }
public void stop() { state = state.stop(); }
public void sound() { state.sound(); }
}
Codice state
interface PlayerState {
PlayerState play();
PlayerState stop();
void sound();
}
class PlayerPlayState implements PlayerState {
public PlayerState play() { return this; }
public PlayerState stop() { return new PlayerStopState(); }
public void sound() { System.out.println("BIP"); }
}
class PlayerStopState implements PlayerState {
public PlayerState play() { return new PlayerPlayState(); }
public PlayerState stop() { return this; }
public void sound() { }
}
Possibili applicazioni
- Stato del personaggio in un gioco (normale, stunnato, buffato, ...)
- Stato di un circuito (aperto, chiuso, ...)
- Fasi di un processo
- Macchina a stati finiti
Pro e contro
- Il comportamento dell'oggetto in ogni caso è delegato a classi specializzate
- Introdurre nuovi stati è trasparente al client e al contesto
- Controllo del flusso simile ad una macchina a stati finiti
- Complessità eccessiva per controlli condizionali semplici o pochi stati
Challenge
- (Facade) Creare un facade per un sistema di pagamento (verifica della carta, pagamento, invio della fattura...)
- (Adapter) Riportare un interfaccia di una struttura dati vista in un altro linguaggio in java
- (Prototype) Creare un oggetto che cloni un altro oggetto con copia profonda di due livelli
- (State) Creare un oggetto che gestisca lo stato di un personaggio in un gioco (normale, stunnato, buffato, ...) modificando le opzioni che il giocatore può scegliere