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 te 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 agregan 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 de 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 un único aspecto, y los combinas según sea necesario para construir exactamente el comportamiento que quieres.
Desafío
FácilConstruyamos un sistema de pedidos de pizza usando el patrón Decorator. Crearás una pizza base y después le añadirás ingredientes dinámicamente, de modo que cada ingrediente envuelva la capa anterior y se sume tanto a la descripción como al precio.
Organizarás el código en tres archivos:
Pizza.cs: Define una interfazIPizzaen el espacio de nombresPizzaShopcon dos métodos:GetDescription(), que devuelve una cadena, yGetPrice(), que devuelve un decimal. Después, crea una clasePlainPizzaque implemente esta interfaz; debe devolver"Pizza"como descripción y8.00mcomo precio base.ToppingDecorators.cs: Crea una clase abstractaToppingDecoratorque implementeIPizzay mantenga una referencia a la pizza envuelta (pasada mediante el constructor). Sus métodos virtuales deben delegar en la pizza envuelta. Después, crea tres decoradores concretos:CheeseTopping: añade", Cheese"al final de la descripción y suma1.50mal precioPepperoniTopping: añade", Pepperoni"al final de la descripción y suma2.00mal precioMushroomTopping: añade", Mushrooms"al final de la descripción y suma1.25mal precio
Program.cs: Une todo comenzando con una pizza sencilla y envolviéndola con ingredientes según 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 que se añadirán (por ejemplo,
cheese,pepperoni,cheese)
Comienza con una PlainPizza y después envuélvela con el decorador correspondiente 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.
Por ejemplo, si la entrada es cheese,pepperoni,mushrooms, la salida debería ser:
Pizza, Cheese, Pepperoni, Mushrooms
12.75¡Observa que puedes añadir el mismo ingrediente varias veces! Si la entrada es cheese,cheese, obtendrías:
Pizza, Cheese, Cheese
11.00Esto demuestra la flexibilidad del patrón decorador: cada decorador envuelve la pizza que recibe, ya sea una pizza sencilla o una que ya tenga decoradores, lo que te permite apilar comportamientos dinámicamente.
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 estudiantesPractica por tu cuenta: Compilador de C# online