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.00El 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.
Desafío
FácilVamos 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 interfazIPizzaen el espacio de nombresPizzaShopcon dos métodos:GetDescription()que devuelve un string yGetPrice()que devuelve un decimal. Luego crea una clasePlainPizzaque implemente esta interfaz; debe devolver"Pizza"como su descripción y8.00mcomo su precio base.ToppingDecorators.cs: Crea una clase abstractaToppingDecoratorque implementeIPizzay 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 suma1.50mal precioPepperoniTopping: añade", Pepperoni"a la descripción y suma2.00mal precioMushroomTopping: añade", Mushrooms"a la descripción y suma1.25mal 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
11Esto 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.00Utiliza 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
}
}
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
1Fundamentos de OOP
Archivos externosNamespaces y directivasIntro a clases y objetosLa palabra clave 'this'Métodos y parámetrosCampos vs PropiedadesConstructoresInicializadores de objetosResumen - Calculadora simple4Herencia
Sintaxis básica de herencia (:)La palabra clave 'base'Palabras clave Virtual y OverrideClases selladas (Sealed)La clase base 'object'Resumen: Jerarquía de empleados7Características avanzadas
Sobrecarga de operadoresIndexadores (this[])Sobrescritura de ToString()Métodos de extensiónResumen - Lista personalizada10Patrones de diseño - Parte 1
Introducción a los patrones de diseñoSingleton Thread-SafePatrón FactoryPatrón Observer (Eventos)Patrón Strategy2Propiedades y miembros estáticos
Propiedades autoimplementadasPropiedades de solo lectura y escrituraCampos y métodos estáticosClases estáticasMiembros con cuerpo de expresión5Polimorfismo e Interfaces
Polimorfismo: Compilación vs. EjecuciónInterfaz vs. Clase AbstractaInterfaces MúltiplesInterfaces ExplícitasUpcasting y DowncastingRepaso: Calculadora de Figuras8Conceptos avanzados de POO
Composición sobre herenciaGenéricos (clases y métodos)Delegados y eventosAtributos y ReflexiónIDisposable y la sentencia usingFundamentos de Inyección de dependencias11Patrones de diseño, parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón Composite3Arquitectura de clases
Datos de instancia vs. estáticosPalabras clave 'readonly' y 'const'Campos de respaldoResumen - Gestor de cuentas bancarias6Encapsulamiento
Modificadores de accesoPropiedades para el encapsulamientoImplementación de ocultamiento de datosPatrones de inmutabilidadRepaso - Registros de estudiantes