Pattern Decorator
Fa parte della sezione Programmazione orientata agli oggetti del percorso Java di Coddy. Lezione 72 di 87.
Il Decorator Pattern è un pattern strutturale che ti permette di aggiungere nuovi comportamenti agli oggetti racchiudendoli in oggetti speciali chiamati decoratori. A differenza dell'ereditarietà, che aggiunge comportamenti in fase di compilazione, i decoratori aggiungono funzionalità dinamicamente in fase di esecuzione senza modificare la classe originale.
Immagina una caffetteria in cui parti da un caffè semplice e puoi aggiungere extra come latte, zucchero o panna montata. Ogni aggiunta avvolge il caffè originale, aggiungendone il costo e la descrizione. Il pattern usa un'interfaccia comune, così gli oggetti decorati possono essere trattati come quello originale:
interface Coffee {
String getDescription();
double getCost();
}
class SimpleCoffee implements Coffee {
public String getDescription() {
return "Simple Coffee";
}
public double getCost() {
return 2.0;
}
}
abstract class CoffeeDecorator implements Coffee {
protected Coffee coffee;
public CoffeeDecorator(Coffee coffee) {
this.coffee = coffee;
}
}
class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) {
super(coffee);
}
public String getDescription() {
return coffee.getDescription() + ", Milk";
}
public double getCost() {
return coffee.getCost() + 0.5;
}
}Il decoratore mantiene un riferimento all’oggetto avvolto e ne delega le chiamate, aggiungendo al contempo il proprio comportamento. Puoi impilare più decoratori per combinare le funzionalità:
Coffee order = new SimpleCoffee();
order = new MilkDecorator(order);
order = new SugarDecorator(order);
System.out.println(order.getDescription()); // Caffè semplice, latte, zucchero
System.out.println(order.getCost()); // 2.75Il pattern Decorator dà il meglio di sé quando devi aggiungere responsabilità agli oggetti senza creare un’esplosione di sottoclassi. Ogni decoratore è indipendente, quindi puoi combinarli liberamente per creare esattamente la combinazione di comportamenti di cui hai bisogno.
Sfida
FacileCostruiamo un sistema di notifiche usando il Decorator Pattern! Creerai una notifica di base che può essere arricchita con diversi canali di invio, avvisi via email, avvisi SMS e notifiche push, avvolgendo dei decorator l'uno attorno all'altro. Questo rispecchia i sistemi del mondo reale, in cui un singolo messaggio potrebbe dover essere inviato contemporaneamente attraverso più canali.
Organizzerai il codice in quattro file:
Notifier.java: Definisci l'interfacciaNotifier, che funge da interfaccia del componente per il tuo pattern decorator. Dovrebbe dichiarare un metodosend(String message)che gestisce l'invio delle notifiche.BasicNotifier.java: Crea la classe concreta del componente che implementaNotifier. Rappresenta la forma più semplice di notifica: un avviso nell'app. Quando viene chiamatosend, dovrebbe stampareIn-App: [message].NotifierDecorators.java: Qui costruirai la gerarchia dei decorator. Inizia con una classe astrattaNotifierDecoratorche implementaNotifiere contiene un riferimento a un oggettoNotifieravvolto (passato tramite il suo costruttore).Crea poi tre decorator concreti che estendono
NotifierDecorator:EmailDecorator- Quando viene chiamatosend, delega prima al notifier avvolto, poi stampaEmail: [message].SMSDecorator- Quando viene chiamatosend, delega prima al notifier avvolto, poi stampaSMS: [message].PushDecorator- Quando viene chiamatosend, delega prima al notifier avvolto, poi stampaPush: [message].Main.java: Metti insieme il tuo sistema di decorator! Riceverai due input: il messaggio di notifica (String) e un elenco separato da virgole dei canali da aggiungere (String), per esempio"email,sms"oppure"push,email,sms".Inizia con un
BasicNotifier. Poi, in base all'input dei canali, avvolgilo con i decorator appropriati nell'ordine in cui compaiono. I nomi dei canali validi sonoemail,smsepush.Infine, chiama
sendcon il tuo messaggio sul notifier completamente decorato.
Riceverai due input in quest'ordine: il messaggio da inviare (String) e i canali da attivare (String, separati da virgole).
Per esempio, con gli input Server is down! e email,push, l'output sarà:
In-App: Server is down!
Email: Server is down!
Push: Server is down!Nota come ogni decorator aggiunga il proprio comportamento preservando la notifica originale. L'ordine di avvolgimento determina l'ordine dell'output: il componente più interno (BasicNotifier) viene eseguito per primo, poi ogni decorator aggiunge il proprio livello. Puoi combinare qualsiasi insieme di canali senza creare classi separate per ogni combinazione!
Provalo tu
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// Leggi il messaggio da inviare
String message = scanner.nextLine();
// Leggi l'elenco dei canali separati da virgole (ad es., "email,sms" o "push,email,sms")
String channelsInput = scanner.nextLine();
// TODO: Inizia con un BasicNotifier
// TODO: Dividi l'input dei canali in corrispondenza delle virgole
// TODO: Scorri ogni canale e avvolgi il notifier con il decorator appropriato
// I canali validi sono: "email", "sms", "push"
// TODO: Chiama send sul notifier completamente decorato, passando il messaggio
}
}
Questa lezione include un breve quiz. Inizia la lezione per rispondere e tenere traccia dei tuoi progressi.
Tutte le lezioni di Programmazione orientata agli oggetti
1Fondamenti della programmazione a oggetti
File esterniIntroduzione alla programmazione a oggettiClassi e oggettiLa parola chiave thisMetodiCampi (attributi)Metodo costruttoreSovraccarico dei costruttoriRipasso - Calcolatrice semplice4Ereditarietà
Ereditarietà di base (extends)La parola chiave superSovrascrittura dei metodi (@Override)Concatenamento dei costruttoriLa classe ObjectEreditarietà singola e multilivelloPerché non esiste l'ereditarietà multipla delle classiRiepilogo: gerarchia dei dipendenti7Metodi speciali e classe Object
Metodo toString()equals() e hashCode()Metodo clone()compareTo() e ComparableInterfaccia ComparatorRiepilogo - Ordinamento personalizzato2Modificatori di accesso e incapsulamento
Panoramica dei livelli di accessoMetodi getter e setterNascondimento delle informazioniLa parola chiave finalRiepilogo - Gestore di conti bancari5Polimorfismo
Nozioni di base sull'overloading dei metodiOverride dei metodi (a runtime)Upcasting e downcastingL'operatore instanceofClassi e metodi astrattiRiepilogo - Calcolatore di forme8Concetti avanzati di OOP
Composizione vs ereditarietàAggregazione vs composizioneClassi interne, annidate e anonimeEnum e metodi degli EnumRecord (Java 16+)Classi sealed (Java 17+)11Modelli di progettazione Parte 1
Introduzione ai modelli di progettazionePattern SingletonPattern FactoryPattern BuilderPattern ObserverPattern Strategy3Proprietà di classe e membri statici
Variabili di istanza e staticheMetodi staticiBlocchi staticiCostanti (static final)Riepilogo - Contatore e utility6Interfacce e classi astratte
Introduzione alle interfacceImplementazione delle interfacceImplementazione di più interfacceMetodi default e static nelle interfacceClassi astratte e interfacce a confrontoInterfacce funzionaliRiepilogo: sistema di pagamento9Tipi generici
Introduzione ai tipi genericiClassi genericheMetodi genericiParametri di tipo con limitiCaratteri jolly (?, extends, super)Riepilogo - Contenitore generico12Modelli di progettazione – Parte 2
Pattern CommandPattern AdapterPattern DecoratorPattern Template MethodPattern StatePattern CompositePattern IteratorEsercitati da solo: Compilatore Java online