Menu
Coddy logo textTech

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] HELLO

Decorator é 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.

challenge icon

Desafio

Fácil

Vamos 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 Beverage com dois métodos:

    • Description() string — retorna o que é a bebida
    • Cost() float64 — retorna o preço

    Implemente uma struct Coffee como sua bebida base. Sua descrição deve ser Coffee e seu custo deve ser 2.00.

  • decorators.go: Crie seus tipos de decoradores que adicionam extras a qualquer bebida.

    Cada decorador envolve um Beverage e implementa a mesma interface. Construa três decoradores:

    • MilkDecorator — adiciona , Milk à descrição e 0.50 ao custo
    • SugarDecorator — adiciona , Sugar à descrição e 0.25 ao custo
    • WhipDecorator — adiciona , Whip à descrição e 0.75 ao 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, sugar ou whip) 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.XX com 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, sugar ou whip)

Por exemplo, dado:

2
milk
sugar

Sua saída deve ser:

Coffee, Milk, Sugar
$2.75

E dado:

3
whip
milk
milk

Sua saída deve ser:

Coffee, Whip, Milk, Milk
$3.75

E dado:

0

Sua saída deve ser:

Coffee
$2.00

Observe 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] HELLO

Use 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
}
quiz iconTeste seus conhecimentos

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