Wzorzec dekoratora
Część sekcji Programowanie obiektowe ścieżki Java w Coddy. Lekcja 72 z 87.
Wzorzec dekoratora to strukturalny wzorzec projektowy, który pozwala dodawać obiektom nowe zachowania poprzez opakowywanie ich w specjalne obiekty nazywane dekoratorami. W przeciwieństwie do dziedziczenia, które dodaje zachowanie w czasie kompilacji, dekoratory dodają funkcjonalność dynamicznie, w czasie działania programu, bez modyfikowania oryginalnej klasy.
Wyobraź sobie kawiarnię, w której zaczynasz od podstawowej kawy i możesz dodać dodatki, takie jak mleko, cukier lub bita śmietana. Każdy dodatek otacza oryginalną kawę, zwiększając jej koszt i rozszerzając opis. Wzorzec wykorzystuje wspólny interfejs, dzięki czemu udekorowane obiekty można traktować tak samo jak oryginalny obiekt:
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;
}
}Dekorator przechowuje referencję do opakowanego obiektu i przekazuje mu wywołania, dodając jednocześnie własne zachowanie. Możesz łączyć wiele dekoratorów, aby zestawiać różne funkcje:
Coffee order = new SimpleCoffee();
order = new MilkDecorator(order);
order = new SugarDecorator(order);
System.out.println(order.getDescription()); // Prosta kawa, mleko, cukier
System.out.println(order.getCost()); // 2.75Wzorzec Dekoratora sprawdza się, gdy chcesz dodawać obiektom odpowiedzialności bez tworzenia ogromnej liczby podklas. Każdy dekorator jest niezależny, więc możesz swobodnie je łączyć, aby uzyskać dokładnie taką kombinację zachowań, jakiej potrzebujesz.
Wyzwanie
ŁatwyZbudujmy system powiadomień z użyciem wzorca Dekorator! Utworzysz podstawowe powiadomienie, które można rozszerzać o różne kanały dostarczania: alerty e-mail, alerty SMS i powiadomienia push, opakowując dekoratory jeden w drugi. Odzwierciedla to rzeczywiste systemy, w których pojedyncza wiadomość może być jednocześnie wysyłana wieloma kanałami.
Rozmieścisz kod w czterech plikach:
Notifier.java: Zdefiniuj interfejsNotifier, który pełni funkcję interfejsu komponentu we wzorcu Dekorator. Powinien deklarować metodęsend(String message), która obsługuje wysyłanie powiadomień.BasicNotifier.java: Utwórz konkretną klasę komponentu implementującą interfejsNotifier. Reprezentuje ona najprostszą formę powiadomienia: alert w aplikacji. Gdy zostanie wywołana metodasend, klasa powinna wypisaćIn-App: [message].NotifierDecorators.java: Zbuduj tutaj hierarchię dekoratorów. Zacznij od abstrakcyjnej klasyNotifierDecorator, która implementuje interfejsNotifieri przechowuje referencję do opakowanego obiektuNotifier(przekazanego przez konstruktor).Następnie utwórz trzy konkretne dekoratory rozszerzające klasę
NotifierDecorator:EmailDecorator— gdy zostanie wywołana metodasend, najpierw przekazuje wywołanie do opakowanego powiadamiacza, a następnie wypisujeEmail: [message].SMSDecorator— gdy zostanie wywołana metodasend, najpierw przekazuje wywołanie do opakowanego powiadamiacza, a następnie wypisujeSMS: [message].PushDecorator— gdy zostanie wywołana metodasend, najpierw przekazuje wywołanie do opakowanego powiadamiacza, a następnie wypisujePush: [message].Main.java: Połącz swój system dekoratorów! Otrzymasz dwa wejścia: wiadomość powiadomienia (String) oraz rozdzieloną przecinkami listę kanałów do dodania (String), na przykład"email,sms"lub"push,email,sms".Zacznij od
BasicNotifier. Następnie, na podstawie wejścia zawierającego kanały, opakuj go odpowiednimi dekoratorami w kolejności, w jakiej występują. Prawidłowe nazwy kanałów toemail,smsipush.Na koniec wywołaj
sendz wiadomością na w pełni udekorowanym powiadamiaczu.
Otrzymasz kolejno dwa wejścia: wiadomość do wysłania (String) oraz kanały do włączenia (String, rozdzielony przecinkami).
Na przykład dla wejść Server is down! i email,push wynikiem będzie:
In-App: Server is down!
Email: Server is down!
Push: Server is down!Zwróć uwagę, że każdy dekorator dodaje własne działanie, zachowując oryginalne powiadomienie. Kolejność opakowywania określa kolejność wyjścia: najpierw działa najbardziej wewnętrzny komponent (BasicNotifier), a następnie każdy dekorator dodaje swoją warstwę. Możesz dowolnie łączyć kanały, nie tworząc osobnych klas dla każdej kombinacji!
Spróbuj swoich sił
import java.util.Scanner;
class Main {
public static void main(String[] args) {
Scanner scanner = new Scanner(System.in);
// Wczytaj wiadomość do wysłania
String message = scanner.nextLine();
// Wczytaj listę kanałów rozdzielonych przecinkami (np. "email,sms" lub "push,email,sms")
String channelsInput = scanner.nextLine();
// TODO: Zacznij od BasicNotifier
// TODO: Podziel dane wejściowe kanałów według przecinków
// TODO: Przejdź przez każdy kanał i opakuj obiekt notifier odpowiednim dekoratorem
// Poprawne kanały to: "email", "sms", "push"
// TODO: Wywołaj send na obiekcie notifier opakowanym wszystkimi dekoratorami, przekazując wiadomość
}
}
Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Podstawy programowania obiektowego
Pliki zewnętrzneWprowadzenie do programowania obiektowegoKlasy a obiektySłowo kluczowe thisMetodyPola (atrybuty)Metoda konstruktoraPrzeciążanie konstruktoraPodsumowanie — prosty kalkulator4Dziedziczenie
Podstawy dziedziczenia (extends)Słowo kluczowe superPrzesłanianie metod (@Override)Łańcuch wywołań konstruktorówKlasa ObjectDziedziczenie jedno- i wielopoziomoweDlaczego Java nie obsługuje wielodziedziczenia klasPodsumowanie – hierarchia pracowników7Metody specjalne i klasa Object
Metoda toString()equals() i hashCode()Metoda clone()compareTo() i ComparableInterfejs ComparatorPodsumowanie — własne sortowanie10Obsługa wyjątków w programowaniu obiektowym
Hierarchia klas wyjątkówWłasne wyjątkiBłędy sprawdzane a niesprawdzaneWzorzec try-with-resourcesPodsumowanie – zweryfikowany użytkownik2Modyfikatory dostępu i enkapsulacja
Przegląd poziomów dostępuMetody pobierające i ustawiająceUkrywanie informacjiSłowo kluczowe finalPodsumowanie — menedżer kont bankowych5Polimorfizm
Podstawy przeciążania metodPrzesłanianie metod (w czasie wykonywania)Rzutowanie w górę i w dółOperator instanceofKlasy i metody abstrakcyjnePowtórka — kalkulator figur8Zaawansowane koncepcje OOP
Kompozycja a dziedziczenieAgregacja a kompozycjaKlasy wewnętrzne, zagnieżdżone i anonimoweEnumy i metody enumówRekordy (Java 16+)Klasy zapieczętowane (Java 17+)11Wzorce projektowe — część 1
Wprowadzenie do wzorców projektowychWzorzec SingletonWzorzec FactoryWzorzec BuilderWzorzec ObserverWzorzec Strategy3Właściwości klas i składowe statyczne
Zmienne instancji a zmienne statyczneMetody statyczneBloki statyczneStałe (static final)Powtórka – Counter i Utility6Interfejsy i klasy abstrakcyjne
Wprowadzenie do interfejsówImplementowanie interfejsówImplementacja wielu interfejsówMetody domyślne i statyczne w interfejsieKlasy abstrakcyjne a interfejsyInterfejsy funkcyjnePodsumowanie — system płatności9Typy generyczne
Wprowadzenie do typów generycznychKlasy generyczneMetody generyczneOgraniczone parametry typówSymbole wieloznaczne (?, extends, super)Powtórzenie – kontener generyczny12Wzorce projektowe, część 2
Wzorzec poleceniaWzorzec adapteraWzorzec dekoratoraWzorzec metody szablonowejWzorzec stanuWzorzec kompozytuWzorzec iteratoraPoćwicz samodzielnie: Kompilator Java online