Executando verificação de segurança...
3

Javascript Patterns - Singleton

Singletons são classes que podem ser instanciadas uma vez e podem ser acessadas globalmente. Essa única instância pode ser compartilhada em todo o nosso aplicativo, o que torna os Singletons ótimos para gerenciar o estado global em um aplicativo.

Primeiro, vamos dar uma olhada em como um singleton pode parecer usando uma classe ES2015. Para este exemplo, vamos construir uma classe Counter que tenha:

  • um método getInstance que retorna o valor da instância
  • um método getCount que retorna o valor atual da variável counter
  • um método de increment que incrementa o valor do counter em um
  • um método de decrement que decrementa o valor do counter em um
let counter = 0;

class Counter {
  getInstance() {
    return this;
  }

  getCount() {
    return counter;
  }

  increment() {
    return ++counter;
  }

  decrement() {
    return --counter;
  }
}

No entanto, esta classe não atende aos critérios para um Singleton! Um Singleton só pode ser instanciado uma vez . Atualmente, podemos criar várias instâncias da classe Counter.

let counter = 0;

class Counter {
  getInstance() {
    return this;
  }

  getCount() {
    return counter;
  }

  increment() {
    return ++counter;
  }

  decrement() {
    return --counter;
  }
}

const counter1 = new Counter();
const counter2 = new Counter();

console.log(counter1.getInstance() === counter2.getInstance()); // false

Ao chamar o método new duas vezes, apenas definimos counter1 e counter2 iguais para instâncias diferentes. Os valores retornados pelo método getInstance em counter1 e counter2 efetivamente retornaram referências a diferentes instâncias: eles não são estritamente iguais!

Vamos garantir que apenas uma instância da classe Counter possa ser criada.

Uma maneira de garantir que apenas uma instância possa ser criada é criar uma variável chamada instance. No construtor de Counter, podemos definir instance igual a uma referência à instância quando uma nova instância é criada. Podemos evitar novas instanciações verificando se a variável de instância já possui um valor. Se for esse o caso, já existe uma instância. Isso não deveria acontecer: um erro deveria ser gerado para que o usuário soubesse

let instance;
let counter = 0;

class Counter {
  constructor() {
    if (instance) {
      throw new Error("Você só pode criar uma instância!");
    }
    instance = this;
  }

  getInstance() {
    return this;
  }

  getCount() {
    return counter;
  }

  increment() {
    return ++counter;
  }

  decrement() {
    return --counter;
  }
}

const counter1 = new Counter();
const counter2 = new Counter();
// Error: Você só pode criar uma instância!

Perfeito! Não podemos mais criar várias instâncias.

Vamos exportar a instância Counter do arquivo counter.js. Mas antes de fazer isso, devemos congelar a instância também. O método Object.freeze garante que o código de consumo não possa modificar o Singleton. As propriedades na instância congelada não podem ser adicionadas ou modificadas, o que reduz o risco de sobrescrever acidentalmente os valores no Singleton.

let instance;
let counter = 0;

class Counter {
  constructor() {
    if (instance) {
      throw new Error("Você só pode criar uma instância!");
    }
    instance = this;
  }

  getInstance() {
    return this;
  }

  getCount() {
    return counter;
  }

  increment() {
    return ++counter;
  }

  decrement() {
    return --counter;
  }
}

const singletonCounter = Object.freeze(new Counter());
export default singletonCounter;

Vamos dar uma olhada em um aplicativo que implementa o exemplo Counter. Temos os seguintes arquivos:

  • counter.js: contém a classe Counter e exporta uma instância Counter como sua exportação padrão
  • index.js: carrega os módulos redButton.js e blueButton.js
  • redButton.js: importa Counter e adiciona o método de incremento de Counter como um ouvinte de evento ao botão vermelho e registra o valor atual de counter invocando o método getCount
  • blueButton.js: importa Counter e adiciona o método de incremento de Counter como um ouvinte de evento ao botão azul e registra o valor atual de counter invocando o método getCount

Ambos blueButton.js e redButton.js importam a mesma instância de counter.js. Esta instância é importada como Counter em ambos os arquivos.

Quando invocamos o método de increment em redButton.js ou blueButton.js, o valor da propriedade counter na instância Counter é atualizado em ambos os arquivos. Não importa se clicamos no botão vermelho ou azul: o mesmo valor é compartilhado entre todas as instâncias. É por isso que o contador continua incrementando em 1, mesmo que estejamos invocando o método em arquivos diferentes.

(Des)vantagens

Restringir a instanciação a apenas uma instância pode economizar muito espaço de memória. Em vez de configurar a memória para uma nova instância a cada vez, só precisamos configurar a memória para aquela instância, que é referenciada em todo o aplicativo. No entanto, Singletons são realmente considerados um antipadrão e podem (ou devem) ser evitados em JavaScript.

Em muitas linguagens de programação, como Java ou C++, não é possível criar objetos diretamente como fazemos em JavaScript. Nessas linguagens de programação orientadas a objetos, precisamos criar uma classe, que cria um objeto. Esse objeto criado tem o valor da instância da classe, assim como o valor da instância no exemplo do JavaScript.

No entanto, a implementação de classe mostrada nos exemplos acima é, na verdade, um exagero. Como podemos criar objetos diretamente em JavaScript, podemos simplesmente usar um objeto regular para obter exatamente o mesmo resultado. Vamos cobrir algumas das desvantagens de usar Singletons!

Gerenciamento de estado em React

No React, geralmente contamos com um estado global por meio de ferramentas de gerenciamento de estado, como Redux ou React Context, em vez de usar Singletons. Embora seu comportamento de estado global possa parecer semelhante ao de um Singleton, essas ferramentas fornecem um estado somente leitura em vez do estado mutável do Singleton. Ao usar o Redux, apenas os reducerspodem atualizar o estado, depois que um componente enviou uma ação por meio de um dispatch.

Embora as desvantagens de ter um estado global não desapareçam magicamente com o uso dessas ferramentas, podemos pelo menos garantir que o estado global seja alterado da maneira pretendida, pois os componentes não podem atualizar o estado diretamente.

Carregando publicação patrocinada...
2

muito boa sua explicação,consegui compreender e ate mesmo aplicar em um exemplo aqui,achei mais facil de compreender do que o padrão 'Observer',que ate hoje algumas vezes ainda revisito conteudo(no caso os videos do dechamps rsrs) para relembrar,teria algum material assim sucinto sobre 'Observer' ? gosto de comparar varios materiais sobre o mesmo assunto de pessoas diferentes pra ter pontos de vistas e ate mesmo metodos de ensinos diferentes.

1

O padrão Observer é um pouco mais complicado de se entender de ínicio mesmo, pense nos sistemas de reatividades de lib/frameworks front-end hoje em dia, a base dessas libs são o padrão observer, claro que muito mais robusto e com vários mecanismos de performance implementados, hoje em dia, você não vai precisar implementar esse padrão por você mesmo, existem várias libs que já extraíram essa complexidade para você, mas lembrar que, é muito importante saber o conceito por traz dessas features, isso vai te dar uma maior capacidade para tomar decisões.

Tudo o que você precisa saber sobre observer está aqui: https://www.patterns.dev/posts/observer-pattern/ (Inclusive foi de onde tirei as informações para essa postagem)