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 aos 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 adicionar camadas de wrappers ao redor 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
}Os decoradores envolvem outro Notifier e adicionam comportamento antes ou depois de delegar a 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 está em empilhar vários 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] HELLOO Decorator é ideal quando você precisa adicionar responsabilidades aos objetos sem modificar o código deles, especialmente quando diferentes combinações de recursos são necessárias. Ele é comumente usado em camadas de registro, armazenamento em cache, autenticação e compactação.
Desafio
FácilVamos criar 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 chantili. Cada decorador envolve o anterior, construindo tanto a descrição quanto o preço: um exemplo perfeito e realista de como os decoradores adicionam funcionalidades em camadas.
Você organizará seu código em três arquivos:
beverage.go: Defina sua interface principal e o tipo base de café.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. Crie 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: Monte bebidas personalizadas empilhando decoradores.Leia uma quantidade de extras a serem adicionados. Em seguida, para cada extra, leia seu tipo (
milk,sugarouwhip) e envolva sua bebida atual com o decorador apropriado.Depois de aplicar todos os extras, imprima a descrição e o custo finais da bebida em linhas separadas. Formate o custo como
$X.XX, com exatamente duas casas decimais.
As seguintes entradas serão fornecidas:
- Linha 1: Número de extras a serem adicionados
- Linhas seguintes: Um tipo de extra por linha (
milk,sugarouwhip)
Por exemplo, dada a entrada:
2
milk
sugarSua saída deve ser:
Coffee, Milk, Sugar
$2.75E dada a entrada:
3
whip
milk
milkSua saída deve ser:
Coffee, Whip, Milk, Milk
$3.75E dada a entrada:
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 aquilo que envolve!
Experimente você mesmo
package main
import (
"fmt"
)
func main() {
// Ler o número de extras
var count int
fmt.Scanln(&count)
// Começar com um café base
var drink Beverage = &Coffee{}
// Ler cada extra e envolver a bebida com o decorator apropriado
for i := 0; i < count; i++ {
var extra string
fmt.Scanln(&extra)
// TODO: Com base no tipo de extra, envolver drink com o decorator apropriado
// Usar MilkDecorator para "milk"
// Usar SugarDecorator para "sugar"
// Usar WhipDecorator para "whip"
}
// TODO: Imprimir a descrição final e o custo
// Formatar 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 ExternosWorkspace e Módulos do GoPacotes e ImportsNomes Exportados vs. Não ExportadosIntrodução à OOP em GoStructs como ClassesDefinindo Métodos em StructsReceptores de Ponteiro vs. de ValorInicialização de StructsFunções ConstrutorasRecapitulação — Calculadora Simples4Interfaces
Introdução às InterfacesImplementação ImplícitaInterface como ContratoInterface Vazia (any)Asserção de TipoSwitch de TipoComposição de InterfacesInterfaces Stringer e ErrorRecapitulação - Calculadora de Formas7Encapsulamento
Campos Exportados vs. Não ExportadosEncapsulamento no Nível de PacoteMétodos Getters e SettersOcultação de Informações em GoRecapitulação - Registros de Alunos10Genéricos (Go 1.18+)
Introdução aos genéricosParâmetros de tipoRestrições de tipoStructs genéricasAlternativa para métodos genéricosRecapitulação - Coleção genérica13Padrões de Projeto — Parte 1
Introdução aos Padrões de ProjetoPadrão SingletonPadrão FactoryPadrão Abstract FactoryPadrão ObserverPadrão Strategy2Tipos e Structs em Profundidade
Tipos Básicos e CompostosDefinições de Tipos PersonalizadosTags de StructStructs AnônimasStructs AninhadasValores Zero e PadrõesRecapitulação - Livro de Contatos5Composição em vez de herança
Por que Go não tem herançaNoções básicas de incorporação de structsPromoção de métodosIncorporação de várias structsIncorporação vs agregaçãoOcultação de métodos incorporadosRecapitulação - Hierarquia de funcionários8Tratamento de Erros e OOP
A interface errorTipos de Erro PersonalizadosEncapsulamento de Erros (fmt.Errorf)Erros Sentinelaerrors.Is() e errors.As()Panic, Defer e RecoverRecapitulação - Analisador de Arquivos11Biblioteca Padrão e POO
io.Reader e io.Writersort.InterfaceInterface fmt.Stringerencoding/json com StructsInterface http.HandlerRevisão - Modelos de API REST14Padrões de Projeto — Parte 2
Padrão de ComandoPadrão AdapterPadrão DecoratorPadrão de Método TemplatePadrão de EstadoPadrão CompositeMiddleware como Decorator3Ponteiros e Memória
Fundamentos de Ponteiros em GoPonteiros para StructsPassagem por Valor vs. ReferênciaA Função new()Coleta de Lixo em GoRecapitulação - Construtor de Lista Ligada6Polimorfismo em Go
Polimorfismo via InterfacesDuck Typing em GoRegras de Satisfação de InterfacesColeções PolimórficasInjeção de DependênciasRecapitulação - Processador de Pagamentos9Concorrência e OOP
Fundamentos de GoroutinesCanais e comunicaçãoCanais com e sem bufferInstrução selectsync.Mutex e sync.RWMutexsync.WaitGroupDesign de structs thread-safeRevisão - Worker PoolPratique por conta própria: Compilador de Go online