Hamburger Icon
Patrones de diseño en JavaScript

Patrones de diseño en JavaScript

En este post veremos qué son los patrones de diseño y algunos de los que pueden sernos útiles en el día a día. Es bueno saber tanto implementarlos como detectarlos, para entender y organizar mejor la lógica de las aplicaciones web.

Un patrón de diseño es una abstracción que puede ser útil en una situación determinada. La implementación no tiene por qué ser siempre igual, eso dependerá de cada caso de uso.

Vamos a ver tres de los más comunes.

Singleton

Este patrón permite asegurar que una clase solo tenga una única instancia inmutable. Útil para cuando se necesita manipular o controlar un recurso compartido y queremos que ese sea el centro de información, como por ejemplo la configuración general de una aplicación:

class Configuracion {
  constructor(param1, param2, param3) {
    this.param1 = param1
    this.param2 = param2
    this.param3 = param3
  }

  start() {
    console.log(
      `Configuración creada con parámetros: ${this.param1}, ${this.param2} y ${this.param3}`
    )
  }
}

// creamos una instancia
const instancia = new Configuracion("valor1", "valor2", "valor3")

// congelamos la instancia para que no se puede modificar
Object.freeze(instancia)

instancia.start()

// esto no tendrá ningún efecto
instancia.param3 = "valor300"
instancia.start()

// Configuración creada con parámetros: valor1, valor2 y valor3
// Configuración creada con parámetros: valor1, valor2 y valor3

Y usando objetos literales podemos hacer lo mismo:

const Configuracion = {
  param1: "valor1",
  param2: "valor2",
  param3: "valor3",
}

Object.freeze(Configuracion)

console.log(Configuracion.param3)

// esto no tendrá ningún efecto
Configuracion.param3 = "valor300"
console.log(Configuracion.param3)

// valor3
// valor3

Patrón constructor

Cuando se necesita crear objetos por partes o por pasos, añadiendo funcionalidad a medida que se cumplen algunas condiciones, podemos usar el patrón constructor:

const vehiculo1 = {
  nombre: "coche",
  tipo: "terrestre",
}

const vehiculo2 = {
  nombre: "avión",
  tipo: "volador",
}

const mejorar = (vehiculo) => {
  if (vehiculo.tipo === "terrestre") {
    vehiculo.circular = () => {
      console.log("El coche arranca y circula por tierra")
    }
  }

  if (vehiculo.tipo === "volador") {
    vehiculo.circular = () => {
      console.log("El avión arranca y despega en el aire")
    }
  }
}

// añade funcionalidad dependiendo del tipo de vehículo
mejorar(vehiculo1)
mejorar(vehiculo2)

vehiculo1.circular() // El coche arranca y circula por tierra
vehiculo2.circular() // El avión arranca y despega en el aire

Vemos como la función mejorar() añade una funcionalidad diferente dependiendo del tipo de vehículo. No es necesario usar una misma función para hacerlo, la idea principal es que se añade funcionalidad dependiendo de algún factor.

Patrón fachada

Con este patrón se da acceso de forma simple a una serie de funciones de algún framework, o API. Tremendamente útil si queremos poner una parte del código a disposición de otros usuarios de una forma simplificada, abstrayendo complejidad:

Por ejemplo, imagina que tenemos este código donde usamos la API pública de Agify para hacer una estimación de la edad de alguien a partir de su nombre y su país:

fetch("https://api.agify.io?name=agapito&country_id=ES")
  .then((res) => res.json())
  .then((data) => console.log(`Edad de Agapito: ${data.age}`))
  .catch((error) => console.log(error))

fetch("https://api.agify.io?name=prudencio&country_id=ES")
  .then((res) => res.json())
  .then((data) => console.log(`Edad de Prudencio: ${data.age}`))
  .catch((error) => console.log(error))

fetch("https://api.agify.io?name=pol&country_id=ES")
  .then((res) => res.json())
  .then((data) => console.log(`Edad de Pol: ${data.age}`))
  .catch((error) => console.log(error))

fetch("https://api.agify.io?name=Anna&country_id=ES")
  .then((res) => res.json())
  .then((data) => console.log(`Edad de Anna: ${data.age}`))
  .catch((error) => console.log(error))

/* 
Edad de Prudencio: 65
Edad de Agapito: 61
Edad de Anna: 59
Edad de Pol: 57
*/

Si te duelen los ojos de ver tanto código repetido, ya somos dos. El patrón fachada también es útil, a parte de para abstraer complejidad, para aplicar el principio DRY (Don't Repeat Yourself):

const agify = (p) => {
  let pais = p

  const edad = async (nombre) => {
    try {
      const res = await fetch(
        `https://api.agify.io?name=${nombre}&country_id=${pais}`
      )
      const data = await res.json()
      return `Edad de ${nombre}: ${data.age}`
    } catch (error) {
      return `Error ${error}`
    }
  }

  const actualizaPais = (p) => {
    pais = p
  }

  return { edad, actualizaPais }
}

;(async () => {
  const calcula = agify("US")
  console.log(await calcula.edad("Robert"))
  console.log(await calcula.edad("James"))
  console.log(await calcula.edad("John"))
})()

/* 
Edad de Robert: 72
Edad de James: 59
Edad de John: 71
*/

Hemos definido agify como una closure que devuelve las funciones edad y actualizaPais. Con esto abstraemos la complejidad que pueda tener la API y ponemos a disposición una forma más sencilla de uso.

Conclusiones

Estos son solo tres patrones bastante usados que puede ser útil conocer, detectar y aplicar. Existen muchos otros patrones y todos y cada uno de ellos puede ser usado en situaciones determinadas. En resumen, un patrón intenta aplicar una solución estándar a una situación especial que puede darse mientras se programa.