Pattern Decorator
Fa parte della sezione Programmazione orientata agli oggetti del percorso GO di Coddy. Lezione 93 di 107.
Il pattern Decorator ti consente di aggiungere dinamicamente nuovi comportamenti agli oggetti avvolgendoli in altri oggetti. Mentre Adapter modifica un'interfaccia per adattarla a un'altra, Decorator mantiene la stessa interfaccia ma ne potenzia le funzionalità stratificando wrapper attorno all'oggetto originale.
In Go, lo implementiamo facendo implementare la stessa interfaccia sia al tipo di base sia ai decoratori:
type Notifier interface {
Send(message string) string
}
type BasicNotifier struct{}
func (b BasicNotifier) Send(message string) string {
return "Sending: " + message
}I decoratori avvolgono un altro Notifier e aggiungono comportamento prima o dopo aver delegato a esso:
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))
}La potenza del Decorator sta nella possibilità di impilare più wrapper per combinare i comportamenti:
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] HELLODecorator è ideale quando devi aggiungere responsabilità agli oggetti senza modificarne il codice, soprattutto quando servono diverse combinazioni di funzionalità. Viene comunemente usato per i livelli di logging, caching, autenticazione e compressione.
Sfida
FacileCostruiamo un sistema per le ordinazioni di una caffetteria usando il pattern Decorator! Creerai una bevanda base a base di caffè e dei decorator che aggiungono extra come latte, zucchero e panna montata. Ogni decorator avvolge quello precedente, arricchendo sia la descrizione sia il prezzo: un perfetto esempio reale di come i decorator aggiungano funzionalità a strati.
Organizzerai il codice in tre file:
beverage.go: definisci l’interfaccia principale e il tipo base per il caffè.Crea un’interfaccia
Beveragecon due metodi:Description() string: restituisce il tipo di bevandaCost() float64: restituisce il prezzo
Implementa una struct
Coffeecome bevanda base. La sua descrizione dovrebbe essereCoffeee il suo costo dovrebbe essere2.00.decorators.go: crea i tipi decorator che aggiungono extra a qualsiasi bevanda.Ogni decorator avvolge un
Beveragee implementa la stessa interfaccia. Crea tre decorator:MilkDecorator: aggiunge, Milkalla descrizione e0.50al costoSugarDecorator: aggiunge, Sugaralla descrizione e0.25al costoWhipDecorator: aggiunge, Whipalla descrizione e0.75al costo
Ogni decorator dovrebbe chiamare i metodi della bevanda avvolta e arricchirne il risultato.
main.go: crea bevande personalizzate impilando i decorator.Leggi il numero di extra da aggiungere. Poi, per ogni extra, leggine il tipo (
milk,sugarowhip) e avvolgi la bevanda corrente con il decorator appropriato.Dopo aver applicato tutti gli extra, stampa la descrizione e il costo finali della bevanda su righe separate. Formatta il costo come
$X.XXcon esattamente due cifre decimali.
Verranno forniti i seguenti input:
- Riga 1: numero di extra da aggiungere
- Righe successive: un tipo di extra per riga (
milk,sugarowhip)
Per esempio, dato:
2
milk
sugarIl tuo output dovrebbe essere:
Coffee, Milk, Sugar
$2.75E dato:
3
whip
milk
milkIl tuo output dovrebbe essere:
Coffee, Whip, Milk, Milk
$3.75E dato:
0Il tuo output dovrebbe essere:
Coffee
$2.00Nota come ogni decorator avvolge la bevanda precedente, arricchendo sia la descrizione sia il costo. Puoi aggiungere lo stesso extra più volte (come una doppia dose di latte) e l’ordine dei decorator determina l’ordine nella descrizione. Il caffè base non sa nulla degli extra: ogni decorator si limita ad arricchire ciò che avvolge!
Provalo tu
package main
import (
"fmt"
)
func main() {
// Leggi il numero di aggiunte
var count int
fmt.Scanln(&count)
// Inizia con un caffè di base
var drink Beverage = &Coffee{}
// Leggi ogni aggiunta e avvolgi drink con il decoratore appropriato
for i := 0; i < count; i++ {
var extra string
fmt.Scanln(&extra)
// TODO: In base al tipo di aggiunta, avvolgi drink con il decoratore appropriato
// Usa MilkDecorator per "milk"
// Usa SugarDecorator per "sugar"
// Usa WhipDecorator per "whip"
}
// TODO: Stampa la descrizione finale e il costo
// Formatta il costo come $X.XX con esattamente due cifre decimali
}
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 di OOP in Go
File esterniWorkspace e moduli GoPacchetti e importazioniNomi esportati e non esportatiIntroduzione all’OOP in GoGli struct come classiDefinire metodi sugli structReceiver con puntatore vs receiver con valoreInizializzazione degli structFunzioni costruttriciRiepilogo - Calcolatrice semplice4Interfacce
Introduzione alle interfacceImplementazione implicitaL’interfaccia come contrattoInterfaccia vuota (any)Asserzione di tipoSwitch sul tipoComposizione di interfacceInterfacce Stringer ed ErrorRipasso - Calcolatore di forme7Incapsulamento
Campi esportati e non esportatiIncapsulamento a livello di packageMetodi Getter e SetterNascondimento delle informazioni in GoRiepilogo - Registri degli studenti10Tipi generici (Go 1.18+)
Introduzione ai tipi genericiParametri di tipoVincoli di tipoStruct genericheSoluzione alternativa per i metodi genericiRiepilogo - Collezione generica13Modelli di progettazione Parte 1
Introduzione ai modelli di progettazioneModello SingletonModello FactoryModello Abstract FactoryModello ObserverModello Strategy2Approfondimento su tipi e struct
Tipi di base e compostiDefinizioni di tipi personalizzatiTag delle structStruct anonimeStruct annidateValori zero e predefinitiRipasso - Rubrica5Composizione invece dell'ereditarietà
Perché Go non ha l'ereditarietàFondamenti dell'embedding delle structPromozione dei metodiEmbedding di più structEmbedding e aggregazione a confrontoMascheramento dei metodi embeddedRiepilogo: gerarchia dei dipendenti8Gestione degli errori e OOP
L'interfaccia errorTipi di errore personalizzatiWrapping degli errori (fmt.Errorf)Errori sentinellaerrors.Is() ed errors.As()Panic, Defer e RecoverRiepilogo - Parser di file11Libreria standard e OOP
io.Reader e io.Writersort.InterfaceInterfaccia fmt.Stringerencoding/json con le structInterfaccia http.HandlerRipasso - modelli REST API14Modelli di progettazione – Parte 2
Pattern CommandPattern AdapterPattern DecoratorPattern Template MethodPattern StatePattern CompositeMiddleware come Decorator3Puntatori e memoria
Concetti base dei puntatori in GoPuntatori a structPassaggio per valore vs per riferimentoLa funzione new()Garbage collection in GoRiepilogo - Costruttore di liste concatenate6Polimorfismo in Go
Polimorfismo tramite interfacceDuck typing in GoRegole di soddisfacimento delle interfacceCollezioni polimorficheIniezione delle dipendenzeRiepilogo - Elaboratore di pagamenti9Concorrenza e OOP
Basi delle goroutineCanali e comunicazioneCanali bufferizzati vs non bufferizzatiIstruzione selectsync.Mutex e sync.RWMutexsync.WaitGroupProgettazione di struct thread-safeRiepilogo: worker poolEsercitati da solo: Compilatore Go online