Menu
Coddy logo textTech

Patrón Decorator

Parte de la sección Programación Orientada a Objetos del Journey de Dart de Coddy — lección 96 de 110.

El patrón Decorator te permite añadir nuevos comportamientos a objetos de forma dinámica envolviéndolos en objetos especiales llamados decoradores. A diferencia de la herencia, que añade comportamiento en tiempo de compilación, los decoradores asignan responsabilidades adicionales en tiempo de ejecución sin modificar la clase original.

La idea clave es que tanto el objeto original como los decoradores implementan la misma interfaz.

Un decorador envuelve al objeto original, delega en él y añade su propio comportamiento antes o después:

abstract class Coffee {
  String getDescription();
  double getCost();
}

class SimpleCoffee implements Coffee {
  @override
  String getDescription() => 'Simple Coffee';

  @override
  double getCost() => 2.0;
}

class MilkDecorator implements Coffee {
  final Coffee _coffee;
  MilkDecorator(this._coffee);

  @override
  String getDescription() => '${_coffee.getDescription()}, Milk';

  @override
  double getCost() => _coffee.getCost() + 0.5;
}

class SugarDecorator implements Coffee {
  final Coffee _coffee;
  SugarDecorator(this._coffee);

  @override
  String getDescription() => '${_coffee.getDescription()}, Sugar';

  @override
  double getCost() => _coffee.getCost() + 0.2;
}

void main() {
  Coffee coffee = SimpleCoffee();
  coffee = MilkDecorator(coffee);
  coffee = SugarDecorator(coffee);

  print(coffee.getDescription());  // Simple Coffee, Milk, Sugar
  print(coffee.getCost());         // 2.7
}

Cada decorador envuelve al objeto anterior y lo mejora. Puedes apilar múltiples decoradores en cualquier combinación, creando un comportamiento flexible sin necesidad de crear una subclase para cada combinación posible. Este patrón es ideal cuando necesitas añadir características a los objetos sin afectar a otros objetos de la misma clase.

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 que se puede mejorar con varios ingredientes, donde cada ingrediente envuelve al elemento anterior y se suma tanto a la descripción como al precio.

Organizarás tu código en dos archivos:

  • pizza.dart: Este archivo contiene tu interfaz de pizza y todas las clases decoradoras. Comienza con una clase abstracta Pizza que defina dos métodos: String getDescription() y double getPrice(). Luego crea una clase PlainPizza que implemente Pizza; esta devuelve Plain Pizza como su descripción y cuesta 8.0. Ahora construye tres clases decoradoras que envuelvan cada una a una Pizza y la mejoren:
    • CheeseDecorator: añade , Cheese a la descripción y 1.5 al precio
    • PepperoniDecorator: añade , Pepperoni a la descripción y 2.0 al precio
    • MushroomDecorator: añade , Mushrooms a la descripción y 1.25 al precio
    Cada decorador debe implementar Pizza, aceptar una Pizza en su constructor y delegar en la pizza envuelta antes de añadir su propia contribución.
  • main.dart: Importa tu archivo de pizza y demuestra cómo se apilan los decoradores para construir un pedido personalizado. Comienza con una PlainPizza, luego envuélvela con CheeseDecorator, después envuelve eso con PepperoniDecorator y, finalmente, envuelve todo con MushroomDecorator. Imprime la descripción de la pizza final, luego imprime Total: $ seguido del precio.

Observa cómo cada decorador se construye sobre el anterior: ¡estás creando una pizza completa apilando decoradores simples de responsabilidad única!

Salida esperada:

Plain Pizza, Cheese, Pepperoni, Mushrooms
Total: $12.75

Hoja de referencia

El patrón Decorator añade nuevos comportamientos a los objetos de forma dinámica envolviéndolos en objetos decoradores. Tanto el objeto original como los decoradores implementan la misma interfaz.

Componentes clave:

  • Una clase abstracta o interfaz que define los métodos comunes
  • Una clase concreta que implementa la funcionalidad base
  • Clases decoradoras que envuelven al objeto original y lo mejoran

Cada decorador:

  • Implementa la misma interfaz que el objeto que decora
  • Mantiene una referencia al objeto envuelto
  • Delega las llamadas al objeto envuelto
  • Añade su propio comportamiento antes o después de la delegación
abstract class Coffee {
  String getDescription();
  double getCost();
}

class SimpleCoffee implements Coffee {
  @override
  String getDescription() => 'Simple Coffee';

  @override
  double getCost() => 2.0;
}

class MilkDecorator implements Coffee {
  final Coffee _coffee;
  MilkDecorator(this._coffee);

  @override
  String getDescription() => '${_coffee.getDescription()}, Milk';

  @override
  double getCost() => _coffee.getCost() + 0.5;
}

// Apilar múltiples decoradores
Coffee coffee = SimpleCoffee();
coffee = MilkDecorator(coffee);
coffee = SugarDecorator(coffee);

Los decoradores se pueden apilar en cualquier combinación, creando un comportamiento flexible sin necesidad de crear subclases para cada combinación posible.

Pruébalo tú mismo

import 'pizza.dart';

void main() {
  // TODO: Crear una PlainPizza
  
  // TODO: Envolverla con CheeseDecorator
  
  // TODO: Envolver eso con PepperoniDecorator
  
  // TODO: Envolver todo con MushroomDecorator
  
  // TODO: Imprimir la descripción final de la pizza
  
  // TODO: Imprimir "Total: $" seguido del 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