Menu
Coddy logo textTech

Patrón Decorator

Parte de la sección Programación Orientada a Objetos del Journey de C# de Coddy — lección 58 de 70.

El patrón Decorator es un patrón estructural que permite añadir nuevos comportamientos a los objetos envolviéndolos en objetos especiales llamados decoradores. A diferencia de la herencia, que añade comportamiento en tiempo de compilación, los decoradores añaden funcionalidad dinámicamente en tiempo de ejecución sin modificar la clase original.

El patrón funciona haciendo que los decoradores implementen la misma interfaz que el objeto que envuelven. Cada decorador mantiene una referencia al objeto envuelto y le delega las llamadas, añadiendo su propio comportamiento antes o después:

public interface ICoffee
{
    string GetDescription();
    decimal GetCost();
}

public class SimpleCoffee : ICoffee
{
    public string GetDescription() => "Coffee";
    public decimal GetCost() => 2.00m;
}

public abstract class CoffeeDecorator : ICoffee
{
    protected ICoffee _coffee;
    
    public CoffeeDecorator(ICoffee coffee) => _coffee = coffee;
    
    public virtual string GetDescription() => _coffee.GetDescription();
    public virtual decimal GetCost() => _coffee.GetCost();
}

public class MilkDecorator : CoffeeDecorator
{
    public MilkDecorator(ICoffee coffee) : base(coffee) { }
    
    public override string GetDescription() => _coffee.GetDescription() + ", Milk";
    public override decimal GetCost() => _coffee.GetCost() + 0.50m;
}

El poder de los decoradores reside en su capacidad para apilarse. Puedes envolver un objeto decorado con otro decorador:

ICoffee coffee = new SimpleCoffee();
coffee = new MilkDecorator(coffee);
coffee = new MilkDecorator(coffee);  // ¡Doble leche!

Console.WriteLine(coffee.GetDescription());  // Café, Leche, Leche
Console.WriteLine(coffee.GetCost());         // 3.00

El patrón Decorator es ideal cuando necesitas añadir responsabilidades a los objetos sin crear una explosión de subclases. Cada decorador se centra en una única preocupación, y puedes combinarlos según sea necesario para construir el comportamiento exacto que deseas.

challenge icon

Desafío

Fácil

Vamos a construir un sistema de pedido de pizzas utilizando el patrón Decorator. Crearás una pizza base y luego añadirás ingredientes dinámicamente, donde cada ingrediente envuelve la capa anterior y se suma tanto a la descripción como al precio.

Organizarás tu código en tres archivos:

  • Pizza.cs: Define una interfaz IPizza en el espacio de nombres PizzaShop con dos métodos: GetDescription() que devuelve un string y GetPrice() que devuelve un decimal. Luego crea una clase PlainPizza que implemente esta interfaz; debe devolver "Pizza" como su descripción y 8.00m como su precio base.
  • ToppingDecorators.cs: Crea una clase abstracta ToppingDecorator que implemente IPizza y mantenga una referencia a la pizza envuelta (pasada a través del constructor). Sus métodos virtuales deben delegar en la pizza envuelta. Luego crea tres decoradores concretos:
    • CheeseTopping: añade ", Cheese" a la descripción y suma 1.50m al precio
    • PepperoniTopping: añade ", Pepperoni" a la descripción y suma 2.00m al precio
    • MushroomTopping: añade ", Mushrooms" a la descripción y suma 1.25m al precio
  • Program.cs: Une todo comenzando con una pizza sencilla y envolviéndola con ingredientes basados en la entrada. Cada decorador de ingredientes envuelve la pizza anterior, construyendo el pedido final capa por capa.

Recibirás una entrada:

  • Una lista de ingredientes separados por comas para añadir (por ejemplo, cheese,pepperoni,cheese)

Comienza con una PlainPizza, luego envuélvela con el decorador apropiado para cada ingrediente en el orden en que aparecen. Los ingredientes válidos son cheese, pepperoni y mushrooms. Después de aplicar todos los ingredientes, imprime la descripción en una línea y el precio total en la siguiente línea.

Por ejemplo, si la entrada es cheese,pepperoni,mushrooms, la salida debería ser:

Pizza, Cheese, Pepperoni, Mushrooms
12.75

¡Observa cómo puedes añadir el mismo ingrediente varias veces! Si la entrada es cheese,cheese, obtendrías:

Pizza, Cheese, Cheese
11

Esto demuestra la flexibilidad del patrón decorator: cada decorador envuelve cualquier pizza que reciba, ya sea una pizza sencilla o una ya decorada, ¡permitiéndote apilar comportamientos dinámicamente!

Hoja de referencia

El patrón Decorator es un patrón estructural que añade nuevos comportamientos a los objetos envolviéndolos en objetos decoradores especiales. Añade funcionalidad dinámicamente en tiempo de ejecución sin modificar la clase original.

Los decoradores implementan la misma interfaz que el objeto que envuelven, mantienen una referencia al objeto envuelto y le delegan las llamadas mientras añaden su propio comportamiento:

public interface ICoffee
{
    string GetDescription();
    decimal GetCost();
}

public class SimpleCoffee : ICoffee
{
    public string GetDescription() => "Coffee";
    public decimal GetCost() => 2.00m;
}

public abstract class CoffeeDecorator : ICoffee
{
    protected ICoffee _coffee;
    
    public CoffeeDecorator(ICoffee coffee) => _coffee = coffee;
    
    public virtual string GetDescription() => _coffee.GetDescription();
    public virtual decimal GetCost() => _coffee.GetCost();
}

public class MilkDecorator : CoffeeDecorator
{
    public MilkDecorator(ICoffee coffee) : base(coffee) { }
    
    public override string GetDescription() => _coffee.GetDescription() + ", Milk";
    public override decimal GetCost() => _coffee.GetCost() + 0.50m;
}

Los decoradores se pueden apilar envolviendo un objeto decorado con otro decorador:

ICoffee coffee = new SimpleCoffee();
coffee = new MilkDecorator(coffee);
coffee = new MilkDecorator(coffee);  // ¡Doble leche!

Console.WriteLine(coffee.GetDescription());  // Coffee, Milk, Milk
Console.WriteLine(coffee.GetCost());         // 3.00

Utiliza el patrón Decorator cuando necesites añadir responsabilidades a los objetos sin crear muchas subclases. Cada decorador se centra en una única responsabilidad y pueden combinarse según sea necesario.

Pruébalo tú mismo

using System;
using PizzaShop;

class Program
{
    public static void Main(string[] args)
    {
        // Lee la lista de ingredientes separados por comas
        string input = Console.ReadLine();
        string[] toppings = input.Split(',');
        
        // TODO: Comenzar con una PlainPizza
        
        // TODO: Recorrer cada ingrediente y envolver la pizza
        // con el decorador apropiado basado en el nombre del ingrediente:
        // - "cheese" -> CheeseTopping
        // - "pepperoni" -> PepperoniTopping
        // - "mushrooms" -> MushroomTopping
        
        // TODO: Imprimir la descripción final y el precio
    }
}
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