4 Design Patterns para usar no frontend
Tags: webdev, frontend, programming, javascript
Published: 2022-12-12 02:51:43

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

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

```
const MenuCategoria  = require('./MenuCategoria ')

const menuCategoria  = new MenuCategoria ()

menuCategoria.subscribe(coresDropdown)
menuCategoria.subscribe(precosDropdown)
menuCategoria.subscribe(marcasDropdown)
```
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
```
Exemplo de classe de cliente

```
const Cliente = require('./Cliente')

const cliente = new Cliente(57)

cliente.comprar(sanduiche)
cliente.comprar(salada)
```


Referências [Design Patterns English](https://www.freecodecamp.org/news/4-design-patterns-to-use-in-web-development/)