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.
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 abstractaPizzaque defina dos métodos:String getDescription()ydouble getPrice(). Luego crea una clasePlainPizzaque implementePizza; esta devuelvePlain Pizzacomo su descripción y cuesta8.0. Ahora construye tres clases decoradoras que envuelvan cada una a unaPizzay la mejoren:CheeseDecorator: añade, Cheesea la descripción y1.5al precioPepperoniDecorator: añade, Pepperonia la descripción y2.0al precioMushroomDecorator: añade, Mushroomsa la descripción y1.25al precio
Pizza, aceptar unaPizzaen 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 unaPlainPizza, luego envuélvela conCheeseDecorator, después envuelve eso conPepperoniDecoratory, finalmente, envuelve todo conMushroomDecorator. Imprime la descripción de la pizza final, luego imprimeTotal: $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.75Hoja 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
}
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 POO
Archivos externosLibrerías e importacionesIntroducción a la POOClases vs. ObjetosLa palabra clave thisMétodosVariables de instanciaConceptos básicos de constructoresResumen - Calculadora sencilla4Seguridad de nulos
Introducción a la seguridad de nulosNulables vs No nulablesLos operadores ? y !Palabra clave Late y Null SafetyOperadores Null-awareNull Safety en clasesResumen - Sistema de perfil de usuario7Clases abstractas e interfaces
Clases abstractasMétodos abstractosInterfaces en DartInterfaces implícitasImplementar vs. ExtenderInterfaces múltiplesRepaso - Calculadora de figuras10Colecciones y Genéricos
Visión general de List, Set y MapColecciones con seguridad de tiposClases genéricasMétodos genéricosRestricciones genéricasIterable e IteratorResumen - Almacenamiento genérico2Constructores en Dart
Constructor por defectoConstructores nombradosListas de inicializaciónConstructores constantesConstructores de fábricaConstructores de redireccionamientoResumen - Constructor de formas5Encapsulamiento
Miembros públicos vs. privadosLa convención del prefijo _Privacidad a nivel de libreríaProfundidad en Getters y SettersOcultamiento de informaciónResumen - Registros de estudiantes8Mixins
Introducción a los MixinsCreación de MixinsUso de múltiples MixinsPalabra clave on en MixinsMixin vs HerenciaMixin vs InterfazResumen - Sistema Animal11Métodos especiales
Sobrescritura de toString()Sobrescritura de hashCode y ==Interfaz ComparableMétodo call()Sobrescritura de noSuchMethodResumen - Colección personalizada14Patrones de diseño Parte 1
Introducción a los patrones de diseñoPatrón SingletonPatrón FactoryPatrón ObserverPatrón Strategy3Propiedades de clase
Miembros de instancia vs. estáticosCampos Final y ConstVariables LateMétodos y campos estáticosGetters y SettersResumen - Gestor de cuentas bancarias6Herencia
Herencia básicaLa palabra clave superSobrescritura de métodosLa anotación @overrideLa palabra clave final en clasesConstructores y herenciaResumen - Jerarquía de empleados9Polimorfismo
Fundamentos del polimorfismoPolimorfismo mediante interfacesComprobación de tipos en tiempo de ejecuciónLos operadores is y asPalabra clave covariantResumen - Procesador de pagos12POO Asíncrona
Futures y async/awaitFundamentos de StreamsStream ControllersConstructores asíncronosAsincronía en métodos de claseRepaso - Data Fetcher15Patrones de diseño Parte 2
Patrón CommandPatrón AdapterPatrón DecoratorPatrón Template MethodPatrón StatePatrón CompositePatrón Repository