DESIGN PATTERN: DECORATOR
Il Decorator è un pattern strutturale che consente di aggiungere, in modo dinamico, nuove funzionalità a un oggetto senza modificarne la classe originale.
Il principio di base è “impacchettare” (wrapping) l’oggetto originale all’interno di un altro oggetto che implementa la stessa interfaccia, delegando le chiamate e arricchendole prima o dopo l’esecuzione.
QUANDO UTILIZZARLO
- Vuoi estendere il comportamento di un oggetto a runtime senza usare l’ereditarietà.
- Vuoi evitare la proliferazione di sottoclassi per ogni combinazione di funzionalità
(es.
ButtonBlueLargeWithIcon,ButtonRedSmallNoIcon, ecc.). - Necessiti di combinare funzionalità in modo flessibile e modulare.
VANTAGGI
- Modularità: aggiungi funzionalità senza toccare la classe originale.
- Flessibilità: puoi impilare più decoratori in qualunque ordine.
- Open/Closed Principle: estendi il comportamento senza modificare codice esistente.
POTENZIALI SVANTAGGI
- Maggiore complessità: troppi oggetti annidati possono rendere il flusso poco chiaro.
- Configurazione verbosa: la creazione e combinazione dei decoratori può diventare prolissa.
CODICE
/**
* Interfaccia comune per DataSource
*/
interface DataSource {
writeData(data: string): void;
readData(): string;
}
/**
* Classe concreta: sorgente dati base
*/
class FileDataSource implements DataSource {
private storage: string = "";
writeData(data: string): void {
console.log("Scrittura dati nel file:", data);
this.storage = data;
}
readData(): string {
console.log("Lettura dati dal file");
return this.storage;
}
}
/**
* DECORATOR
* Decoratore concreto che implementa DataSource e ha un riferimento a un DataSource
* Inoltre aggiunge compressione ai dati
* Serve per dare piu struttura al codice ma si può evitare e fare direttamente il Decoratore concreto
*/
class DataSourceDecorator implements DataSource{
//Oggetto di base wrappato
protected wrapper: DataSource;
constructor(source: DataSource) {
this.wrapper = source;
}
//riutilizzo le funzioni del wrapper e aggiungo altra logica
writeData(data: string): void {
data = this.compress(data);
this.wrapper.writeData(data);
}
readData(): string {
let data = this.wrapper.readData();
return this.decompress(data);
}
//aggiungo ulteriori funzioni
private compress(data: string): string {
//Implementazione di compressione fittizia (esempio semplice)
return data.split("").reverse().join("");
}
private decompress(data: string): string {
//Decompressione fittizia(in questo caso inversa della compressione)
return data.split("").reverse().join("");
}
}
//Da questo momento in poi potrei procedere in 2 modi:
//VERTICALE: Aggiungo un nuovo decoratore(virtuale e concreto) 'wrappando' un oggetto di tipo CompressionDecorator
//ORIZZONTALE: Aggiungo un nuovo decoratore(virtuale e concreto) 'wrappando' un oggetto di tipo DataSource => Consigliato
//USO DECORATOR
const fileSource: FileDataSource = new FileDataSource();
const compressedSource: DataSourceDecorator = new DataSourceDecorator(fileSource);
compressedSource.writeData("Questo è un testo da salvare");
const result: string = compressedSource.readData();
console.log("Dati letti decompressi:", result);