Padrão Decorator
Parte da seção Programação Orientada a Objetos do Journey de GO da Coddy — lição 93 de 107.
O padrão Decorator permite adicionar novos comportamentos a objetos dinamicamente, envolvendo-os em outros objetos. Enquanto o Adapter altera uma interface para corresponder a outra, o Decorator mantém a mesma interface, mas aprimora a funcionalidade ao sobrepor wrappers em torno do objeto original.
Em Go, implementamos isso fazendo com que tanto o tipo base quanto os decoradores implementem a mesma interface:
type Notifier interface {
Send(message string) string
}
type BasicNotifier struct{}
func (b BasicNotifier) Send(message string) string {
return "Sending: " + message
}Decoradores envolvem outro Notifier e adicionam comportamento antes ou depois de delegar para ele:
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))
}O poder do Decorator reside no empilhamento de múltiplos wrappers para combinar comportamentos:
notifier := BasicNotifier{}
withTimestamp := TimestampDecorator{wrapped: notifier}
withBoth := UppercaseDecorator{wrapped: withTimestamp}
fmt.Println(notifier.Send("Hello"))
// Enviando: Hello
fmt.Println(withBoth.Send("Hello"))
// ENVIANDO: [2024-01-15] HELLODecorator é ideal quando você precisa adicionar responsabilidades a objetos sem modificar seu código, especialmente quando diferentes combinações de recursos são necessárias. É comumente usado para camadas de logging, caching, autenticação e compressão.
Desafio
FácilVamos construir um sistema de pedidos de uma cafeteria usando o padrão Decorator! Você criará uma bebida de café base e decoradores que adicionam extras como leite, açúcar e chantilly. Cada decorador envolve o anterior, construindo tanto a descrição quanto o preço — um exemplo perfeito do mundo real de como os decoradores aplicam funcionalidades em camadas.
Você organizará seu código em três arquivos:
beverage.go: Defina sua interface principal e o tipo de café base.Crie uma interface
Beveragecom dois métodos:Description() string— retorna o que é a bebidaCost() float64— retorna o preço
Implemente uma struct
Coffeecomo sua bebida base. Sua descrição deve serCoffeee seu custo deve ser2.00.decorators.go: Crie seus tipos de decoradores que adicionam extras a qualquer bebida.Cada decorador envolve um
Beveragee implementa a mesma interface. Construa três decoradores:MilkDecorator— adiciona, Milkà descrição e0.50ao custoSugarDecorator— adiciona, Sugarà descrição e0.25ao custoWhipDecorator— adiciona, Whipà descrição e0.75ao custo
Cada decorador deve chamar os métodos da bebida envolvida e aprimorar o resultado.
main.go: Construa bebidas personalizadas empilhando decoradores.Leia uma contagem de extras a serem adicionados. Então, para cada extra, leia seu tipo (
milk,sugarouwhip) e envolva sua bebida atual com o decorador apropriado.Após aplicar todos os extras, imprima a descrição e o custo final da bebida em linhas separadas. Formate o custo como
$X.XXcom exatamente duas casas decimais.
As seguintes entradas serão fornecidas:
- Linha 1: Número de extras a adicionar
- Linhas seguintes: Um tipo de extra por linha (
milk,sugarouwhip)
Por exemplo, dado:
2
milk
sugarSua saída deve ser:
Coffee, Milk, Sugar
$2.75E dado:
3
whip
milk
milkSua saída deve ser:
Coffee, Whip, Milk, Milk
$3.75E dado:
0Sua saída deve ser:
Coffee
$2.00Observe como cada decorador envolve a bebida anterior, construindo tanto a descrição quanto o custo. Você pode adicionar o mesmo extra várias vezes (como leite duplo), e a ordem dos decoradores determina a ordem na descrição. O café base não sabe nada sobre os extras — cada decorador simplesmente aprimora o que quer que ele envolva!
Folha de consulta
O padrão Decorator adiciona novo comportamento a objetos dinamicamente, envolvendo-os em outros objetos. Ele mantém a mesma interface, mas aprimora a funcionalidade por meio de camadas.
Tanto o tipo base quanto os decoradores implementam a mesma interface:
type Notifier interface {
Send(message string) string
}
type BasicNotifier struct{}
func (b BasicNotifier) Send(message string) string {
return "Sending: " + message
}Os decoradores envolvem outro objeto e adicionam comportamento antes ou depois de delegar:
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))
}Empilhe múltiplos decoradores para combinar comportamentos:
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] HELLOUse o Decorator para adicionar responsabilidades sem modificar o código do objeto, especialmente para diferentes combinações de recursos. Usos comuns: logging, cache, autenticação, compressão.
Experimente você mesmo
package main
import (
"fmt"
)
func main() {
// Lê o número de extras
var count int
fmt.Scanln(&count)
// Começa com um café base
var drink Beverage = &Coffee{}
// Lê cada extra e envolve a bebida com o decorador apropriado
for i := 0; i < count; i++ {
var extra string
fmt.Scanln(&extra)
// TODO: Com base no tipo de extra, envolva drink com o decorador apropriado
// Use MilkDecorator para "milk"
// Use SugarDecorator para "sugar"
// Use WhipDecorator para "whip"
}
// TODO: Imprima a descrição final e o custo
// Formate o custo como $X.XX com exatamente duas casas decimais
}
Esta lição inclui um quiz rápido. Comece a lição para respondê-lo e acompanhar seu progresso.
Todas as lições de Programação Orientada a Objetos
1Fundamentos de OOP em Go
Arquivos ExternosGo Workspace e MódulosPacotes e ImportaçõesNomes Exportados vs Não ExportadosIntrodução a OOP em GoStructs como ClassesDefinindo Métodos em StructsRecebedores de Ponteiro vs ValorInicialização de StructsFunções ConstrutorasRecapitulação - Calculadora Simples4Interfaces
Introdução a InterfacesImplementação ImplícitaInterface como ContratoInterface Vazia (any)Type AssertionType SwitchComposição de InterfacesInterfaces Stringer & ErrorRecapitulação - Calculadora de Formas7Encapsulamento
Campos Exportados vs Não ExportadosEncapsulamento em Nível de PacoteMétodos Getter e SetterOcultação de Informação em GoRecapitulação - Registros de Estudantes10Generics (Go 1.18+)
Introdução a GenericsParâmetros de TipoRestrições de TipoStructs GenéricasSolução Alternativa para Métodos GenéricosRecapitulação - Coleção Genérica2Mergulho Profundo em Tipos e Structs
Tipos Básicos e CompostosDefinições de Tipos PersonalizadosStruct TagsStructs AnônimasStructs AninhadasZero Values e PadrõesRecapitulação - Agenda de Contatos5Composição em vez de Herança
Por que Go não possui HerançaFundamentos de Incorporação de StructsPromoção de MétodosIncorporando Múltiplas StructsIncorporação vs AgregaçãoShadowing de Métodos IncorporadosRecapitulação - Hierarquia de Funcionários8Tratamento de Erros e OOP
A Interface errorTipos de Erro PersonalizadosError Wrapping (fmt.Errorf)Erros Sentinelaerrors.Is() e errors.As()Panic, Defer e RecoverRecapitulação - File Parser11Biblioteca Padrão & POO
io.Reader & io.Writersort.InterfaceInterface fmt.Stringerencoding/json com StructsInterface http.HandlerRecapitulação - Modelos de API REST14Padrões de Projeto Parte 2
Padrão CommandPadrão AdapterPadrão DecoratorPadrão Template MethodPadrão StatePadrão CompositeMiddleware como Decorator3Ponteiros e Memória
Fundamentos de Ponteiros em GoPonteiros para StructsPassagem por Valor vs ReferênciaA Função new()Garbage Collection em GoRecapitulação - Construtor de Lista Encadeada6Polimorfismo em Go
Polimorfismo via InterfacesDuck Typing em GoRegras de Satisfação de InterfaceColeções PolimórficasInjeção de DependênciaRecapitulação - Processador de Pagamentos9Concorrência e OOP
Fundamentos de GoroutinesCanais e ComunicaçãoCanais com Buffer vs. Sem BufferInstrução Selectsync.Mutex e sync.RWMutexsync.WaitGroupDesign de Structs Thread-SafeRecapitulação - Worker Pool