Tema 15 · Factory & Inmutabilidad

Patrón Factory, Inmutabilidad con Object.freeze() y Buenas Prácticas

Aprende a encapsular la creación de objetos con el Patrón Factory, blindar estados con Object.freeze() y aplicar las mejores prácticas de la industria en JS.

El Patrón Factory (Factoría) en JavaScript

El Patrón Factory centraliza y oculta la lógica de creación de objetos. En lugar de que el código cliente tenga que saber qué clase instanciar con new y cómo configurarla, simplemente le pide a la factoría: "dame un usuario de tipo admin" o "dame una pasarela de pago para Colombia".

En JavaScript, una factoría puede ser un método estático de una clase (UsuarioFactory.crear()) o una simple función pura que retorna objetos (conocida como Factory Function).

Blindaje e Inmutabilidad con `Object.freeze()`

Una de las mejores prácticas en aplicaciones críticas consiste en evitar que los datos de configuración o estados de negocio sean alterados accidentalmente.

Método de Seguridad ¿Permite Añadir? ¿Permite Borrar? ¿Permite Modificar Valores?
Object.freeze(obj) ❌ No ❌ No ❌ No (Inmutable al 100%)
Object.seal(obj) ❌ No ❌ No ✔ Sí (Solo existentes)

Decálogo de Buenas Prácticas de POO en JavaScript

  • 1. Usa campos privados (`#`): Olvídate de los guiones bajos como _propiedad; utiliza la sintaxis nativa de ES2022.
  • 2. Favorece la composición sobre la herencia: Evita jerarquías de más de dos niveles con extends.
  • 3. Un archivo por clase con ESM: Exporta cada clase en su propio archivo .js o .mjs.
  • 4. Arrow Functions para callbacks: Evita problemas con this usando funciones flecha para eventos y promesas.

⚡ Factoría de Perfiles de Usuario con Inmutabilidad Object.freeze()

Ejecuta el código en el Sandbox y observa la creación estandarizada y la protección contra mutaciones:

FactoryDemo.js
Solo lectura
// 1. Roles del Sistema Inmutables (Sellados con freeze)
const ROLES = Object.freeze({
    ADMIN: "administrador",
    ESTUDIANTE: "estudiante",
    DOCENTE: "docente"
});

// 2. Clases Especializadas de Usuario
class UsuarioBase {
    #id;
    constructor(id, nombre, email, rol, permisos) {
        this.#id = id;
        this.nombre = nombre;
        this.email = email;
        this.rol = rol;
        // Congelamos la lista de permisos para que nadie pueda inyectar privilegios
        this.permisos = Object.freeze([...permisos]);
    }

    tienePermiso(permiso) {
        return this.permisos.includes(permiso);
    }

    obtenerPerfil() {
        return `[#${this.#id}] ${this.nombre} (${this.rol.toUpperCase()}) | Permisos: [${this.permisos.join(", ")}]`;
    }
}

// 3. Patrón Factory (Factoría Centralizada)
class UsuarioFactory {
    static #contadorId = 1;

    static crearUsuario(tipo, nombre, email) {
        const id = UsuarioFactory.#contadorId++;

        switch (tipo.toLowerCase()) {
            case 'admin':
                return new UsuarioBase(
                    id, 
                    nombre, 
                    email, 
                    ROLES.ADMIN, 
                    ['leer', 'crear', 'editar', 'eliminar', 'configurar_sistema']
                );

            case 'docente':
                return new UsuarioBase(
                    id, 
                    nombre, 
                    email, 
                    ROLES.DOCENTE, 
                    ['leer', 'crear', 'editar', 'calificar']
                );

            case 'estudiante':
                return new UsuarioBase(
                    id, 
                    nombre, 
                    email, 
                    ROLES.ESTUDIANTE, 
                    ['leer', 'enviar_tareas']
                );

            default:
                throw new Error(`Tipo de usuario desconocido: ${tipo}`);
        }
    }
}

// ==========================================
// PRUEBA EN EL SANDBOX
// ==========================================
console.log("=== CREACIÓN DE USUARIOS VÍA FACTORY ===");
const admin = UsuarioFactory.crearUsuario('admin', 'Carlos Mendoza', '[email protected]');
const profe = UsuarioFactory.crearUsuario('docente', 'Valeria Torres', '[email protected]');
const alumno = UsuarioFactory.crearUsuario('estudiante', 'Mateo Silva', '[email protected]');

console.log(admin.obtenerPerfil());
console.log(profe.obtenerPerfil());
console.log(alumno.obtenerPerfil());

console.log("\n=== COMPROBACIÓN DE PERMISOS ===");
console.log("¿El alumno puede eliminar?:", alumno.tienePermiso('eliminar')); // false
console.log("¿El admin puede configurar el sistema?:", admin.tienePermiso('configurar_sistema')); // true

console.log("\n=== PRUEBA DE INMUTABILIDAD CON OBJECT.FREEZE ===");
// Intento de mutar la lista de permisos congelada:
try {
    alumno.permisos.push('eliminar'); // Fallará porque el array está congelado
} catch (e) {
    console.log("✔ Intento de inyectar permisos bloqueado por Object.freeze():", e.message);
}
console.log("Permisos del alumno intactos:", alumno.permisos);
Pruébalo tú mismo » Se abre en una nueva pestaña con ejecución en vivo
🧠
Mini-Quiz de Retención
Comprueba tu comprensión antes de pasar a la siguiente lección
1 Pregunta
Pregunta 1
¿Qué efecto produce ejecutar Object.freeze(objeto) sobre una estructura de datos en JavaScript?
🦉
¿Tienes dudas con este tema?
Sócrates AI puede darte analogías y explicarte este código paso a paso.

🚀 ¿Quieres dominar Manual de Programación Orientada a Objetos en JavaScript (POO) y crear proyectos reales?

Aprende arquitectura limpia, desarrollo web moderno, buenas prácticas y despliegue en producción con nuestro Bootcamp Fullstack Developer con mentorías en vivo.

🏛️ Autoría y Propiedad Intelectual: Obra educativa original de David Amador para Kilopolis Academia (Solucionáticos), estructurada sobre las especificaciones y estándares abiertos del W3C, el estándar vivo de WHATWG y la documentación oficial de código abierto. © Todos los derechos reservados.