Patrón Module
Encapsula estado y comportamiento privados dentro de una unidad auto-contenida con una API pública. Un patrón structural para organizar código en módulos reutilizables y seguros en scope.
Nota para desarrolladores hispanohablantes: Esta guía incluye ejemplos y convenciones de nomenclatura adaptadas a equipos que trabajan en español. Cuando existen diferencias significativas en terminología técnica entre el inglés y el español, se indican explícitamente para facilitar la comunicación en equipos multiculturales.
Descripción General
El Patrón Module encapsula variables y funciones privadas dentro de una unidad auto-contenida, exponiendo solo una API pública curada. Previene la polución de namespace, evita conflictos de variables globales y crea límites claros entre partes no relacionadas de un codebase.
En JavaScript antes de ES6, esto se lograba con Immediately Invoked Function Expressions (IIFE). Los lenguajes modernos proveen módulos nativos (ES modules, paquetes Python, paquetes Java), pero la idea central del patrón — ocultar internals y exponer interfaces — sigue siendo fundamental.
Cuándo Usar
- For alternatives, see Adapter Pattern.
Usa el Patrón Module cuando:
- Necesitas estado privado al que no se puede acceder desde fuera del módulo
- Múltiples componentes comparten un codebase y deben evitar colisiones de nombres
- Quieres exponer una API limpia mientras ocultas la complejidad de implementación
- Las pruebas requieren límites claros entre contratos públicos y helpers privados
Cuándo Evitar
- El lenguaje provee módulos nativos con encapsulación adecuada (úsalos en su lugar)
- Sobre-modularizar scripts simples agrega indirección innecesaria
- Necesitas dependencias profundas entre módulos que crean referencias circulares
Solución
JavaScript (IIFE)
const CounterModule = (function () {
let count = 0; // Estado privado
function log(operation) {
console.log(`Counter ${operation}: ${count}`);
}
return {
increment() {
count++;
log('incremented');
return count;
},
decrement() {
count--;
log('decremented');
return count;
},
getCount() {
return count;
}
};
})();
// Uso
CounterModule.increment();
CounterModule.increment();
console.log(CounterModule.getCount()); // 2
// CounterModule.count es undefined — no accesible
JavaScript (ES Module)
// counter.js
let count = 0; // Privado al módulo
function log(operation) {
console.log(`Counter ${operation}: ${count}`);
}
export function increment() {
count++;
log('incremented');
return count;
}
export function decrement() {
count--;
log('decremented');
return count;
}
export function getCount() {
return count;
}
// main.js
import { increment, getCount } from './counter.js';
increment();
console.log(getCount());
Python
# counter.py
_count = 0 # Privado al módulo por convención
def _log(operation):
print(f"Counter {operation}: {_count}")
def increment():
global _count
_count += 1
_log("incremented")
return _count
def decrement():
global _count
_count -= 1
_log("decremented")
return _count
def get_count():
return _count
# main.py
from counter import increment, get_count
increment()
increment()
print(get_count()) # 2
# counter._count es accesible pero desaconsejado
Java
// com.myapp.counter.Counter.java
package com.myapp.counter;
public class Counter {
private static int count = 0; // Acceso package-private
private static void log(String operation) {
System.out.println("Counter " + operation + ": " + count);
}
public static int increment() {
count++;
log("incremented");
return count;
}
public static int decrement() {
count--;
log("decremented");
return count;
}
public static int getCount() {
return count;
}
}
// Uso
Counter.increment();
System.out.println(Counter.getCount());
Explicación
El Patrón Module se basa en:
- Scope privado: Las variables y funciones existen solo dentro del límite del módulo
- API pública: Las funciones exportadas explícitamente forman el contrato con los consumidores
- Responsabilidad única: Cada módulo maneja un solo concern (contar, formatear, HTTP)
Variantes
| Variante | Mecanismo | Caso de Uso |
|---|---|---|
| IIFE Module | Closure-based privacy | JavaScript pre-ES6 |
| Revealing Module | Retorna un object literal | Definición de API más limpia en JS |
| CommonJS | module.exports | Node.js antes de ES modules |
| ES Module | export/import | Estándar moderno de JavaScript |
| Namespace Module | Namespace object literal | Organizar utilidades bajo un global |
Lo que funciona
- Un concern por módulo. Un módulo que cuenta, formatea fechas y hace HTTP requests debería dividirse.
- Usa exports explícitos. No confíes en wildcard exports; ocultan el contrato público.
- Nombra miembros privados claramente. Python usa
_prefijo; JavaScript los oculta con closures. - Evita dependencias circulares. El módulo A importando B mientras B importa A causa errores de runtime.
- Mantén los módulos pequeños. Un módulo de 500 líneas probablemente hace demasiado. Apunta a 100-200 líneas.
Errores Comunes
- Estado global en módulos los hace no testeables. Un módulo contador con un
countglobal no se puede resetear fácilmente entre tests. - Wild-card exports (
export *) filtran helpers internos que se convierten en APIs públicas accidentales. - Side effects en import como conectar a bases de datos o registrar event handlers hacen los módulos impredecibles.
- Rutas de módulo profundamente anidadas (
a.b.c.d.e) crean cadenas de imports frágiles. Aplana donde sea posible. - Tratar módulos como clases — los módulos son namespaces, no objetos instanciables. Usa clases o factories cuando necesites múltiples instancias.
Ejemplos del Mundo Real
Módulos Core de Node.js
fs, path y http son módulos built-in que encapsulan operaciones del SO detrás de APIs limpias. Ninguno expone bindings C++ internos directamente.
Python Standard Library
json, re y urllib son módulos que ocultan motores de parsing y compiladores regex detrás de interfaces de funciones simples.
Angular Modules
Los decoradores NgModule agrupan componentes, servicios y directivas en unidades de feature cohesivas con exports e imports explícitos.
Temas Avanzados
Escenario: Module Pattern para Encapsulacion en JS
// Module pattern: encapsulacion privada con closures
const PaymentModule = (function() {
// Privado: no accesible desde fuera
let apiKey = "";
let transactions: Transaction[] = [];
const MAX_TRANSACTIONS = 1000;
// Privado: validar API key
function validateKey(key: string): boolean {
return key.startsWith("pk_") && key.length === 32;
}
// Privado: logging interno
function log(msg: string) {
console.log(`[PAYMENT] ${new Date().toISOString()} ${msg}`);
}
// Publico: API expuesta
return {
init(key: string) {
if (!validateKey(key)) throw new Error("Invalid API key");
apiKey = key;
log("Module initialized");
},
charge(amount: number, currency: string): Promise<Transaction> {
if (!apiKey) throw new Error("Module not initialized");
if (transactions.length >= MAX_TRANSACTIONS) {
throw new Error("Transaction limit reached");
}
log(`Charging ${amount} ${currency}`);
const tx = { id: crypto.randomUUID(), amount, currency, status: "pending" };
transactions.push(tx);
return Promise.resolve(tx);
},
getTransactions(): Transaction[] { return [...transactions]; }
getStats() {
return {
total: transactions.length,
pending: transactions.filter(t => t.status === "pending").length,
completed: transactions.filter(t => t.status === "completed").length,
};
}
};
})();
// Uso
PaymentModule.init("pk_test_1234567890123456789012345678");
const tx = await PaymentModule.charge(99.99, "USD");
console.log(PaymentModule.getStats()); // { total: 1, pending: 1, completed: 0 }
// PaymentModule.apiKey -> undefined (privado)
// PaymentModule.validateKey -> undefined (privado)
Lecciones:
- Module pattern: IIFE crea un closure con estado privado
- Solo lo que se retorna en el objeto es publico
- apiKey, transactions y funciones internas son privadas
- En ES6+, usar import/export nativo en lugar de IIFE
- El module pattern sigue siendo util para singletons con estado
- Namespacing: agrupar funcionalidad relacionada bajo un modulo
### Module pattern vs ES modules: cual uso?
Usa ES modules (import/export) para todo codigo nuevo: es el estandar, soportado nativamente por browsers y Node.js. Usa el module pattern (IIFE) solo para compatibilidad legacy o cuando necesitas un singleton con estado privado sin clases. ES modules son estaticos: el bundler puede tree-shake. El module pattern es dinamico: todo se incluye. Para librerias nuevas, ES modules. Para scripts inline o compatibilidad con sistemas viejos, module pattern.
## Troubleshooting
- **Pattern does not fit the problem**: re-evaluate the forces (performance, scalability, team size, coupling). A pattern is only appropriate when its trade-offs match your constraints.
- **Too many abstractions**: if adding a pattern increases complexity without a clear benefit, simplify. Not every module needs a factory, decorator, or strategy.
- **Tight coupling after refactoring**: check that interfaces are stable and dependencies point inward.
- **Tests break when the design changes**: favor stable contracts over internal structure.
- **Performance regression from indirection**: measure before and after. Layers, decorators, and adapters can add latency; cache or inline hot paths if needed. Related Resources
Patrón Facade
Provee una interfaz simplificada a un subsistema complejo. Un patrón estructural que oculta detalles de implementación detrás de una API limpia.
PatternPatrón Singleton
Garantiza que una clase tenga una única instancia y proporciona un acceso global a ella. Patrón de diseño creacional para controlar la creación de objetos.
PatternPatrón Repository
Abstrae la lógica de acceso a datos detrás de una interfaz limpia. Patrón de diseño arquitectural para capas de datos testeables y mantenibles.
Preguntas frecuentes
- Es el Patrón Module lo mismo que una clase?
- No. Una clase es un blueprint instanciable. Un módulo es un namespace singleton. Usa módulos para organización; clases para creación de objetos.
- Los módulos pueden tener dependencias entre sí?
- Sí, pero evita dependencias circulares. Si A importa B y B importa A, refactoriza código compartido en un tercer módulo C.
- Cómo testeo funciones privadas de un módulo?
- No las testees directamente. Si una función privada es lo suficientemente compleja como para necesitar testing, extráela a su propio módulo o hazla package-private.