Generando patrones de diseño en ES6 + en el ejemplo de Game of Thrones



Patrones de diseño: formas de resolver los problemas más comunes en el desarrollo de software. En este artículo, veremos patrones generativos con referencias al Juego de Tronos.

Lea sobre los patrones estructurales aquí .

Los patrones de generación están diseñados para trabajar con mecanismos para construir objetos con el fin de crear un objeto de la manera más adecuada en esta situación.

Los patrones generativos más comunes son los siguientes:

  • Fábrica (tela)
  • Abstracción (Resumen)
  • Constructor (Constructor)
  • Prototipo (Prototipo)
  • Singleton (Singleton)

Fábrica


Una fábrica es un patrón que utiliza los llamados métodos de fábrica para crear objetos sin la necesidad de determinar la clase del objeto creado. ¿Qué significa esto?

Imagina que queremos poder crear soldados. Estos soldados pueden ser de la casa Targaryen o de la casa Lannister.

Para esto necesitamos:

  • Interfaz para definir soldados
  • Una clase para cada tipo de soldado para potenciar cada hogar
  • Clase para solicitar la creación de un nuevo soldado, independientemente de la casa a la que pertenece

class Soldier {
    constructor(name) {
        this.name = name
        this.attack = this.attack.bind(this)
    }

    attack() {}
}

class LannisterSoldier extends Soldier {
    attack() {
        return 'Lannister always pays his debts'
    }
}

class TargaryenSoldier extends Soldier {
    attack() {
        return 'Fire and blond'
    }
}

class Spawn {
    constructor(type, name) {
        if (type === 'lannister') return new LannisterSoldier(name)
        else return new TargaryenSoldier(name)
    }
}

(() => {
    const lannister = new Spawn('lannister', 'soldier1')
    const targaryen = new Spawn('targaryen', 'soldier2')
    console.log(lannister.attack())
    console.log(targaryen.attack())
})()

Cuando se usa


  • ,
  • ,


Descargo de responsabilidad: una plantilla abstracta basada en objetos. Es extremadamente difícil de usar en la programación funcional.

Esta plantilla le permite encapsular un grupo de fábricas individuales que realizan tareas similares, sin la necesidad de definir clases específicas. En el uso estándar, el software del cliente crea una implementación específica de la fábrica abstracta y luego usa la interfaz de fábrica general para crear objetos específicos como parte de un solo sistema. El cliente no sabe (o no le importa) qué objetos específicos recibe de cada fábrica interna, ya que se utiliza una interfaz común para esto.

Imagine que queremos administrar cada casa individualmente. Cada casa debe tener la oportunidad de determinar el heredero del trono y su amada.

Para esto necesitamos:

  • ,

//  
class HeirToTheThrone {
    conctructor(name, isActualOnTheThrone) {
        this.name = name
        this.isActualOnTheThrone = isActualOnTheThrone
        this.getTheThrone = this.getTheThrone.bind(this)
    }
    getTheThrone() {}
}

class HeirToTheThroneLannister extends HeirToTheThrone {
    getTheThrone(){
        console.log('kill all')
    }
}

class HeirToTheThroneTargaryen extends HeirToTheThrone {
    getTheThrone() {
        console.log('burn all')
    }
}

//  
class Subject {
    constructor(name) {
        this.name = name
        this.speak = this.speak.bind(this)
    }
    speak() {}
}

class SubjectLannister extends Subject {
    speak(){
        console.log('i love Cersei')
    }
}

class SubjectTargaryen extends Subject {
    speak(){
        console.log('i love Daenerys')
    }
}

//  
class House {
    constructor() {
        this.getHeir = this.getHeir.bind(this)
        this.getSubject = this.getSubject.bind(this)
    }
    getHeir(){}
    getSubject(){}
}

class Lannister extends House {
    getHeir() {
        return new HeirToTheThroneLannister('Cersei', true)
    }
    getSubject(name) {
        return new SubjectLannister(name)
    }
}

class Targaryen extends House {
    getHeir() {
        return new HeirToTheThroneTargaryen('Daenerys', true)
    }
    getSubject(name) {
        return new SubjectTargaryen(name)
    }
}

(()=>{
    const lannister = new Lannister()
    const targaryen = new Targaryen()
    lannister.getHeir().getTheThrone()
    lannister.getSubject().speak()
    targaryen.getHeir().getTheThrone()
    targaryen.getSubject().speak()
})()

?


  • ,
  • ,


El propósito del diseñador es separar un objeto complejo de sus representaciones. Con la complejidad del objeto, esta plantilla le permite separar el proceso de creación de un nuevo objeto a través de otro objeto (constructor).

Imagine que queremos crear una flota para cada hogar. Cada familia tendrá una cierta cantidad de barcos y guerreros. Para facilitar la creación, deberíamos poder llamar al método makeNavy, que creará automáticamente todo lo que necesitemos a través de la configuración.

Para esto necesitamos:

  • Clase para crear una flota.
  • Interfaz para definición de constructor
  • La clase responsable de crear los objetos de nuestro ejército.
  • Clases responsables de crear soldados y barcos.

class Lannister {
    constructor() {
        this.soldiers = []
        this.ships = []
        this.makeNavy = this.makeNavy.bind(this)
    }
    makeNavy(soldiers, ships) {
        const Build = new ConcreteBuilder()
        for (let i = 0; i < soldiers; i++) {
            this.soldiers.push(Build.createSoldier())
        }
        for (let i = 0; i < ships; i++) {
            this.ships.push(Build.createShip())
        }
    }
}

class Builder {
    createSoldier() {}
    createShip() {}
}

class ConcreteBuilder extends Builder {
    createSoldier() {
        const soldier = new Soldier()
        return soldier
    }
    createShip() {
        const ship = new Ship()
        return ship
    }
}

class Soldier {
    constructor() {
        console.log('soldier created')
    }
}

class Ship {
    constructor() {
        console.log('ship created')
    }
}

(() => {
    const lannister = new Lannister()
    lannister.makeNavy(100, 10)
})()

Cuando se usa


  • Cuando el proceso de creación de un objeto es muy complejo, involucra una gran cantidad de parámetros obligatorios y opcionales
  • Cuando un aumento en el número de parámetros de constructor conduce a un aumento en el número de constructores
  • Cuando el cliente espera diferentes vistas para el objeto construido

Prototipo


El prototipo le permite determinar los tipos de objetos creados a través de la herencia del prototipo y crear nuevos objetos utilizando el esquema de un objeto existente. Esto mejora el rendimiento y minimiza la pérdida de memoria.

Imagina que queremos crear un ejército de caminantes blancos. No tenemos requisitos especiales para este ejército. Solo queremos muchos de ellos, que tengan las mismas características y que no ocupen mucho espacio en la memoria.

Para esto necesitamos:

  • Clase para almacenar información sobre caminantes blancos
  • Método para clonar una instancia que devuelve el mismo método

class WhiteWalker {
    constructor(force, weight) {
        this.force = force
        this.weight = weight
    }

    clone() {
        console.log('cloned')
        return new WhiteWalker(this.name, this.weight)
    }
}

(()=>{
    const firstWalker = new WhiteWalker()
    const walkers = [firstWalker]
    for(let i=0;i<100;i++){
        walkers.push(firstWalker.clone())
    }
})()

Ventajas y desventajas del prototipo.


Pros:

  • Ayuda a ahorrar costos, tiempo y productividad al eliminar la necesidad de usar un nuevo operador para crear nuevos objetos
  • Reduce la complejidad de inicializar un objeto: cada clase usa su propio método de clonación
  • No es necesario clasificar y crear muchas subclases para inicializar objetos como cuando se usa una plantilla abstracta
  • Aumenta la flexibilidad del sistema al crear nuevos objetos al cambiar algunas propiedades del objeto copiado

Desventajas:

  • Clonar objetos complejos con referencias circulares es una tarea no trivial

único


Singleton le permite asegurarse de que el objeto creado sea la única instancia de una clase en particular. Tal objeto se usa generalmente para controlar varias operaciones en el sistema.

Imagina que queremos estar seguros de tener una sola Madre Dragón.

Para esto necesitamos:

  • Clase para almacenar información sobre Madre de dragones
  • Instancia guardada de Dragon Mother

let INSTANCE = null

class MotherOfDragons {
    constructor(name, dragons) {
        if(!!INSTANCE) return INSTANCE
        this.dragons = dragons
        this.name = name
        this.getMyDragons = this.getMyDragons.bind(this)
        INSTANCE = this
    }

    getMyDragons(){
        console.log(`I'm ${this.name} and my dragons are`, this.dragons)
    }
}

(()=>{
    const dragonMother = new MotherOfDragons('Daenerys Targaryen', [
        'Drogon',
        'Rhaegal',
        'Viserion'
    ])
    const dragonMother2 = new MotherOfDragons('Cercei Targaryen', [
        'Tirion',
        'Jennifer',
        'Goblin'
    ])
    dragonMother.getMyDragons()
    dragonMother2.getMyDragons()
    console.log(dragonMother instanceof MotherOfDragons)
    console.log(dragonMother2 instanceof MotherOfDragons)
    console.log(dragonMother2 === dragonMother)
})()

Cuando se usa


  • Cuando los recursos utilizados para crear el objeto son limitados (por ejemplo, objetos de conexión de base de datos)
  • Es una buena práctica crear autorización con un singleton para mejorar el rendimiento.
  • Cuando necesita crear una clase para configurar la aplicación
  • Cuando necesita crear una clase para la asignación de recursos

Github código .

Nota trans: aquí hay un gran video sobre patrones de diseño.

Gracias por su atención.

All Articles