Gerando padrões de design no ES6 + no exemplo de Game of Thrones



Padrões de design - maneiras de resolver os problemas mais comuns no desenvolvimento de software. Neste artigo, veremos padrões generativos com referências ao Game of Thrones.

Leia sobre padrões estruturais aqui .

Os padrões de geração são projetados para trabalhar com mecanismos de construção de objetos, a fim de criar um objeto da maneira mais apropriada nessa situação.

Os padrões generativos mais comuns são os seguintes:

  • Fábrica (Tecido)
  • Abstração (Resumo)
  • Construtor (Construtor)
  • Protótipo (Protótipo)
  • Singleton (Singleton)

Fábrica


Uma fábrica é um padrão que usa os chamados métodos de fábrica para criar objetos sem a necessidade de determinar a classe do objeto criado. O que isto significa?

Imagine que queremos ser capazes de criar soldados. Esses soldados podem ser da casa Targaryen ou da casa Lannister.

Para isso, precisamos:

  • Interface para definir soldados
  • Uma classe para cada tipo de soldado para capacitar todos os lares
  • Classe para solicitar a criação de um novo soldado, independentemente da casa à qual ele pertence

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())
})()

Quando é usado?


  • ,
  • ,


Isenção de responsabilidade: um modelo abstrato com base em objetos. É extremamente difícil de usar na programação funcional.

Este modelo permite encapsular um grupo de fábricas individuais que executam tarefas semelhantes, sem a necessidade de definir classes específicas. No uso padrão, o software cliente cria uma implementação específica da fábrica abstrata e, em seguida, usa a interface geral da fábrica para criar objetos específicos como parte de um único sistema. O cliente não sabe (ou não importa para ele) quais objetos específicos recebe de cada fábrica interna, pois uma interface comum é usada para isso.

Imagine que queremos gerenciar cada casa individualmente. Cada casa deve ter a oportunidade de determinar o herdeiro do trono e sua amada.

Para isso, precisamos:

  • ,

//  
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()
})()

?


  • ,
  • ,


O objetivo do designer é separar um objeto complexo de suas representações. Com a complexidade do objeto, este modelo permite separar o processo de criação de um novo objeto por outro objeto (construtor).

Imagine que queremos criar uma frota para cada casa. Cada família terá um certo número de navios e guerreiros. Para facilitar a criação, poderemos chamar o método makeNavy, que criará automaticamente tudo o que precisamos através das configurações.

Para isso, precisamos:

  • Classe para criar uma frota
  • Interface para definição de construtor
  • A classe responsável por criar os objetos do nosso exército
  • Classes responsáveis ​​pela criação de soldados e navios

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)
})()

Quando é usado?


  • Quando o processo de criação de um objeto é muito complexo, envolve um grande número de parâmetros obrigatórios e opcionais
  • Quando um aumento no número de parâmetros do construtor leva a um aumento no número de construtores
  • Quando o cliente espera visualizações diferentes para o objeto construído

Protótipo


O protótipo permite determinar os tipos de objetos criados por meio da herança do protótipo e criar novos objetos usando o esquema de um objeto existente. Isso melhora o desempenho e minimiza a perda de memória.

Imagine que queremos criar um exército de caminhantes brancos. Não temos requisitos especiais para este exército. Nós apenas queremos muitos deles, que tenham as mesmas características e que não ocupem muito espaço na memória.

Para isso, precisamos:

  • Classe para armazenar informações sobre caminhantes brancos
  • Método para clonar uma instância retornando o mesmo 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())
    }
})()

Vantagens e desvantagens do protótipo


Prós:

  • Ajuda a economizar custo, tempo e produtividade, eliminando a necessidade de usar um novo operador para criar novos objetos
  • Reduz a complexidade de inicializar um objeto: cada classe usa seu próprio método de clonagem
  • Não há necessidade de classificar e criar muitas subclasses para inicializar objetos, como ao usar um modelo abstrato
  • Aumenta a flexibilidade do sistema criando novos objetos, alterando algumas propriedades do objeto copiado

Minuses:

  • A clonagem de objetos complexos com referências circulares é uma tarefa não trivial

Singleton


O Singleton permite garantir que o objeto criado seja a única instância de uma classe específica. Esse objeto geralmente é usado para controlar várias operações no sistema.

Imagine que queremos ter certeza de ter uma única mãe-dragão.

Para isso, precisamos:

  • Classe para armazenar informações sobre a Mãe dos Dragões
  • Instância salva de Mãe Dragão

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)
})()

Quando é usado?


  • Quando os recursos usados ​​para criar o objeto são limitados (por exemplo, objetos de conexão com o banco de dados)
  • É uma boa prática criar autorização com um singleton para melhorar o desempenho.
  • Quando você precisa criar uma classe para configurar o aplicativo
  • Quando você precisa criar uma classe para alocação de recursos

Código do Github .

Nota trans: aqui está um ótimo vídeo sobre padrões de design.

Obrigado pela atenção.

All Articles