Wzorzec dekoratora
Część sekcji Programowanie obiektowe ścieżki GO w Coddy. Lekcja 93 z 107.
Wzorzec Dekorator pozwala dynamicznie dodawać nowe zachowania do obiektów poprzez opakowywanie ich innymi obiektami. Adapter zmienia interfejs, aby dopasować go do innego, natomiast Dekorator zachowuje ten sam interfejs, ale rozszerza funkcjonalność, nakładając warstwy opakowań na oryginalny obiekt.
W Go implementujemy to, sprawiając, że zarówno typ bazowy, jak i dekoratory implementują ten sam interfejs:
type Notifier interface {
Send(message string) string
}
type BasicNotifier struct{}
func (b BasicNotifier) Send(message string) string {
return "Sending: " + message
}Dekoratory opakowują inny obiekt Notifier i dodają zachowanie przed przekazaniem mu zadania lub po nim:
type TimestampDecorator struct {
wrapped Notifier
}
func (t TimestampDecorator) Send(message string) string {
timestamped := "[2024-01-15] " + message
return t.wrapped.Send(timestamped)
}
type UppercaseDecorator struct {
wrapped Notifier
}
func (u UppercaseDecorator) Send(message string) string {
return strings.ToUpper(u.wrapped.Send(message))
}Siła wzorca Decorator tkwi w nakładaniu na siebie wielu opakowań, aby łączyć zachowania:
notifier := BasicNotifier{}
withTimestamp := TimestampDecorator{wrapped: notifier}
withBoth := UppercaseDecorator{wrapped: withTimestamp}
fmt.Println(notifier.Send("Hello"))
// Sending: Hello
fmt.Println(withBoth.Send("Hello"))
// SENDING: [2024-01-15] HELLOWzorzec Dekorator sprawdza się idealnie, gdy potrzebujesz dodawać obiektom odpowiedzialności bez modyfikowania ich kodu, zwłaszcza gdy potrzebne są różne kombinacje funkcji. Jest powszechnie używany w warstwach logowania, buforowania, uwierzytelniania i kompresji.
Wyzwanie
ŁatwyStwórzmy system zamawiania w kawiarni, wykorzystując wzorzec Dekorator! Utworzysz bazowy napój kawowy oraz dekoratory dodające dodatki, takie jak mleko, cukier i bita śmietana. Każdy dekorator opakowuje poprzedni, rozbudowując zarówno opis, jak i cenę — to doskonały przykład z życia pokazujący, jak dekoratory nakładają na siebie funkcjonalności.
Rozmieścisz swój kod w trzech plikach:
beverage.go: Zdefiniuj podstawowy interfejs i typ bazowej kawy.Utwórz interfejs
Beveragez dwiema metodami:Description() string: zwraca informację, jaki to napójCost() float64: zwraca cenę
Zaimplementuj strukturę
Coffeejako bazowy napój. Jej opis powinien mieć wartośćCoffee, a cena2.00.decorators.go: Utwórz typy dekoratorów dodających dodatki do dowolnego napoju.Każdy dekorator opakowuje
Beveragei implementuje ten sam interfejs. Utwórz trzy dekoratory:MilkDecorator: dodaje, Milkdo opisu i0.50do cenySugarDecorator: dodaje, Sugardo opisu i0.25do cenyWhipDecorator: dodaje, Whipdo opisu i0.75do ceny
Każdy dekorator powinien wywoływać metody opakowanego napoju i wzbogacać ich wynik.
main.go: Twórz spersonalizowane napoje, nakładając dekoratory jeden na drugi.Wczytaj liczbę dodatków do dodania. Następnie dla każdego dodatku wczytaj jego typ (
milk,sugarlubwhip) i opakuj aktualny napój odpowiednim dekoratorem.Po zastosowaniu wszystkich dodatków wypisz opis i cenę końcowego napoju w osobnych wierszach. Sformatuj cenę jako
$X.XX, z dokładnie dwoma miejscami po przecinku.
Otrzymasz następujące dane wejściowe:
- Wiersz 1: liczba dodatków do dodania
- Kolejne wiersze: jeden typ dodatku w każdym wierszu (
milk,sugarlubwhip)
Na przykład dla danych:
2
milk
sugarWynik powinien być następujący:
Coffee, Milk, Sugar
$2.75A dla danych:
3
whip
milk
milkWynik powinien być następujący:
Coffee, Whip, Milk, Milk
$3.75A dla danych:
0Wynik powinien być następujący:
Coffee
$2.00Zwróć uwagę, jak każdy dekorator opakowuje poprzedni napój, rozbudowując zarówno opis, jak i cenę. Możesz dodawać ten sam dodatek wiele razy (na przykład podwójną porcję mleka), a kolejność dekoratorów określa kolejność dodatków w opisie. Bazowa kawa nie wie nic o dodatkach — każdy dekorator po prostu wzbogaca to, co opakowuje!
Spróbuj swoich sił
package main
import (
"fmt"
)
func main() {
// Wczytaj liczbę dodatków
var count int
fmt.Scanln(&count)
// Zacznij od podstawowej kawy
var drink Beverage = &Coffee{}
// Wczytaj każdy dodatek i opakuj napój odpowiednim dekoratorem
for i := 0; i < count; i++ {
var extra string
fmt.Scanln(&extra)
// TODO: W zależności od rodzaju dodatku opakuj drink odpowiednim dekoratorem
// Użyj MilkDecorator dla "milk"
// Użyj SugarDecorator dla "sugar"
// Użyj WhipDecorator dla "whip"
}
// TODO: Wypisz końcowy opis i koszt
// Sformatuj koszt jako $X.XX z dokładnie dwoma miejscami po przecinku
}
Ta lekcja zawiera krótki quiz. Zacznij lekcję, żeby na niego odpowiedzieć i śledzić swoje postępy.
Wszystkie lekcje w sekcji Programowanie obiektowe
1Podstawy OOP w Go
Pliki zewnętrzneObszar roboczy i moduły GoPakiety i importyNazwy eksportowane i nieeksportowaneWprowadzenie do OOP w GoStruktury jako klasyDefiniowanie metod dla strukturOdbiorniki wskaźnikowe i wartościoweInicjalizacja strukturFunkcje konstruktoraPodsumowanie – prosty kalkulator4Interfejsy
Wprowadzenie do interfejsówImplementacja niejawnaInterfejs jako kontraktPusty interfejs (any)Asercja typuPrzełącznik typówKompozycja interfejsówInterfejsy Stringer i ErrorPowtórka — kalkulator figur7Hermetyzacja
Pola eksportowane i nieeksportowaneHermetyzacja na poziomie pakietuMetody pobierające i ustawiająceUkrywanie informacji w GoPowtórka – dane studentów10Typy generyczne (Go 1.18+)
Wprowadzenie do typów generycznychParametry typówOgraniczenia typówStruktury generyczneObejście ograniczeń metod generycznychPodsumowanie — generyczna kolekcja2Typy i struktury w praktyce
Typy proste i złożoneDefiniowanie własnych typówTagi pól strukturStruktury anonimoweStruktury zagnieżdżoneWartości zerowe i domyślnePodsumowanie – książka kontaktów5Kompozycja zamiast dziedziczenia
Dlaczego Go nie ma dziedziczeniaPodstawy osadzania strukturPromowanie metodOsadzanie wielu strukturOsadzanie a agregacjaPrzesłanianie osadzonych metodPodsumowanie — hierarchia pracowników8Obsługa błędów i programowanie obiektowe
Interfejs błęduWłasne typy błędówOpakowywanie błędów (fmt.Errorf)Błędy wartowniczeerrors.Is() i errors.As()Panic, Defer i RecoverPodsumowanie — parser plików11Biblioteka standardowa i OOP
io.Reader i io.Writersort.InterfaceInterfejs fmt.Stringerencoding/json ze strukturamiInterfejs http.HandlerPowtórzenie – modele REST API14Wzorce projektowe, część 2
Wzorzec poleceniaWzorzec adapteraWzorzec dekoratoraWzorzec metody szablonowejWzorzec stanuWzorzec kompozytuMiddleware jako dekorator3Wskaźniki i pamięć
Podstawy wskaźników w GoWskaźniki do strukturPrzekazywanie przez wartość a przez referencjęFunkcja new()Odśmiecanie pamięci w GoPowtórka — kreator listy wiązanej6Polimorfizm w Go
Polimorfizm za pomocą interfejsówDuck typing w GoZasady spełniania interfejsówKolekcje polimorficzneWstrzykiwanie zależnościPowtórka — procesor płatności9Współbieżność i OOP
Podstawy goroutinesKanały i komunikacjaKanały buforowane i niebuforowaneInstrukcja selectsync.Mutex i sync.RWMutexsync.WaitGroupProjektowanie struktur bezpiecznych wątkowoPowtórka – pula workerów12Zaawansowane zagadnienia programowania obiektowego
Wzorzec opcji funkcyjnychWzorzec Builder w GoŁańcuchowe wywoływanie metodAliasy typów a definicje typówPodstawy refleksjiPrzegląd generowania koduPoćwicz samodzielnie: Kompilator Go online