Menu
Coddy logo textTech

Patrón Decorator

Parte de la sección Programación Orientada a Objetos del Journey de GO de Coddy — lección 93 de 107.

El patrón Decorator permite añadir nuevos comportamientos a los objetos de forma dinámica envolviéndolos en otros objetos. Mientras que Adapter cambia una interfaz para que coincida con otra, Decorator mantiene la misma interfaz pero mejora la funcionalidad añadiendo capas de envoltorios alrededor del objeto original.

En Go, implementamos esto haciendo que tanto el tipo base como los decoradores implementen la misma interfaz:

type Notifier interface {
    Send(message string) string
}

type BasicNotifier struct{}

func (b BasicNotifier) Send(message string) string {
    return "Sending: " + message
}

Los decoradores envuelven a otro Notifier y añaden comportamiento antes o después de delegar en él:

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))
}

El poder del Decorator reside en apilar múltiples envoltorios para combinar comportamientos:

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 es ideal cuando necesitas añadir responsabilidades a los objetos sin modificar su código, especialmente cuando se necesitan diferentes combinaciones de funcionalidades. Se utiliza comúnmente para capas de registro (logging), almacenamiento en caché (caching), autenticación y compresión.

challenge icon

Desafío

Fácil

¡Vamos a construir un sistema de pedidos para una cafetería utilizando el patrón Decorator! Crearás una bebida de café base y decoradores que añaden extras como leche, azúcar y crema batida. Cada decorador envuelve al anterior, construyendo tanto la descripción como el precio—un ejemplo perfecto del mundo real de cómo los decoradores superponen funcionalidad.

Organizarás tu código en tres archivos:

  • beverage.go: Define tu interfaz principal y el tipo de café base.

    Crea una interfaz Beverage con dos métodos:

    • Description() string — devuelve qué es la bebida
    • Cost() float64 — devuelve el precio

    Implementa una estructura Coffee como tu bebida base. Su descripción debe ser Coffee y su costo debe ser 2.00.

  • decorators.go: Crea tus tipos de decoradores que añaden extras a cualquier bebida.

    Cada decorador envuelve un Beverage e implementa la misma interfaz. Construye tres decoradores:

    • MilkDecorator — añade , Milk a la descripción y 0.50 al costo
    • SugarDecorator — añade , Sugar a la descripción y 0.25 al costo
    • WhipDecorator — añade , Whip a la descripción y 0.75 al costo

    Cada decorador debe llamar a los métodos de la bebida envuelta y mejorar el resultado.

  • main.go: Construye bebidas personalizadas apilando decoradores.

    Lee una cantidad de extras a añadir. Luego, para cada extra, lee su tipo (milk, sugar, o whip) y envuelve tu bebida actual con el decorador apropiado.

    Después de aplicar todos los extras, imprime la descripción y el costo final de la bebida en líneas separadas. Formatea el costo como $X.XX con exactamente dos decimales.

Se proporcionarán las siguientes entradas:

  • Línea 1: Número de extras a añadir
  • Líneas siguientes: Un tipo de extra por línea (milk, sugar, o whip)

Por ejemplo, dado:

2
milk
sugar

Tu salida debería ser:

Coffee, Milk, Sugar
$2.75

Y dado:

3
whip
milk
milk

Tu salida debería ser:

Coffee, Whip, Milk, Milk
$3.75

Y dado:

0

Tu salida debería ser:

Coffee
$2.00

Observa cómo cada decorador envuelve la bebida anterior, acumulando tanto la descripción como el costo. Puedes añadir el mismo extra varias veces (como leche doble), y el orden de los decoradores determina el orden en la descripción. El café base no sabe nada sobre los extras—¡cada decorador simplemente mejora lo que sea que esté envolviendo!

Hoja de referencia

El patrón Decorator añade nuevo comportamiento a los objetos de forma dinámica envolviéndolos en otros objetos. Mantiene la misma interfaz pero mejora la funcionalidad a través de capas.

Tanto el tipo base como los decoradores implementan la misma interfaz:

type Notifier interface {
    Send(message string) string
}

type BasicNotifier struct{}

func (b BasicNotifier) Send(message string) string {
    return "Sending: " + message
}

Los decoradores envuelven a otro objeto y añaden comportamiento antes o después 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))
}

Apila múltiples decoradores para combinar comportamientos:

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

Utiliza el Decorator para añadir responsabilidades sin modificar el código del objeto, especialmente para diferentes combinaciones de características. Usos comunes: registro (logging), almacenamiento en caché, autenticación, compresión.

Pruébalo tú mismo

package main

import (
	"fmt"
)

func main() {
	// Leer el número de extras
	var count int
	fmt.Scanln(&count)

	// Comenzar con un café base
	var drink Beverage = &Coffee{}

	// Leer cada extra y envolver la bebida con el decorador apropiado
	for i := 0; i < count; i++ {
		var extra string
		fmt.Scanln(&extra)

		// TODO: Basado en el tipo de extra, envolver drink con el decorador apropiado
		// Usar MilkDecorator para "milk"
		// Usar SugarDecorator para "sugar"
		// Usar WhipDecorator para "whip"
	}

	// TODO: Imprimir la descripción final y el costo
	// Formatear el costo como $X.XX con exactamente dos decimales
}
quiz iconPonte a prueba

Esta lección incluye un breve cuestionario. Empieza la lección para responderlo y registrar tu progreso.

Todas las lecciones de Programación Orientada a Objetos