Singletons . Eles são seu pior pesadelo-ou pelo menos é o que você foi levado a acreditar.

Eles são realmente tão ruins? Por que eles são considerados maus? E eles sempre estiveram do lado errado da opinião pública?

Singletons são chamados de padrão de design. Eles também são chamados de anti-padrão. Então qual é? Certamente não podem ser os dois.

Esta é a minha confissão de culpa: ainda uso singletons. Mas descobri uma maneira de mitigar as desvantagens, então, felizmente, uso singletons para seus benefícios, sem sofrer seus problemas.

Nesta postagem do blog, examinamos de onde vieram os singletons, de onde tudo deu errado e o que você pode fazer agora para usar os singletons para os benefícios originalmente pretendidos-sem culpa ou medo. Continue lendo para saber mais.

Histórico

Dada a quantidade de medo e ódio que cerca os solteiros, pode ficar surpreso em saber que eles não nasceram maus. Na verdade, os solteiros estavam em uso comum por pelo menos 10 anos antes que as noções de serem malignos se infiltrassem na blogosfera. Vamos dar uma olhada rápida na história.

Singletons foram apresentados ao mundo em 1995 por meio do agora clássico livro de desenvolvimento de software Design Patterns pelo alvo “ Gang of Four ”(mostrado na Figura 1), embora certamente o conceito de singleton (se não o nome real singleton ) existisse por muitos anos antes de este livro ser publicado.

Padrões de design por Gang of Four
Figura 1 : Minha cópia pessoal do livro Design Patterns da Gang of Four. Este livro apresentou ao mundo o padrão de design singleton em 1995.

Enquanto pesquisava para este post, eu queria saber exatamente quando o amor por solteiros parou. Pelo menos em 2007, o ódio floresceu. Aqui está o mais antigo ( ainda online) postagem do blog que consegui encontrar. Também encontrei este na Wayback Machine de 2008. Em seguida, avanço rápido para 2020 para este .

Assim, os solteiros desfrutaram de mais de 10 anos de uso antes que a raiva contra eles viesse à tona. É uma boa corrida, mas agora já se passaram mais de 10 anos desde que foram considerados um antipadrão. Isso me fez perguntar por que ainda estamos falando sobre isso depois de tanto tempo. As pessoas não deveriam ter parado de usar singletons já?

Então eu percebi, eu mesmo nunca tinha realmente parado de usar singletons. Na verdade, ainda os uso com frequência. Eu sabia como eles eram ruins; os anos de discussões não escaparam da minha atenção. No entanto, de alguma forma, descobri uma maneira de fazer os singletons funcionarem.

Meu código hoje está mais limpo e elegante do que nunca. Eu uso singletons, mas também faço testes automatizados significativos e estou constantemente refatorando meu código com facilidade e segurança. Como é possível que o singleton difamado não destruiu meu processo de desenvolvimento?

O desenvolvimento para mim é um processo constante de melhoria e evolução. Sim, os solteiros causam-me problemas de vez em quando, mas será que os joguei fora? Não, porque singletons ainda são úteis. Na verdade, é por isso que as pessoas ainda os usam; se as pessoas não os estivessem usando, ainda não estaríamos discutindo sobre eles.

Em vez de jogar fora o padrão de design singleton, eu o desenvolvi . Aprendi os problemas com ele (em primeira mão) e ajustei a forma como o usei. Eu descobri uma maneira de usar singletons sem sofrer as desvantagens (agora) bem conhecidas. Em um momento, vou mostrar como.

O que é um singleton?

Vamos começar com uma visão geral simples do padrão singleton, apenas para mantê-lo atualizado.

Às vezes, ao codificar, precisamos do conceito de um objeto global. Este é um objeto que possui apenas uma instância em nosso aplicativo. Design Patterns usa os seguintes exemplos: spooler de impressora, sistema de arquivos e gerenciador de janelas. Pode e deve haver apenas uma instância para esses tipos de objetos.

O padrão de design singleton torna a classe responsável por sua própria criação e controla o acesso à instância para que sua natureza de instância única não possa ser subvertida. Portanto, podemos garantir que esse objeto nunca seja criado mais de uma vez.

O singleton é um dos vários padrões de criação abordados em Padrões de design . É apenas um dos vários métodos para criar objetos.

Basic Singleton
Figura 2 : Ilustração do padrão de design tradicional singleton.

Exemplos modernos de singletons

Para entender o que é um singleton e como ele é útil, vamos considerar alguns exemplos mais modernos de objetos que podem ser representados bem como singletons.

Provedor de diálogo

Um bom exemplo é o provedor de diálogo. Um aplicativo baseado em IU pode exibir diálogos para coletar informações do usuário. Faz sentido termos apenas uma instância de nosso provedor de diálogo, para que possamos controlar como ele é usado. Por exemplo, provavelmente queremos impor apenas um diálogo na tela por vez.

A Figura 3 ilustra como podemos usar um provedor de diálogo como um singleton para conectá-lo de forma fácil e rápida a objetos profundamente aninhados em sua hierarquia de IU.

O código abaixo nas Listagens 1 e 2 é um exemplo de como esse provedor de diálogo pode ser codificado em JavaScript.

Provedor de diálogo como Singleton
Figura 3 : Conectando diretamente objetos profundamente aninhados em nossa hierarquia de IU a um provedor de diálogo singleton.

Repositório de entidades

Aqui está outro exemplo que pode agradar a você. Quase todo aplicativo precisa de alguma forma de armazenamento de dados, e isso geralmente é implementado usando o padrão de repositório. Pode ser muito tentador armazenar nossos objetos respiratórios como singletons para que sejam facilmente acessíveis de qualquer lugar em nosso código.

No entanto, isso não é apenas por conveniência: ter uma instância singleton de nosso repositório de entidades significa que temos um lugar para implementar o cache para nossas entidades e otimizar para que os carregamentos de dados subsequentes não precisem ir para o sistema de arquivos ou banco de dados.

Listagem 1: Um exemplo de implementação de singleton para nosso provedor de diálogo no TypeScript
 exportar classe DialogProvider { // //Obtém a instância singleton. //Cria o singleton vagarosamente quando chamado pela primeira vez. // public static getInstance (): DialogProvider { if (! this.instance) { this.instance=new DialogProvider (); } return this.instance; } // //Instância do singleton, após sua criação. // instância estática privada ?: DialogProvider; // //Apresenta a caixa de diálogo ao usuário. // public async showDialog (question: string): Promise  { //... codifique aqui para exibir a caixa de diálogo.... } // //... outras funções vão aqui... //
}
Listagem 2: Exemplo de uso do singleton
 string question=...
string answer=await DialogProvider.getInstance (). showDialog (question);
//... faça algo com a resposta recebida do usuário...

Dependências de fiação

Tradicionalmente, ao conectar dependências por meio de nossa base de código, tínhamos duas opções:

  1. Conecte as dependências em toda a nossa estrutura de código potencialmente profundamente aninhada (consulte a Figura 4 para obter uma ilustração)
  2. Acesse diretamente a dependência como um objeto global
Passing Through Dialog Provider
Figura 4 : Ao não usar um singleton para o provedor de diálogo, devemos conectar essa dependência em toda a nossa hierarquia de IU profundamente aninhada.

A primeira opção é entediante e dolorosa, e essa instalação dificulta a reestruturação de nosso aplicativo.

A segunda opção, acessar diretamente um objeto global, é muito mais fácil, mas, novamente, torna difícil reestruturar nosso aplicativo.

Sem dúvida, a segunda opção é melhor. Ambas as alternativas levam a um código com fio difícil de modificar. Mas o segundo é mais fácil de colocar no lugar e há menos fiação para mudar depois-porque não temos que conectá-lo por todas as camadas intermediárias.

Mas os globais são ruins, certo? Bem, não tanto na época em que o singleton foi inventado.

Naquela época, os programas de computador não eram tão grandes e complicados como agora, e os testes automatizados eram raros. O padrão de design singleton introduz controle sobre o acesso enquanto ainda mantém a conveniência de ter acesso direto de qualquer lugar em nossa base de código. Até certo ponto, o padrão de design singleton legitimou o uso de objetos globais.

Os problemas começam

Com o passar dos anos, nossos programas de computador se tornaram maiores e mais complexos. As equipes que os desenvolvem ficaram maiores. Os testes automatizados se tornaram populares.

O padrão de design singleton foi usado em demasia e provavelmente muitas vezes foi mal usado. Os problemas com o singleton se manifestaram a ponto de se tornar conhecido como um antipadrão.

Um singleton por si só dificilmente é melhor do que apenas acessar um objeto global, com todos os problemas que isso implica:

  • Objetos que dependem de singletons não são facilmente isolados para teste
  • Nossa base de código é cabeada e não é fácil reestruturá-la
  • Mudar de um objeto global para um objeto não global (se decidirmos que os singletons estão errados em um caso particular) é particularmente difícil. Imagine ter que conectar tudo através de sua base de código

Singletons (na verdade, quaisquer referências globais) e efeitos colaterais são provavelmente o maior motivo pelo qual os aplicativos legados são difíceis de reestruturar e difíceis de se adaptar a testes automatizados.

Você está usando singletons da maneira errada

Vamos encarar os fatos-a codificação é difícil. Cada padrão de design, cada técnica, cada prática recomendada pode ser usada da maneira errada e pode ser usada em excesso. O padrão de design de um codificador é o antipadrão de outro.

O singleton não é exceção.

Acontece que você está usando singletons da maneira errada . Na maioria dos casos, provavelmente nem nos importamos se há uma instância singleton, principalmente queremos a conveniência de um objeto facilmente acessível quando isso faz sentido (e mais tarde, quando não fizer mais sentido, gostaríamos de uma maneira fácil para corrigir a situação).

Também gostaríamos da conveniência de não ter que nos preocupar com problemas de pedido inicial. Idealmente, queremos apenas iniciar as dependências para se resolverem e descobrirem sua própria ordem de inicialização. Isso é algo incrível que conseguimos autocriando singletons instanciados preguiçosamente.

Então, geralmente, gostaríamos da conveniência do singleton sem ter que assumir nenhuma das coisas negativas. Existe uma maneira de obter os benefícios do singleton sem as desvantagens?

Sim, certamente existe!

Correção de singletons

Singletons são extremamente convenientes. Há uma razão pela qual as pessoas ainda os usam!

Como podemos usar singletons, mas ainda ser capazes de fazer testes automatizados e ter uma arquitetura que pode ser reestruturada?

Nós podemos resgatar o singleton, e é mais fácil do que você imagina. Vamos fazer algumas mudanças:

  1. A própria classe singleton não deve ser responsável por sua própria criação
  2. Outras classes não devem ser vinculadas ao singleton

Resolver esses problemas não é tão difícil, mas o que realmente precisamos para que seja tão conveniente quanto o singleton original é que a fiação das dependências seja automática. Não queremos ter que conectar uma dependência até o fim de nossa base de código para que ela esteja em todos os lugares em que precisa ser acessada. Essa fiação manual é tediosa e o oposto da conveniência.

O que precisamos é de outro padrão de design-algo que possa automatizar a fiação de dependências em nossa base de código.

DI salva o dia

A boa notícia é que injeção de dependência (DI) , um design padrão que veio um pouco mais tarde, salva o dia para singletons. Singletons juntamente com DI nos dão a conveniência de singletons sem remorso ou culpa (veja o código de exemplo nas Listagens 3 e 4 usando o Biblioteca Fusion DI ).

A injeção automática de dependência é especificamente o que estou falando; às vezes é chamado de inversão de controle (IoC) . Ele automatiza a criação e a conexão de nossas dependências.

Podemos usar DI para conectar nossos objetos globais (também conhecidos como singletons) por meio de nossa base de código sem ter que fazer nenhuma configuração manual. Essa automação torna trivial reescrever e reestruturar as conexões entre os componentes em nosso aplicativo, mesmo quando essas conexões são para singletons.

Quando uma dependência é injetada em um objeto, esse objeto não precisa saber que está realmente conectado a um singleton! Em seguida, para testes automatizados, injetamos um objeto simulado como a dependência em vez do objeto real. Isso significa que podemos fazer testes automatizados em objetos que dependem de singletons.

A DI automatizada também descobre a ordem de inicialização do nosso aplicativo. Ele automaticamente e preguiçosamente instancia dependências e dependências de dependências e as cria da maneira certa pedido e no momento certo, antes de serem necessários.

Singletons por conta própria não precisam mais gerenciar sua própria criação. A estrutura de DI gerencia sua criação, de modo que singletons podem ser instanciados como objetos normais e, portanto, podemos instanciá-los em nossos testes automatizados e executar testes neles.

Os problemas com singletons evaporaram!

Agora, alguns argumentariam que o que estou descrevendo é simplesmente DI e não singletons.

Bem, isso é apenas semântica. Eu diria que esta é uma evolução de como criamos e consumimos objetos globais; é uma evolução de como usamos singletons.

Na minha perspectiva, nunca parei de usar singletons. Ainda os chamo de singletons na biblioteca DI que criei para TypeScript (A Listagem 3 mostra como um singleton é definido usando a biblioteca Fusion DI).

Listagem 3: Exemplo de um singleton injetável de dependência criado lentamente no TypeScript
 importar {InjectableSingleton} de"@ codecapers/fusion"; interface de exportação IDialogProvider { // //Apresenta a caixa de diálogo ao usuário. // showDialog (): Promessa 
} @InjectableSingleton ("IDialogProvider")
export class DialogProvider implementa IDialogProvider { // //Apresenta a caixa de diálogo ao usuário. // public async showDialog (): Promise  { //... codifique aqui para exibir a caixa de diálogo.... } // //... outras funções vão aqui... //
}
Listagem 4: exemplo de injeção de dependência de um singleton criado preguiçosamente em uma classe TypeScript
 importar {InjectProperty} de"@ codecapers/fusion"; export class SomeUIComponent { @InjectProperty ("IDialogProvider") dialogProvider !: IDialogProvider; //... outro código aqui... public async onButtonClicked (): Promise  { aguarde this.dialogProvider.showDialog (); }
}

Para saber mais sobre a estrutura Fusion DI, você pode leia minha postagem anterior no blog .

Conclusão

Singletons são considerados um padrão de design e um antipadrão, mas você precisa se lembrar que o padrão de design de uma pessoa é o antipadrão de outra.

Todos os padrões de design podem ser aplicados às situações erradas (em que se tornam um antipadrão) e todos os padrões de design que são usados ​​incorretamente ou em excesso e podem causar danos. Eu quero que você saia desta postagem do blog com a compreensão de que nem tudo é preto e branco. Existem muitos tons de cinza.

Indiscutivelmente, o singleton é o padrão de design mais usado e mal aplicado, e é por isso que sofreu a reação negativa que recebeu. Mas não acredite apenas no que você ouve; você precisa ser capaz de pensar sobre essas coisas por si mesmo. Pense criticamente e experimente antes de formar uma opinião sobre ele.

Há uma razão pela qual as pessoas ainda reclamam dos solteiros! É porque eles ainda estão sendo usados, mesmo depois de 10 anos sendo considerados maus!

Por que os singletons ainda estão sendo usados? É porque alguns desenvolvedores não entenderam que singletons são ruins? Não, é porque singletons são realmente convenientes e úteis, apesar das várias desvantagens potenciais. Se os desenvolvedores não estivessem usando singletons, simplesmente não ouviríamos mais sobre eles.

Se você for usar singletons, certifique-se de também usar injeção de dependência. DI salva o dia para solteiros. Usar DI significa que podemos ter objetos singleton globais e podemos nos beneficiar de fiação de dependência automatizada e a capacidade de isolar usando simulação para permitir testes automatizados.

Podemos usar o padrão de design singleton para seus benefícios originalmente pretendidos, sem nos expor aos riscos normalmente associados aos singletons.

Portanto, pare de se preocupar e use apenas singletons. *

* Certifique-se de usar DI também.

A postagem Você está errado sobre os singletons apareceu primeiro em LogRocket Blog .

Source link