¿Alguna vez te has encontrado escribiendo el mismo tipo de código una y otra vez? ¿O has heredado un proyecto donde el código es tan complejo que te cuesta entender qué hace cada parte?
Si alguna de estas situaciones te resulta familiar, déjame presentarte a tus nuevos mejores amigos: los patrones de diseño. Son como recetas probadas que otros desarrolladores han perfeccionado para resolver problemas que tú y yo enfrentamos constantemente.
En esta guía completa, exploraremos los patrones de diseño más importantes en JavaScript moderno, cómo implementarlos con ES6+ y cuándo aplicarlos en tus proyectos. Al final, encontrarás ejercicios prácticos para consolidar tu aprendizaje.
- ¿Qué son los Patrones de Diseño?
- Patrones de Diseño Creacionales
- Patrones de Diseño Estructurales
- Patrones de Diseño Comportamentales
- Patrones de Optimización y Performance
- Patrones Modernos con ES6+
- Patrones Anti-Pattern: Lo que Debes Evitar
- Mejores Prácticas al Usar Patrones de Diseño
- Recursos Adicionales
- Ejercicios Prácticos
- Conclusión
¿Qué son los Patrones de Diseño?

Piensa en los patrones de diseño como el GPS del desarrollo de software. No te dicen exactamente qué teclas presionar, pero sí te muestran el camino más eficiente para llegar a tu destino.
Son soluciones que han sido probadas miles de veces por desarrolladores de todo el mundo. No código para copiar y pegar, sino plantillas conceptuales que moldeas según las necesidades únicas de tu proyecto.
Beneficios de Usar Patrones de Diseño
- Código mantenible: Facilitan la comprensión y modificación del código
- Comunicación efectiva: Proporcionan un vocabulario común entre desarrolladores
- Soluciones probadas: Implementan mejores prácticas de la industria
- Escalabilidad: Ayudan a crear arquitecturas que crecen con tu aplicación
Patrones de Diseño Creacionales
Los patrones creacionales se enfocan en cómo crear objetos de manera eficiente y flexible.
1. Singleton: una sola instancia para gobernarlas
Su trabajo es simple: asegurar que exista una sola instancia de una clase en toda tu aplicación, como tener un único guardia de seguridad en la entrada principal.
Ejemplo sencillo:
class DatabaseConnection {
constructor() {
if (DatabaseConnection.instance) {
return DatabaseConnection.instance;
}
this.connection = null;
DatabaseConnection.instance = this;
}
connect() {
if (!this.connection) {
this.connection = "Conexión establecida";
console.log("Nueva conexión creada");
}
return this.connection;
}
}
// Uso
const db1 = new DatabaseConnection();
const db2 = new DatabaseConnection();
console.log(db1 === db2); // true - misma instancia
Cuándo usar Singleton:
- Cuando necesitas múltiples instancias con estados diferentes (por ejemplo, conexiones a diferentes bases de datos)
- Si complica las pruebas unitarias porque crea dependencias globales difíciles de mockear
- Cuando el estado compartido puede causar efectos secundarios inesperados en aplicaciones concurrentes
- Si estás tentado a usarlo solo para «ahorrar memoria» sin una razón arquitectural sólida </aside>
Ventajas: Control estricto sobre cómo y cuándo se accede a la instancia
Desventajas: Puede dificultar las pruebas unitarias y crear acoplamiento global
graph TD
A["Singleton Instance"]
B["Reference 1: db1"] --> A
C["Reference 2: db2"] --> A
D["Reference 3: db3"] --> A
E["Reference 4: db4"] --> A
style A fill:#4CAF50,stroke:#2E7D32,stroke-width:3px,color:#fff
style B fill:#2196F3,stroke:#1565C0,stroke-width:2px,color:#fff
style C fill:#2196F3,stroke:#1565C0,stroke-width:2px,color:#fff
style D fill:#2196F3,stroke:#1565C0,stroke-width:2px,color:#fff
style E fill:#2196F3,stroke:#1565C0,stroke-width:2px,color:#fffDiagrama mostrando múltiples referencias apuntando a una única instancia
2. Factory: crea objetos sin ensuciar tu código
Supón que tienes una fábrica de vehículos. No necesitas saber todos los detalles técnicos de cómo se ensambla un auto o una moto, solo dices «necesito un auto» y la fábrica se encarga del resto.
Eso es exactamente lo que hace el patrón Factory: crea objetos sin que te preocupes por los detalles internos de su construcción.
Es tu mejor compañero cuando el proceso de creación es complejo o cuando quieres mantener tu código flexible y desacoplado. Por ejemplo:
- Creación de objetos complejos con múltiples configuraciones
- Sistema de plugins, donde los tipos de objetos se determinan en tiempo de ejecución
- Cuando quieres encapsular la lógica de creación
Ejemplo sencillo:
class Car {
constructor(model) {
this.model = model;
this.type = "car";
}
drive() {
console.log(`Conduciendo un ${this.model}`);
}
}
class Motorcycle {
constructor(model) {
this.model = model;
this.type = "motorcycle";
}
drive() {
console.log(`Manejando una ${this.model}`);
}
}
// Factory
class VehicleFactory {
static createVehicle(type, model) {
switch(type) {
case "car":
return new Car(model);
case "motorcycle":
return new Motorcycle(model);
default:
throw new Error("Tipo de vehículo no válido");
}
}
}
// Uso
const myCar = VehicleFactory.createVehicle("car", "Toyota Corolla");
const myBike = VehicleFactory.createVehicle("motorcycle", "Harley Davidson");
myCar.drive(); // Conduciendo un Toyota Corolla
myBike.drive(); // Manejando una Harley Davidson
Patrones de Diseño Estructurales
Los patrones estructurales se centran en cómo componer objetos y clases para formar estructuras más grandes.
3. Patrón Proxy: Tu guardaespaldas de control de acceso
El patrón Proxy te da un sustituto o marcador de posición para otro objeto, permitiendo controlar el acceso a él. Es útil para añadir funcionalidad adicional como caché, validación o logging.
Ejemplo sencillo:
const person = {
name: "Juan",
age: 30,
email: "juan@example.com"
};
const personProxy = new Proxy(person, {
get: (target, prop) => {
console.log(`Accediendo a la propiedad: ${prop}`);
return target[prop];
},
set: (target, prop, value) => {
if (prop === "age" && typeof value !== "number") {
throw new Error("La edad debe ser un número");
}
console.log(`Modificando ${prop} de ${target[prop]} a ${value}`);
target[prop] = value;
return true;
}
});
// Uso
console.log(personProxy.name); // Accediendo a la propiedad: name
personProxy.age = 31; // Modificando age de 30 a 31
// personProxy.age = "treinta"; // Error: La edad debe ser un número
Cuándo usarlo:
- Validación de datos
- Lazy loading de recursos
- Logging y debugging
- Control de acceso

4. Patrón Module: Organiza tu código como cajones de un armario
El patrón Module permite organizar y encapsular código relacionado en unidades independientes y reutilizables. Con ES6+, esto se logra fácilmente con los módulos nativos.
Ejemplo sencillo:
// mathUtils.js
const privateCounter = 0; // Variable privada
export const add = (a, b) => a + b;
export const subtract = (a, b) => a - b;
// Función privada (no exportada)
const validateNumber = (num) => {
return typeof num === "number";
};
export const multiply = (a, b) => {
if (!validateNumber(a) || !validateNumber(b)) {
throw new Error("Ambos argumentos deben ser números");
}
return a * b;
};
// main.js
import { add, multiply } from "./mathUtils.js";
console.log(add(5, 3)); // 8
console.log(multiply(4, 2)); // 8
Cuándo usarlo:
- Organizar código en archivos separados
- Evitar contaminar el scope global
- Crear APIs públicas con implementación privada
5. Patrón Mixin: Comparte responsabilidades sin herencia
El patrón Mixin agrega funcionalidades a objetos o clases sin usar herencia. Es útil cuando quieres compartir comportamiento entre objetos que no tienen una relación jerárquica.
Ejemplo sencillo:
// Mixin con funcionalidad compartida
const canEat = {
eat(food) {
console.log(`${this.name} está comiendo ${food}`);
}
};
const canWalk = {
walk() {
console.log(`${this.name} está caminando`);
}
};
const canSwim = {
swim() {
console.log(`${this.name} está nadando`);
}
};
// Clases base
class Dog {
constructor(name) {
this.name = name;
}
}
class Fish {
constructor(name) {
this.name = name;
}
}
// Aplicar mixins
Object.assign(Dog.prototype, canEat, canWalk);
Object.assign(Fish.prototype, canEat, canSwim);
// Uso
const myDog = new Dog("Rex");
myDog.eat("croquetas"); // Rex está comiendo croquetas
myDog.walk(); // Rex está caminando
const myFish = new Fish("Nemo");
myFish.eat("algas"); // Nemo está comiendo algas
myFish.swim(); // Nemo está nadando
Cuándo usarlo:
- Compartir funcionalidad entre clases no relacionadas
- Evitar herencia múltiple
- Composición sobre herencia
Patrones de Diseño Comportamentales
Los patrones comportamentales se enfocan en la comunicación entre objetos y cómo distribuyen responsabilidades.
6. Patrón Observer
Piensa en YouTube: cuando tu creador de contenido favorito sube un nuevo video, recibes una notificación instantánea. No tienes que estar revisando constantemente su canal.
El patrón Observer funciona exactamente así: establece una relación donde un objeto (el «subject») avisa automáticamente a todos sus suscriptores cuando algo importante sucede.
Es el corazón de cualquier sistema de notificaciones, actualizaciones en tiempo real y aplicaciones reactivas modernas.
Ejemplo sencillo:
class Subject {
constructor() {
this.observers = [];
}
subscribe(observer) {
this.observers.push(observer);
}
unsubscribe(observer) {
this.observers = this.observers.filter(obs => obs !== observer);
}
notify(data) {
this.observers.forEach(observer => observer.update(data));
}
}
class Observer {
constructor(name) {
this.name = name;
}
update(data) {
console.log(`${this.name} recibió la notificación: ${data}`);
}
}
// Uso
const newsPublisher = new Subject();
const subscriber1 = new Observer("Usuario 1");
const subscriber2 = new Observer("Usuario 2");
newsPublisher.subscribe(subscriber1);
newsPublisher.subscribe(subscriber2);
newsPublisher.notify("¡Nueva noticia disponible!");
// Usuario 1 recibió la notificación: ¡Nueva noticia disponible!
// Usuario 2 recibió la notificación: ¡Nueva noticia disponible!
newsPublisher.unsubscribe(subscriber1);
newsPublisher.notify("Otra noticia");
// Usuario 2 recibió la notificación: Otra noticia
Cuándo usarlo:
- Sistemas de eventos
- Actualizaciones de UI reactivas
- Notificaciones push
- Cambios de estado en aplicaciones

7. Patrón Mediator/Middleware
El patrón Mediator centraliza la comunicación entre objetos, evitando que se referencien directamente entre sí. Esto reduce el acoplamiento y facilita el mantenimiento.
Ejemplo sencillo:
class ChatRoom {
constructor() {
this.users = {};
}
register(user) {
this.users[user.name] = user;
user.chatRoom = this;
}
send(message, from, to) {
if (to) {
// Mensaje privado
to.receive(message, from);
} else {
// Mensaje público
Object.keys(this.users).forEach(key => {
if (this.users[key] !== from) {
this.users[key].receive(message, from);
}
});
}
}
}
class User {
constructor(name) {
this.name = name;
this.chatRoom = null;
}
send(message, to) {
this.chatRoom.send(message, this, to);
}
receive(message, from) {
console.log(`${from.name} a ${this.name}: ${message}`);
}
}
// Uso
const chatRoom = new ChatRoom();
const john = new User("John");
const jane = new User("Jane");
const joe = new User("Joe");
chatRoom.register(john);
chatRoom.register(jane);
chatRoom.register(joe);
john.send("Hola a todos");
// John a Jane: Hola a todos
// John a Joe: Hola a todos
jane.send("Hola John", john);
// Jane a John: Hola John
Cuándo usarlo:
- Sistemas de chat o mensajería
- Coordinación entre componentes complejos
- Pipelines de procesamiento (middleware)
8. Patrón Strategy
El patrón Strategy define una familia de algoritmos, encapsula cada uno y los hace intercambiables. Permite que el algoritmo varíe independientemente de los clientes que lo usan.
Ejemplo sencillo:
// Estrategias de pago
class CreditCardPayment {
pay(amount) {
console.log(`Pagando $${amount} con tarjeta de crédito`);
}
}
class PayPalPayment {
pay(amount) {
console.log(`Pagando $${amount} con PayPal`);
}
}
class CryptoPayment {
pay(amount) {
console.log(`Pagando $${amount} con criptomonedas`);
}
}
// Contexto
class ShoppingCart {
constructor() {
this.amount = 0;
this.paymentStrategy = null;
}
setAmount(amount) {
this.amount = amount;
}
setPaymentStrategy(strategy) {
this.paymentStrategy = strategy;
}
checkout() {
if (!this.paymentStrategy) {
console.log("Por favor selecciona un método de pago");
return;
}
this.paymentStrategy.pay(this.amount);
}
}
// Uso
const cart = new ShoppingCart();
cart.setAmount(100);
cart.setPaymentStrategy(new CreditCardPayment());
cart.checkout(); // Pagando $100 con tarjeta de crédito
cart.setPaymentStrategy(new PayPalPayment());
cart.checkout(); // Pagando $100 con PayPal
Cuándo usarlo:
- Diferentes algoritmos para el mismo problema
- Múltiples formas de procesar datos
- Validaciones o cálculos variables
Patrones de Optimización y Performance
9. Patrón Flyweight
El patrón Flyweight minimiza el uso de memoria compartiendo la mayor cantidad posible de datos con objetos similares. Es útil cuando trabajas con grandes cantidades de objetos similares.
Ejemplo sencillo:
class Book {
constructor(title, author, isbn) {
this.title = title;
this.author = author;
this.isbn = isbn;
}
}
// Flyweight Factory
class BookFactory {
constructor() {
this.existingBooks = {};
}
createBook(title, author, isbn) {
const key = `${title}-${author}-${isbn}`;
if (this.existingBooks[key]) {
console.log("Reutilizando libro existente");
return this.existingBooks[key];
}
console.log("Creando nuevo libro");
const book = new Book(title, author, isbn);
this.existingBooks[key] = book;
return book;
}
getBookCount() {
return Object.keys(this.existingBooks).length;
}
}
// Uso
const factory = new BookFactory();
const book1 = factory.createBook("1984", "George Orwell", "123");
const book2 = factory.createBook("1984", "George Orwell", "123");
const book3 = factory.createBook("Animal Farm", "George Orwell", "456");
console.log(book1 === book2); // true - misma instancia
console.log(book1 === book3); // false - instancia diferente
console.log(`Libros únicos en memoria: ${factory.getBookCount()}`); // 2
Cuándo usarlo:
- Grandes cantidades de objetos similares
- Optimización de memoria
- Sistemas de caché

Análisis de ahorro de memoria:
- Sin Flyweight: Cada instancia del libro ocupa memoria completa (5 objetos × 1KB = 5KB)
- Con Flyweight: Una única instancia compartida más referencias ligeras (1KB + overhead mínimo de referencias)
- Ahorro: ~80% de memoria en este ejemplo. El ahorro aumenta proporcionalmente con más instancias
10. Patrón Prototype
El patrón Prototype crea nuevos objetos clonando instancias existentes. Es útil cuando la creación de un objeto es costosa o compleja.
Ejemplo sencillo:
class Car {
constructor(model, year, features) {
this.model = model;
this.year = year;
this.features = features;
}
clone() {
return new Car(
this.model,
this.year,
[...this.features] // Copia profunda del array
);
}
displayInfo() {
console.log(`${this.year} ${this.model}`);
console.log("Características:", this.features.join(", "));
}
}
// Uso
const originalCar = new Car("Tesla Model 3", 2024, [
"Autopilot",
"Techo panorámico",
"Sistema de sonido premium"
]);
const clonedCar = originalCar.clone();
clonedCar.year = 2025;
clonedCar.features.push("Carga inalámbrica");
originalCar.displayInfo();
// 2024 Tesla Model 3
// Características: Autopilot, Techo panorámico, Sistema de sonido premium
clonedCar.displayInfo();
// 2025 Tesla Model 3
// Características: Autopilot, Techo panorámico, Sistema de sonido premium, Carga inalámbrica
Cuándo usarlo:
- Creación de objetos costosa
- Necesitas múltiples variaciones de un objeto base
- Sistema de configuraciones predefinidas
Patrones Modernos con ES6+
11. Dynamic Import Pattern
El patrón de importación dinámica permite cargar módulos bajo demanda, mejorando el rendimiento inicial de tu aplicación.
Ejemplo sencillo:
// utils.js
export const heavyCalculation = (num) => {
console.log("Ejecutando cálculo pesado...");
return num * num * num;
};
// main.js
async function loadAndCalculate(num) {
// Solo carga el módulo cuando se necesita
const { heavyCalculation } = await import('./utils.js');
return heavyCalculation(num);
}
// Uso con evento
document.getElementById('calculateBtn').addEventListener('click', async () => {
const result = await loadAndCalculate(5);
console.log(`Resultado: ${result}`);
});
Cuándo usarlo:
- Code splitting en aplicaciones grandes
- Cargar funcionalidad solo cuando el usuario la necesita
- Optimizar el tiempo de carga inicial
12. Import on Interaction Pattern
Carga componentes o funcionalidad solo cuando el usuario interactúa con elementos específicos de la UI.
Ejemplo sencillo:
// chatWidget.js
export class ChatWidget {
constructor() {
console.log("Widget de chat inicializado");
}
open() {
console.log("Abriendo chat...");
}
}
// main.js
const chatButton = document.getElementById('chatBtn');
let chatWidget = null;
chatButton.addEventListener('click', async () => {
if (!chatWidget) {
// Solo carga el widget cuando el usuario hace clic
const { ChatWidget } = await import('./chatWidget.js');
chatWidget = new ChatWidget();
}
chatWidget.open();
});
Cuándo usarlo:
- Widgets o modales que no se usan de inmediato
- Funcionalidades premium o avanzadas
- Reducir el bundle inicial
Patrones Anti-Pattern: Lo que Debes Evitar
Conocer los patrones correctos es como saber qué alimentos son saludables, pero identificar los anti-patrones es como reconocer qué comidas te harán daño.
God Object (Objeto Dios)
Un objeto que conoce demasiado o hace demasiado. Viola el principio de responsabilidad única.
// ❌ MAL - God Object
class Application {
constructor() {
this.users = [];
this.products = [];
this.orders = [];
}
addUser(user) { /* ... */ }
deleteUser(id) { /* ... */ }
updateUser(id, data) { /* ... */ }
addProduct(product) { /* ... */ }
deleteProduct(id) { /* ... */ }
createOrder(order) { /* ... */ }
processPayment(orderId) { /* ... */ }
sendEmail(to, message) { /* ... */ }
// ... y muchas más responsabilidades
}
// ✅ BIEN - Separación de responsabilidades
class UserManager {
constructor() {
this.users = [];
}
addUser(user) { /* ... */ }
deleteUser(id) { /* ... */ }
}
class ProductManager {
constructor() {
this.products = [];
}
addProduct(product) { /* ... */ }
}
class OrderManager {
constructor() {
this.orders = [];
}
createOrder(order) { /* ... */ }
}
Callback Hell
Anidación excesiva de callbacks que dificulta la lectura y mantenimiento del código.
// ❌ MAL - Callback Hell
getData(function(a) {
getMoreData(a, function(b) {
getMoreData(b, function(c) {
getMoreData(c, function(d) {
console.log(d);
});
});
});
});
// ✅ BIEN - Async/Await
async function fetchAllData() {
const a = await getData();
const b = await getMoreData(a);
const c = await getMoreData(b);
const d = await getMoreData(c);
console.log(d);
}
Mejores Prácticas al Usar Patrones de Diseño
- No fuerces un patrón: Úsalos solo cuando resuelvan un problema real
- Mantén la simplicidad: Un código simple es mejor que uno «elegante» pero complejo
- Considera el contexto: Lo que funciona en un proyecto puede no ser adecuado en otro
- Documenta tu decisión: Explica por qué elegiste un patrón específico
- Refactoriza cuando sea necesario: Los patrones pueden evolucionar con tu código
Sugerencia de imagen: Infografía con las mejores prácticas enumeradas
Recursos Adicionales
Para profundizar en patrones de diseño en JavaScript moderno, te recomiendo estos recursos:
- Patterns.dev – Guía completa de patrones modernos
- JavaScript Patterns – Patrones específicos de JavaScript vanilla
- Libros: «Learning JavaScript Design Patterns» de Addy Osmani
- JavaScript | MDN – Documentación oficial de JavaScript
- Cheatsheet de Patrones de Diseño en Javascript
Ejercicios Prácticos
Ahora que has aprendido los patrones de diseño, es momento de poner en práctica tus conocimientos. Aquí tienes ejercicios progresivos que te ayudarán a dominar estos conceptos.
Ejercicio 1: Implementa un Singleton para Configuración
Objetivo: Crear un sistema de configuración global que solo pueda tener una instancia.
Requisitos:
- Crea una clase
AppConfigque siga el patrón Singleton - Debe permitir establecer y obtener valores de configuración (theme, language, apiUrl)
- Incluye un método
reset()que restaure la configuración por defecto - Asegúrate de que múltiples instancias devuelvan el mismo objeto
Código de inicio:
class AppConfig {
// TODO: Implementa el patrón Singleton aquí
constructor() {
// Valores por defecto
this.config = {
theme: 'light',
language: 'es',
apiUrl: '<https://api.example.com>'
};
}
get(key) {
// TODO: Devuelve el valor de configuración
}
set(key, value) {
// TODO: Establece un valor de configuración
}
reset() {
// TODO: Restaura valores por defecto
}
}
// Pruebas
const config1 = new AppConfig();
const config2 = new AppConfig();
console.log(config1 === config2); // Debe ser true
config1.set('theme', 'dark');
console.log(config2.get('theme')); // Debe ser 'dark'
Ejercicio 2: Sistema de Notificaciones con Observer
Objetivo: Crear un sistema de notificaciones que notifique a múltiples suscriptores.
Requisitos:
- Implementa una clase
NotificationCenterque actúe como Subject - Crea una clase
Subscriberque pueda recibir notificaciones de diferentes tipos - Permite suscribirse a tipos específicos de notificaciones (email, sms, push)
- Los suscriptores deben poder desuscribirse
Código de inicio:
class NotificationCenter {
constructor() {
this.subscribers = {
email: [],
sms: [],
push: []
};
}
subscribe(type, subscriber) {
// TODO: Agregar suscriptor
}
unsubscribe(type, subscriber) {
// TODO: Remover suscriptor
}
notify(type, message) {
// TODO: Notificar a todos los suscriptores del tipo especificado
}
}
class Subscriber {
constructor(name) {
this.name = name;
}
update(type, message) {
console.log(`${this.name} recibió ${type}: ${message}`);
}
}
// Pruebas
const notificationCenter = new NotificationCenter();
const user1 = new Subscriber("Usuario 1");
const user2 = new Subscriber("Usuario 2");
notificationCenter.subscribe('email', user1);
notificationCenter.subscribe('sms', user1);
notificationCenter.subscribe('email', user2);
notificationCenter.notify('email', 'Nuevo mensaje en tu bandeja');
// Debe notificar a user1 y user2
notificationCenter.notify('sms', 'Código de verificación: 123456');
// Solo debe notificar a user1
Ejercicio 3: Factory para Crear Diferentes Tipos de Usuarios
Objetivo: Implementar un Factory que cree diferentes tipos de usuarios con permisos específicos.
Requisitos:
- Crea clases para tres tipos de usuarios:
AdminUser,ModeratorUser,RegularUser - Cada tipo debe tener diferentes permisos (canDelete, canEdit, canView)
- Implementa un
UserFactoryque cree el tipo correcto según un parámetro - Incluye un método
getPermissions()en cada clase de usuario
Código de inicio:
class AdminUser {
constructor(name) {
this.name = name;
this.role = 'admin';
}
getPermissions() {
// TODO: Devuelve permisos de administrador
}
}
class ModeratorUser {
// TODO: Implementar
}
class RegularUser {
// TODO: Implementar
}
class UserFactory {
static createUser(type, name) {
// TODO: Crear y devolver el tipo correcto de usuario
}
}
// Pruebas
const admin = UserFactory.createUser('admin', 'Alice');
const mod = UserFactory.createUser('moderator', 'Bob');
const user = UserFactory.createUser('regular', 'Charlie');
console.log(admin.getPermissions());
// { canDelete: true, canEdit: true, canView: true }
console.log(mod.getPermissions());
// { canDelete: false, canEdit: true, canView: true }
console.log(user.getPermissions());
// { canDelete: false, canEdit: false, canView: true }
Ejercicio 4: Carrito de Compras con Strategy
Objetivo: Implementar un carrito de compras que pueda calcular descuentos usando diferentes estrategias.
Requisitos:
- Crea estrategias de descuento:
NoDiscount,PercentageDiscount,FixedDiscount - Implementa una clase
ShoppingCartque use estas estrategias - Permite cambiar la estrategia de descuento dinámicamente
- Incluye un método
calculateTotal()que aplique el descuento correspondiente
Código de inicio:
class NoDiscount {
calculate(amount) {
return amount;
}
}
class PercentageDiscount {
constructor(percentage) {
this.percentage = percentage;
}
calculate(amount) {
// TODO: Calcular descuento porcentual
}
}
class FixedDiscount {
// TODO: Implementar descuento fijo
}
class ShoppingCart {
constructor() {
this.items = [];
this.discountStrategy = new NoDiscount();
}
addItem(item, price) {
this.items.push({ item, price });
}
setDiscountStrategy(strategy) {
// TODO: Establecer estrategia de descuento
}
calculateTotal() {
// TODO: Calcular total con descuento aplicado
}
}
// Pruebas
const cart = new ShoppingCart();
cart.addItem('Laptop', 1000);
cart.addItem('Mouse', 50);
console.log(cart.calculateTotal()); // 1050
cart.setDiscountStrategy(new PercentageDiscount(10));
console.log(cart.calculateTotal()); // 945 (10% de descuento)
cart.setDiscountStrategy(new FixedDiscount(100));
console.log(cart.calculateTotal()); // 950 (descuento fijo de 100)
Ejercicio 5: Proyecto Integrador – Sistema de Blog
Objetivo: Crear un sistema de blog completo que combine múltiples patrones de diseño.
Requisitos:
- Usa Singleton para gestionar la configuración del blog
- Implementa Factory para crear diferentes tipos de posts (Text, Image, Video)
- Aplica Observer para notificar a los suscriptores cuando hay nuevos posts
- Utiliza Proxy para controlar el acceso a posts premium
- Implementa Strategy para diferentes algoritmos de ordenamiento (por fecha, por popularidad)
Funcionalidades requeridas:
- Crear posts de diferentes tipos
- Suscribirse a notificaciones de nuevos posts
- Controlar acceso a contenido premium
- Ordenar posts usando diferentes criterios
Este ejercicio es avanzado y te ayudará a entender cómo combinar patrones en una aplicación real.
Soluciones y Discusión
Las soluciones a estos ejercicios, junto con explicaciones detalladas, están disponibles en el repositorio de GitHub del blog. Te animo a intentar resolverlos por tu cuenta primero antes de consultar las soluciones.
Recuerda: No hay una única forma correcta de implementar estos patrones. Lo importante es entender el problema que resuelven y adaptar la solución a tu contexto específico.
Conclusión
Si hay algo que quiero que te lleves de este post: no se trata de memorizar código como si fueras una enciclopedia andante. Se trata de reconocer problemas familiares y saber qué herramienta sacar de tu caja. Ahora sabes cuándo aplicar un Singleton, un Observer o cualquier otro patrón.
¿Qué patrón de diseño te resultó más útil? ¿Ya has implementado alguno en tus proyectos? Comparte tu experiencia en los comentarios. Me encantaría conocer cómo estás aplicando estos patrones en tus aplicaciones.
Sigueme en mis redes sociales para más contenido