DEV Community

Stefany Repetcki
Stefany Repetcki

Posted on

4 Design Patterns para usar no frontend

Existem 23 padrões oficiais do livro Design Patterns - Elements of Reusable Object-Oriented Software, que é considerado um dos livros mais influentes sobre teoria orientada a objetos e desenvolvimento de software.

Basicamente o Design Patterns é como um modelo para o seu projeto. Ele usa certas convenções e você pode esperar um tipo específico de comportamento dele. Esses padrões foram compostos de experiências de muitos desenvolvedores, portanto, eles são realmente conjuntos diferentes de práticas recomendadas.

E vou mostrar 4 deles onde nós podemos utilizar no front end, que são:

  • Singleton
  • Strategy
  • Observer
  • Decorator

O padrão de projeto Singleton

O padrão singleton permite apenas que uma classe ou objeto tenha uma única instância e usa uma variável global para armazenar essa instância. Você pode usar o carregamento lento para garantir que haja apenas uma instância da classe porque ele só criará a classe quando você precisar dela.

Isso evita que várias instâncias fiquem ativas ao mesmo tempo, o que pode causar bugs estranhos. Na maioria das vezes, isso é implementado no construtor. O objetivo do padrão singleton é normalmente regular o estado global de um aplicativo.

Um exemplo de um singleton que você provavelmente usa o tempo todo é o seu logger.

Se você trabalha com algumas das estruturas de front-end como React, Angular ou Vue, sabe tudo sobre como pode ser complicado lidar com logs provenientes de vários componentes. Este é um ótimo exemplo de singletons em ação porque você nunca deseja mais de uma instância de um objeto logger, especialmente se estiver usando algum tipo de ferramenta de rastreamento de erros.

class PedirLanche {
  constructor() {
    this.comida = []
  }

  log(order) {
    this.comida.push(order.lanche)
    // faça um código para enviar este log para algum lugar
  }
}

// isso é singleton
class PessoaSingleton {
  constructor() {
    if (!PessoaSingleton.instanciaFome) {
      PessoaSingleton.instanciaFome = new PedirLanche()
    }
  }

  getPedirLancheInstanciaFome() {
    return PessoaSingleton.instanciaFome
  }
}

module.exports = PessoaSingleton
Enter fullscreen mode Exit fullscreen mode

Um exemplo da classe singleton

Agora você não precisa se preocupar em perder logs de várias instâncias porque só tem uma em seu projeto. Portanto, quando você deseja registrar a comida que foi solicitada, pode usar a mesma instância do FoodLogger em vários arquivos ou componentes.

const PedirLanche = require('./PedirLanche')

const pedirLanche = new PedirLanche().getPedirLancheInstanciaFome()

class Customer {
  constructor(order) {
    this.preco = order.preco
    this.comida = order.item
    pedirLanche.log(order)
  }

  // outras coisas acontecendo para o cliente
}

module.exports = Customer
Enter fullscreen mode Exit fullscreen mode

Um exemplo de uma classe Customer usando o singleton

const PedirLanche = require('./PedirLanche')

const pedirLanche = new PedirLanche().getPedirLancheInstanciaFome()

class Restaurante {
  constructor(inventory) {
    this.quantidade = inventory.count
    this.comida = inventory.item
    pedirLanche.log(inventory)
  }

  // outras coisas acontecendo para o restaurante
}

module.exports = Restaurante
Enter fullscreen mode Exit fullscreen mode

Um exemplo da classe Restaurant usando o mesmo singleton da classe Customer

O padrão de projeto Strategy

O padrão da estratégia é como uma versão avançada de uma instrução if else. É basicamente onde você faz uma interface para um método que você tem em sua classe base. Essa interface é usada para encontrar a implementação correta desse método que deve ser usado em uma classe derivada. A implementação, neste caso, será decidida em tempo de execução com base no cliente.

Esse padrão é incrivelmente útil em situações em que você tem métodos obrigatórios e opcionais para uma classe. Algumas instâncias dessa classe não precisarão dos métodos opcionais e isso causa um problema para soluções de herança. Você poderia usar interfaces para os métodos opcionais, mas teria que escrever a implementação toda vez que usasse essa classe, pois não haveria implementação padrão.

É aí que o padrão de estratégia nos salva. Em vez de o cliente procurar uma implementação, ele delega para uma interface de estratégia e a estratégia encontra a implementação certa. Um uso comum para isso é com sistemas de processamento de pagamentos.

Você pode ter um carrinho de compras que só permite que os clientes façam check-out com seus cartões de crédito, mas perderá clientes que desejam usar outros métodos de pagamento.

class MetodoPagamentoStrategy {

    const usuarioTipoInfo = {
      pais: string
      email: string
      nome: string
      numero?: number
      endereco?: string
      numeroCartao?: number
      cidade?: string
      ddd?: number
      estado?: string
    }

    static ContaBanco(usuarioInfo: usuarioTipoInfo) {
      const { nome, numero, ddd } = usuarioInfo
      // apos receber pagamento
    }

    static BitCoin(usuarioInfo: usuarioTipoInfo) {
      const { email, numero } = usuarioInfo
      // apos receber pagamento
    }

    static CartaoCredito(usuarioInfo: usuarioTipoInfo) {
      const { nome, numeroCartao, email } = usuarioInfo
      // apos receber pagamento
    }

    static MailIn(usuarioInfo: usuarioTipoInfo) {
      const { nome, endereco, cidade, estado, pais } = usuarioInfo
      // apos receber pagamento
    }

    static PayPal(usuarioInfo: usuarioTipoInfo) {
      const { email } = usuarioInfo
      // apos receber pagamento
    }
  }
Enter fullscreen mode Exit fullscreen mode

Um exemplo de implementação do padrão de estratégia

 const MetodoPagamentoStrategy = require('./MetodoPagamentoStrategy')
  const config = require('./config')

  class Checkout {
    constructor(strategy='CartaoCredito') {
      this.strategy = MetodoPagamentoStrategy[strategy]
    }
    // fazer a entrada do usuário e o método de pagamento

    mudaStrategy(novaStrategy) {
      this.strategy = MetodoPagamentoStrategy[novaStrategy]
    }

    const formulario = {
      nome: 'Stefany',
      numeroCartao: 8974132485522,
      email: 'tes@gmailer.com',
      pais: 'BR'
    }

    const selecionaStrategy = 'Bitcoin'

    mudaStrategy(selecionaStrategy)

    postPayment(formulario) {
      this.strategy(formulario)
    }
  }

  module.exports = new Checkout(config.metodoDePagamento.strategy)
Enter fullscreen mode Exit fullscreen mode

Essa classe Checkout é onde o padrão de estratégia pode ser exibido. Importamos alguns arquivos para termos as estratégias de método de pagamento disponíveis e a estratégia padrão da configuração.

O padrão de design de estratégia é poderoso quando você está lidando com métodos que possuem várias implementações. Pode parecer que você está usando uma interface, mas não precisa escrever uma implementação para o método toda vez que chamá-lo em uma classe diferente. Dando mais flexibilidade do que interfaces.

O padrão de projeto Observer

Se você já usou o padrão MVC, já usou o padrão de projeto observador. A parte Model é como um assunto e a parte View é como um observador desse assunto. Seu assunto detém todos os dados e o estado desses dados. Então você tem observadores, como componentes diferentes, que obterão esses dados do assunto quando os dados forem atualizados.

O objetivo do padrão de projeto do observador é criar esse relacionamento um-para-muitos entre o sujeito e todos os observadores que aguardam dados para que possam ser atualizados. Portanto, sempre que o estado do assunto mudar, todos os observadores serão notificados e atualizados instantaneamente.

Alguns exemplos de quando você usaria esse padrão incluem: envio de notificações de usuário, atualização, filtros e tratamento de assinantes.

Digamos que você tenha uma pagina de compras com um menu e nela existe um dropdown, e neste dropdown existem níveis para acessar cada categoria. Basicamente você tem vários filtros na página que dependem do valor de um filtro de nível superior.

class MenuCategoria {
    constructor() {
      this.categories = ['eletrodomesticos', 'portas', 'ferramentas']
      this.subscriber = []
    }

    subscribe(observer) {
      this.subscriber.push(observer)
    }

    onChange(categoriaSelecionada) {
      this.subscriber.forEach(observer => observer.update(categoriaSelecionada))
    }
  }
Enter fullscreen mode Exit fullscreen mode
const MenuCategoria  = require('./MenuCategoria ')

const menuCategoria  = new MenuCategoria ()

menuCategoria.subscribe(coresDropdown)
menuCategoria.subscribe(precosDropdown)
menuCategoria.subscribe(marcasDropdown)
Enter fullscreen mode Exit fullscreen mode

Um exemplo do padrão observador em ação

O que este arquivo nos mostra é que temos 3 menus suspensos que são assinantes do observável suspenso de categoria. Em seguida, inscrevemos cada um desses menus suspensos no observador. Sempre que a categoria do observador for atualizada, ele enviará o valor para cada assinante que atualizará as listas suspensas individuais instantaneamente.

O padrão de projeto Decorator

Usar o padrão de projeto decorator é bastante simples. Você pode ter uma classe base com métodos e propriedades que estão presentes quando você cria um novo objeto com a classe. Agora digamos que você tenha algumas instâncias da classe que precisam de métodos ou propriedades que não vieram da classe base.

Você pode adicionar esses métodos e propriedades extras à classe base, mas isso pode atrapalhar suas outras instâncias. Você pode até criar subclasses para manter métodos e propriedades específicos de que precisa e que não pode colocar em sua classe base.

Qualquer uma dessas abordagens resolverá seu problema, mas são desajeitadas e ineficientes. É aí que entra o padrão decorator. Em vez de tornar sua base de código feia apenas para adicionar algumas coisas a uma instância de objeto, você pode adicionar essas coisas específicas diretamente à instância.

Portanto, se você precisar adicionar uma nova propriedade que contenha o preço de um objeto, poderá usar o padrão decorador para adicioná-lo diretamente a essa instância de objeto específica e isso não afetará nenhuma outra instância desse objeto de classe.

Você já pediu comida online? Então você provavelmente já encontrou o padrão decorator. Se você está comprando um sanduíche e deseja adicionar coberturas especiais, o site não está adicionando essas coberturas a todas as instâncias de sanduíche que os usuários atuais estão tentando pedir.

class Cliente {
    constructor(saldo=20) {
      this.saldo = saldo
      this.comidaItems = []
    }

    comprar(comida) {
      if (comida.preco) < this.saldo {
        this.saldo -= comida.preco
        this.comidaItems.push(comida)
      }  else {
        console.log('Talvez você deva pegar outra coisa')
      }
    }
  }

  module.exports = Cliente
Enter fullscreen mode Exit fullscreen mode

Exemplo de classe de cliente

const Cliente = require('./Cliente')

const cliente = new Cliente(57)

cliente.comprar(sanduiche)
cliente.comprar(salada)
Enter fullscreen mode Exit fullscreen mode

Referências Design Patterns English

Top comments (0)