Patrones de diseño en TypeScript: cuándo y por qué
\n\nImagina que estás construyendo una casa. Puedes colocar ladrillos de cualquier manera y, probablemente, no se derrumbará. Pero, ¿será cómodo vivir en ella? Difícilmente. La arquitectura de software es la misma construcción, solo que en lugar de ladrillos tienes clases y funciones. Los patrones de diseño en TypeScript son planos probados para problemas arquitectónicos típicos. Pero, como cualquier herramienta, hay que usarlos con inteligencia.
\n\nEn este artículo no vamos a repetir la teoría seca de los libros de GoF (Gang of Four). En su lugar, analizaremos qué patrones de diseño de TypeScript son realmente necesarios en el desarrollo moderno, cuándo salvan un proyecto y cuándo solo añaden dolores de cabeza. ¡Allá vamos!
\n\n¿Por qué TypeScript cambia las reglas del juego?
\n\nAntes de sumergirnos en los patrones, recordemos la característica principal de TypeScript: el tipado estático. En JavaScript, a menudo usábamos patrones para compensar la naturaleza dinámica del lenguaje. Por ejemplo, el Método Fábrica (Factory Method) ayudaba a ocultar la complejidad de crear objetos. En TypeScript, esta complejidad a menudo se resuelve simplemente con interfaces y genéricos.
\n\nPero esto no significa que los patrones hayan muerto. Al contrario, TypeScript los hace más expresivos y seguros. El compilador verifica que hayas implementado correctamente la estructura y no te deja cometer errores tontos. Aquí es donde TypeScript revela el poder de los patrones GoF.
\n\nTop 5 patrones que usarás con más frecuencia
\n\n1. Singleton (Instancia Única) — cuando uno es bueno y dos sobran
\n\nCuándo usarlo: Necesitas exactamente una instancia de una clase para toda la aplicación. Ejemplos clásicos: logger, configuración de la aplicación, conexión a la base de datos.
\n\nPor qué en TypeScript: TypeScript permite hacer un Singleton seguro para hilos y con seguridad de tipos. Sin él, corres el riesgo de crear decenas de objetos que desperdiciarán memoria y confundirán el estado.
\n\nEjemplo de código:
\n\nclass DatabaseConnection {\n private static instance: DatabaseConnection;\n private connectionString: string;\n\n private constructor(connectionString: string) {\n this.connectionString = connectionString;\n // Simulación de conexión\n console.log(`Conectando a ${connectionString}`);\n }\n\n static getInstance(connectionString: string): DatabaseConnection {\n if (!DatabaseConnection.instance) {\n DatabaseConnection.instance = new DatabaseConnection(connectionString);\n }\n return DatabaseConnection.instance;\n }\n\n query(sql: string): void {\n console.log(`Ejecutando consulta: ${sql}`);\n }\n}\n\n// Uso\nconst db = DatabaseConnection.getInstance('postgres://localhost:5432/mydb');\ndb.query('SELECT * FROM users');\n\n// Intentar crear otra conexión devuelve la misma instancia\nconst db2 = DatabaseConnection.getInstance('postgres://localhost:5432/mydb');\nconsole.log(db === db2); // true\n\nAntipatrón: No hagas un Singleton solo por hacerlo. Si el objeto puede existir en varias instancias con diferentes estados (por ejemplo, diferentes conexiones a distintas bases de datos), entonces Singleton es malo.
\n\n2. Factory Method (Método Fábrica) — creación de objetos sin dolor
\n\nCuándo usarlo: No sabes de antemano qué objeto exacto necesitas crear. O el proceso de creación es complejo y requiere lógica.
\n\nPor qué en TypeScript: El Método Fábrica se combina perfectamente con interfaces y genéricos. Obtienes flexibilidad sin perder los tipos.
\n\nEjemplo de código:
\n\n// Producto abstracto\ninterface Notification {\n send(message: string): void;\n}\n\n// Productos concretos\nclass EmailNotification implements Notification {\n send(message: string): void {\n console.log(`Enviando email: ${message}`);\n }