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
getInstanceque retorna o valor da instância - um método
getCountque retorna o valor atual da variávelcounter - um método de
incrementque incrementa o valor docounterem um - um método de
decrementque decrementa o valor docounterem 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 classeCountere exporta uma instânciaCountercomo sua exportação padrãoindex.js: carrega os módulosredButton.jseblueButton.jsredButton.js: importaCountere adiciona o método de incremento de Counter como um ouvinte de evento ao botão vermelho e registra o valor atual decounterinvocando o métodogetCountblueButton.js: importaCountere adiciona o método de incremento de Counter como um ouvinte de evento ao botão azul e registra o valor atual decounterinvocando o métodogetCount
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.