Sul pattern Decorator 1 Introduzione Consideriamo una famosa panineria fa tre tipi di panini: al prosciutto crudo, al prosciutto cotto, al tonno. A ciascuno di questi tre tipi corrisponde un costo. Figura 1: Schema di partenza Il programma client quando vuole un prezzo fa così: Sandwich crudo = new SandwichCrudo(); double prezzo = crudo.getcost(); Se ci sono le aggiunte (pomodoro, mozzarella, lattuga, capperi,...) ai tre tipi di base, si può avere una varietà grandissima di tipi di panino. Si ha così il modello dei Figura 2, o quello di Figura 3. Ciascun tipo di panino ha un suo specifico costo. Il modello di Figura 2 è più compatto e modulare di quello di Figura 3 ma in ogni caso si tratta di una strada impercorribile quando il numero degli ingredienti cresce. Bisogna aggiungere classi e nel definire il prezzo di un panino multiingredienti occorre fare mente locale a quanto vale il prezzo della base, ecc. Se un ingrediente viene a costare di più occorre mettere mano a tutte le classi in cui c è l ingrediente. Regola Il sistema deve essere aperto alle espansioni, ma le classi devono restare chiuse! toccando solo in un punto. Col modello di Figura 3 si avrebbe questo genere di codice. Si estende aggiungendo o Sandwich crudomozzpom = new SandwichCrudoMozzarellaPomodoro(); double prezzo = crudomozzpom.getcost(); 1
Figura 2: Panini diversificati rispetto a quelli di base in funzione degli ingredienti aggiuntivi. Figura 3: Panini diversificati in base agli ingredienti principali e agli ingredienti aggiuntivi. 2 Tenere separati gli aspetti che si possono aggiungere Conviene aggiungere il costo di ciascun ingrediente a quello di base del panino, come in Figura 4. Sandwich panino = new SandwichCrudo(); panino.addingrediente(pomo); panino.addingrediente(xx); double prezzo = panino.getcost(); forall i in panino.ingredienti prezzo = prezzo + i.getcost(); Vantaggi Si può estendere quanto si vuole. Le classi sono separate. Tutto si limita ad aggiungere nuove classi nell aggregato degli ingredienti. 2
Figura 4: Panini diversificati in base ai tre tipi principali con l aggiunta di ingredienti. 2.1 Un altro modo (migliore?): il decoratore In Figura 5 c è il modello generale del decoratore. In Figura 6 il decoratore applicato al nostro esempio. Figura 5: Pattern Decorator. 3
Figura 6: Pattern Decorator nel nostro caso. Nel caso specifico. public abstract class Sandwich{ protected double cost; public abstract double getcost(); public class SandwichCrudo extends Sandwich{ public SandwichCrudo(double cost){ this.cost = cost; public double getcost(){ return cost; public abstract class IngrDecoratore extends Sandwich{ protected Sandwich component; public class Pomodoro extends IngrDecoratore{ public Pomodoro(Sandwich component){ this.component = component; double getcost(){ return component.getcost+cost; // Nel client Sandwich panino = new SandwichCrudo(); panino = new Pomodoro(panino); panino = new Lattuga(panino); panino = new Capperi(panino); //panino di base // panino + pomodoro // panino + pomodoro + lattuga // panino + pomodoro + lattuga + capperi 4
int prezzo = panino.getcost(); Vantaggi Come sopra: si può estendere quanto si vuole. Si tratta di aggiungere classi senza toccare le esistenti Il decoratore aggiunge funzionalità a un oggetto. Si può costruire una pila di decoratori: il client può limitarsi a vedere solo il frutto dell ultima decorazione. L esempio tipico è quello delle interfacce: una finestra viene decorata con una scrollbar. Il client vede solo una finestra con scrollbar e non sa che la finestra in effetti è di per sé priva di scrollbar. Definizione Il pattern Decorator aggiunge funzionalità a un oggetto dinamicamente. La decorazione è una alternativa al subclassing (all uso dell ereditarietà per differenziare-estendere le funzionalità ). 2.1.1 Confronto con la programmazione procedurale Anche per il decoratore la base concettuale è rappresentata dal polimorfismo e dalla composizione degli oggetti a runtime. Nella programmazione procedurale ciò era possibile solo in parte. Le procedure ricorsive sono esempi di decoratori che, iterativamente, lavorano sugli ingressi prodotti da una precedente chiamata. Nella programmazione procedurale non esiste un meccanismo come quello della composizione degli oggetti e pertanto l unica possibilità è quella di prevedere sequenze di chiamate a tempo di compilazione. 3 Decoratori in Java Java fa un largo uso di decoratori nel package di io. In Figura 7 viene mostrato il decoratore FilteredInputStream e due sue realizzazioni concrete Figura 7: Sopra: alcune classi nella gerarchia di input. Sotto la gerarchia per la classe InputDataStream. 5