Sobre padrões de design: quando devo usar o singleton?

448

A variável global glorificada - se torna uma classe global glorificada. Alguns dizem que quebram o design orientado a objetos.

Dê-me cenários, além do bom e antigo logger, onde faz sentido usar o singleton.

Setori
fonte
4
Desde que aprendi erlang, prefiro essa abordagem, a saber, imutabilidade e transmissão de mensagens.
Setori 26/11/09
209
O que não é construtivo nessa questão? Eu vejo respostas construtivas abaixo.
Mk12
3
Uma estrutura de injeção de dependência é um singleton muito complexo que fornece objeto….
Ian Ringrose
1
O Singleton pode ser usado como um objeto gerenciador entre as instâncias de outros objetos; portanto, deve haver apenas uma instância do singleton em que as outras instâncias devem se comunicar por meio da instância do singleton.
Levent Divilioglu
Eu tenho uma pergunta paralela: qualquer implementação Singleton também pode ser implementada usando uma classe "estática" (com um método "factory" / "init") - sem realmente criar uma instância de uma classe (você poderia dizer que uma classe estática é uma tipo de implementação Singleton, mas ...) - por que alguém deve usar um Singleton real (uma instância de classe única que garante que seja única) em vez de uma classe estática? A única razão pela qual consigo pensar é talvez a "semântica", mas mesmo nesse sentido, os casos de uso Singleton realmente não exigem um relacionamento de "instância-> classe" por definição ... então ... por quê?
Yuval A.

Respostas:

358

Na minha busca pela verdade, descobri que existem realmente muito poucas razões "aceitáveis" para usar um Singleton.

Um motivo que tende a aparecer repetidamente nas internets é o de uma classe de "registro" (que você mencionou). Nesse caso, um Singleton pode ser usado em vez de uma única instância de uma classe, porque uma classe de log geralmente precisa ser usada repetidamente e repetidamente por todas as classes de um projeto. Se toda classe usa essa classe de log, a injeção de dependência se torna complicada.

O log é um exemplo específico de um Singleton "aceitável" porque não afeta a execução do seu código. Desabilitar o log, a execução do código permanece a mesma. Habilite o mesmo. Misko coloca da seguinte maneira em Causa raiz dos singletons : "As informações aqui fluem de uma maneira: do seu aplicativo para o criador de logs. Mesmo que os registradores sejam um estado global, já que nenhuma informação flui dos criadores de logs para o seu aplicativo, os registradores são aceitáveis".

Tenho certeza de que existem outros motivos válidos também. Alex Miller, em " Patterns I Hate ", fala sobre localizadores de serviços e interfaces de usuário do lado do cliente, sendo possivelmente opções "aceitáveis".

Leia mais em Singleton, eu te amo, mas você está me derrubando.

CodingWithoutComments
fonte
3
@ArneMertz Acho que esse é o único.
Attacktive
1
Por que você não pode simplesmente usar um objeto global? Por que tem que ser um singleton?
Shoe
1
Eu acho que o método estático para um utilitário de log?
Skynet
1
Singletons são melhores quando você precisa gerenciar recursos. Por exemplo, conexões HTTP. Você não deseja estabelecer 1 milhão de clientes http para um único cliente, isso é um desperdício louco e lento. Portanto, um singleton com um cliente http com pool de conexão será muito mais rápido e utilizará recursos.
Cogman
3
Sei que essa é uma pergunta antiga e as informações nesta resposta são ótimas. No entanto, estou tendo problemas para entender por que essa é a resposta aceita quando o OP especificou claramente: "Dê-me cenários, além do bom e antigo logger, onde faz sentido usar o singleton".
Francisco C.
124

Um candidato Singleton deve atender a três requisitos:

  • controla o acesso simultâneo a um recurso compartilhado.
  • o acesso ao recurso será solicitado em várias partes diferentes do sistema.
  • só pode haver um objeto.

Se o seu Singleton proposto tiver apenas um ou dois desses requisitos, um novo design é quase sempre a opção correta.

Por exemplo, é improvável que um spooler de impressora seja chamado de mais de um local (o menu Imprimir), para que você possa usar mutexes para resolver o problema de acesso simultâneo.

Um logger simples é o exemplo mais óbvio de um Singleton possivelmente válido, mas isso pode mudar com esquemas de log mais complexos.

metao
fonte
3
Eu discordo do ponto 2. O ponto 3 não é realmente um motivo (apenas porque você pode não significa que deveria) e 1 é um bom ponto, mas ainda não vejo utilidade para ele. Digamos que o recurso compartilhado seja uma unidade de disco ou um cache db. Você pode adicionar outra unidade ou ter um cache db focado em outra coisa (como um cache para uma tabela especializada para um encadeamento, sendo o outro de uso mais geral).
15
Eu acho que você perdeu a palavra "candidato". Um candidato Singleton deve atender aos três requisitos; só porque algo atende aos requisitos, não significa que deva ser um Singleton. Pode haver outros fatores de design :)
metao
45

Lendo arquivos de configuração que devem ser lidos apenas no momento da inicialização e encapsulando-os em um Singleton.

Paul Croarkin
fonte
8
Semelhante ao Properties.Settings.Default.NET.
Nick Bedford
9
@Paul, O "campo no-singleton" indicará que o objeto de configuração deve simplesmente ser passado para as funções que precisam dele, em vez de torná-lo acessível globalmente (também conhecido como singleton).
Pacerier
2
Discordo. Se a configuração for movida para o banco de dados, tudo está danificado. Caso o caminho para a configuração dependa de algo fora desse singleton, essas coisas também precisam ser estáticas.
precisa saber é
3
@PaulCroarkin Você pode expandir isso e explicar como isso é benéfico?
AlexG
1
@ rr- se a configuração for movida para o banco de dados, ainda poderá ser encapsulada em um objeto de configuração que será passado para as funções que precisam dele. (PS: Eu não estou no campo "no-singleton").
Will Sheppard
36

Você usa um singleton quando precisa gerenciar um recurso compartilhado. Por exemplo, um spooler de impressora. Seu aplicativo deve ter apenas uma instância do spooler para evitar solicitações conflitantes para o mesmo recurso.

Ou uma conexão com o banco de dados ou um gerenciador de arquivos etc.

Vincent Ramdhanie
fonte
30
Ouvi esse exemplo de spooler de impressora e acho que é meio coxo. Quem disse que não posso ter mais de um spooler? Mas que diabos é um spooler de impressora? E se eu tiver diferentes tipos de impressoras que não possam entrar em conflito ou usar drivers diferentes?
1800 INFORMAÇÃO
6
É apenas um exemplo ... para qualquer situação que alguém use como exemplo, você poderá encontrar um design alternativo que torne o exemplo inútil. Vamos fingir que o spooler gerencia um único recurso que é compartilhado por vários componentes. Funciona.
Vincent Ramdhanie
2
É o exemplo clássico da quadrilha. Eu acho que uma resposta com um caso de uso real experimentado seria mais útil. Quero dizer, uma situação em que você realmente sentiu que o Singleton é a melhor solução.
Andrei Vajna II 16/09/09
Um recurso compartilhado é, na minha opinião, um exemplo muito amplo. Como você testaria se os objetos que usam o spooler de impressão estão funcionando corretamente diante de um spooler com defeito quando você não pode injetar uma implementação com defeito de 'spooler'? embora curto e informativo a resposta aceita é uma abordagem muito mais seguro em meu livro
Rune FS
Que diabos é um spooler de impressora?
RayLoveless
23

Os singletons somente leitura que armazenam algum estado global (idioma do usuário, caminho do arquivo de ajuda, caminho do aplicativo) são razoáveis. Tenha cuidado ao usar singletons para controlar a lógica de negócios - o single quase sempre acaba sendo múltiplo

Martin Beckett
fonte
4
O idioma do usuário pode ser único, com a suposição de que apenas um usuário pode usar o sistema.
Samuel Åslund
… E esse usuário fala apenas um idioma.
espectros
17

Gerenciando uma conexão (ou um conjunto de conexões) com um banco de dados.

Eu o usaria também para recuperar e armazenar informações em arquivos de configuração externos.

Federico A. Ramponi
fonte
2
Um gerador de conexão com o banco de dados não seria um exemplo de fábrica?
Ken
3
@ Ken você gostaria que a fábrica fosse um singleton em quase todos os casos.
Chris Marisic
2
@Federico, O "campo no-singleton" indicará que essas conexões com o banco de dados devem ser simplesmente passadas para as funções que precisam delas, em vez de torná-las acessíveis globalmente (aka singleton).
Pacerier
3
Você realmente não precisa de um singleton para isso. Pode ser injetado.
Nestor Ledon
11

Uma das maneiras de usar um singleton é cobrir uma instância em que deve haver um único "intermediário" controlando o acesso a um recurso. Singletons são bons em madeireiros, porque intermediam o acesso a, digamos, um arquivo, no qual só pode ser gravado exclusivamente. Para algo como log, eles fornecem uma maneira de abstrair as gravações para algo como um arquivo de log - você pode agrupar um mecanismo de cache no seu singleton, etc ...

Pense também em uma situação em que você tem um aplicativo com muitas janelas / threads / etc, mas que precisa de um único ponto de comunicação. Uma vez eu usei um para controlar trabalhos que eu queria que meu aplicativo iniciasse. O singleton era responsável por serializar os trabalhos e exibir seu status para qualquer outra parte do programa que estivesse interessada. Nesse tipo de cenário, você pode ver um singleton como uma classe de "servidor" em execução no aplicativo ... HTH

Dave Markle
fonte
3
Os registradores são geralmente Singletons, para que os objetos de registro não precisem ser passados. Qualquer implementação decente de um fluxo de logs garantirá que gravações simultâneas sejam impossíveis, seja um Singleton ou não.
metao 23/10/08
10

Um singleton deve ser usado ao gerenciar o acesso a um recurso compartilhado por todo o aplicativo, e seria destrutivo potencialmente ter várias instâncias da mesma classe. Garantir que o acesso a recursos compartilhados seja seguro é um exemplo muito bom de onde esse tipo de padrão pode ser vital.

Ao usar Singletons, verifique se não oculta acidentalmente dependências. Idealmente, os singletons (como a maioria das variáveis ​​estáticas em um aplicativo) são configurados durante a execução do código de inicialização do aplicativo (static void Main () para executáveis ​​em C #, static void main () para executáveis ​​em java) e depois transmitidos para todas as outras classes instanciadas que requerem. Isso ajuda a manter a testabilidade.

Adam Ness
fonte
8

Acho que o uso de singleton pode ser considerado o mesmo que o relacionamento muitos-para-um nos bancos de dados. Se você tem muitas partes diferentes do seu código que precisam trabalhar com uma única instância de um objeto, é aí que faz sentido usar singletons.

daalbert
fonte
6

Um exemplo prático de um singleton pode ser encontrado em Test :: Builder , a classe que suporta quase todos os módulos de teste Perl modernos. O singleton Test :: Builder armazena e intermedia o estado e o histórico do processo de teste (resultados históricos do teste, conta o número de testes executados), bem como coisas como para onde está indo a saída do teste. Tudo isso é necessário para coordenar vários módulos de teste, escritos por autores diferentes, para trabalharem juntos em um único script de teste.

A história do singleton do Test :: Builder é educacional. Ligar new()sempre oferece o mesmo objeto. Primeiro, todos os dados foram armazenados como variáveis ​​de classe sem nada no próprio objeto. Isso funcionou até que eu quisesse testar o Test :: Builder por conta própria. Então, eu precisava de dois objetos Test :: Builder, um configurado como fictício, para capturar e testar seu comportamento e saída, e outro para ser o objeto de teste real. Nesse ponto, o Test :: Builder foi refatorado para um objeto real. O objeto singleton era armazenado como dados de classe e new()sempre o retornava. create()foi adicionado para criar um objeto novo e permitir o teste.

Atualmente, os usuários desejam alterar alguns comportamentos do Test :: Builder em seu próprio módulo, mas deixam outros em paz, enquanto o histórico do teste permanece em comum em todos os módulos de teste. O que está acontecendo agora é que o objeto monolítico Test :: Builder está sendo dividido em partes menores (histórico, saída, formato ...) com uma instância Test :: Builder reunindo-os. Agora o Test :: Builder não precisa mais ser um singleton. Seus componentes, como a história, podem ser. Isso empurra a necessidade inflexível de um singleton para um nível abaixo. Dá mais flexibilidade ao usuário para misturar e combinar peças. Os objetos singleton menores agora podem apenas armazenar dados, com seus objetos contidos decidindo como usá-los. Ele ainda permite que uma classe que não seja Test :: Builder seja reproduzida usando o histórico Test :: Builder e os singletons de saída.

Parece haver uma pressão entre a coordenação de dados e a flexibilidade de comportamento, que pode ser atenuada colocando o singleton em torno de apenas dados compartilhados com a menor quantidade de comportamento possível para garantir a integridade dos dados.

Schwern
fonte
5

Quando você carrega um objeto Propriedades de configuração, do banco de dados ou de um arquivo, ajuda a tê-lo como um singleton; não há razão para continuar relendo os dados estáticos que não serão alterados enquanto o servidor estiver em execução.

Dean J
fonte
2
Por que você não apenas carregava os dados uma vez e passava o objeto de configuração conforme necessário?
lagweezle
o que há com a passagem ??? Se eu tivesse que passar em torno de cada objeto que eu preciso Eu teria construtores com 20 argumentos ...
Enerccio
@Enerccio Se você possui objetos que dependem de 20 outros sem encapsulamento, você já tem problemas importantes de design.
spectras 23/01
@spectras Eu? Se eu implementar o diálogo da GUI, precisarei de: repositório, localização, dados da sessão, dados do aplicativo, pai do widget, dados do cliente, gerenciador de permissões e provavelmente mais. Claro, você pode agregar um pouco, mas por quê? Pessoalmente, uso a primavera e os aspectos para incluir automaticamente todas essas dependências na classe de widgets e isso desacopla tudo.
Enerccio 25/01
Se você tiver esse estado, considere implementar uma fachada, fornecendo uma visão dos aspectos relevantes para o contexto específico. Por quê? Porque isso permitiria um design limpo, sem os antipadrões de construtor de singleton ou 29 arg. Na verdade, o fato de sua caixa de diálogo da GUI acessar todas essas coisas grita "violação do princípio da responsabilidade única".
spectras 26/01
3

Como todos disseram, um recurso compartilhado - especificamente algo que não pode lidar com acesso simultâneo.

Um exemplo específico que eu já vi é o Lucene Search Index Writer.

Mike
fonte
1
E, no entanto, IndexWriter não é um solteirão ...
Mark
3

Você pode usar Singleton ao implementar o padrão State (da maneira mostrada no livro GoF). Isso ocorre porque as classes State concretas não têm um estado próprio e executam suas ações em termos de uma classe de contexto.

Você também pode fazer da Abstract Factory um singleton.

Emile Cormier
fonte
É o caso com o qual estou lidando agora em um projeto. Eu usei um padrão de estado para remover o código condicional repetitivo dos métodos do contexto. Os estados não possuem variáveis ​​de instância próprias. No entanto, estou em cima do muro para saber se devo fazê-los singletons. Sempre que o estado alterna, uma nova instância é instanciada. Isso parece um desperdício, porque não há como a instância ser diferente de outra (porque não há variáveis ​​de instância). Estou tentando descobrir por que não devo usá-lo.
kiwicomb123
1
@ kiwicomb123 Tente setState()responsabilizar-se por decidir a política de criação do estado. Ajuda se a sua linguagem de programação suportar modelos ou genéricos. Em vez de Singleton, você poderia usar o padrão Monostate , em que instanciar um objeto de estado acaba reutilizando o mesmo objeto de estado global / estático. A sintaxe para alterar o estado pode permanecer inalterada, pois seus usuários não precisam estar cientes de que o estado instanciado é um Monoestado.
Emile Cormier
Ok, então, nos meus estados, eu poderia apenas tornar todos os métodos estáticos; portanto, sempre que uma nova instância é criada, ela não tem a mesma sobrecarga? Estou um pouco confuso, preciso ler sobre o padrão Monostate.
kiwicomb123
@ kiwicomb123 Não, o Monostate não trata de tornar todos os membros estáticos. Melhor que você leia sobre isso e verifique SO para perguntas e respostas relacionadas.
Emile Cormier
Eu sinto que isso deveria ter mais votos. A fábrica abstrata é bastante comum e, como as fábricas são sem estado, estáveis ​​em estado sem estado e não podem ser implementadas com métodos estáticos (em Java) que não são substituídos, o uso de singleton deve ser bom.
DPM
3

Recursos compartilhados. Especialmente em PHP, uma classe de banco de dados, uma classe de modelo e uma classe de depósito de variável global. Todos precisam ser compartilhados por todos os módulos / classes que estão sendo usados ​​em todo o código.

É um verdadeiro uso de objeto -> a classe de modelo contém o modelo de página que está sendo construído e é modelado, adicionado e alterado pelos módulos que estão sendo adicionados à saída da página. Ele deve ser mantido como uma instância única para que isso possa acontecer, e o mesmo vale para os bancos de dados. Com um singleton de banco de dados compartilhado, todas as classes dos módulos podem ter acesso a consultas e obtê-las sem ter que executá-las novamente.

Um singleton de depósito de variável global fornece um depósito de variável global, confiável e facilmente utilizável. Ele organiza bastante o seu código. Imagine ter todos os valores de configuração em uma matriz em um singleton como:

$gb->config['hostname']

ou ter todos os valores de idioma em uma matriz como:

$gb->lang['ENTER_USER']

No final da execução do código da página, você obtém, digamos, um agora maduro:

$template

Singleton, um $gb singleton que possui a matriz lang para sua substituição e toda a saída carregada e pronta. Você apenas substitui-os pelas chaves que agora estão presentes no valor da página do objeto de modelo maduro e, em seguida, envia ao usuário.

A grande vantagem disso é que você pode fazer QUALQUER pós-processamento que desejar em qualquer coisa. Você pode canalizar todos os valores de idioma para o google translate ou outro serviço de tradução, recuperá-los e substituí-los em seus lugares, traduzidos, por exemplo. ou, você pode substituir nas estruturas da página ou nas cadeias de conteúdo conforme desejar.

Ozgur Zeren
fonte
21
Você pode dividir sua resposta em vários parágrafos e bloquear os segmentos de código para facilitar a leitura.
Justin
1

Pode ser muito pragmático configurar preocupações específicas de infraestrutura como singletons ou variáveis ​​globais. Meu exemplo favorito disso são as estruturas de injeção de dependência que usam singletons para atuar como um ponto de conexão com a estrutura.

Nesse caso, você está usando uma dependência da infraestrutura para simplificar o uso da biblioteca e evitar a complexidade desnecessária.

smaclell
fonte
0

Eu o uso para um objeto que encapsula parâmetros de linha de comando ao lidar com módulos conectáveis. O programa principal não sabe quais são os parâmetros da linha de comando para os módulos que são carregados (e nem sempre sabe quais módulos estão sendo carregados). por exemplo, cargas principais A, que não precisam de nenhum parâmetro em si (então, por que ele deve levar um ponteiro / referência extra / o que seja, não tenho certeza - parece poluição), carrega os módulos X, Y e Z. Dois destes, digamos X e Z, precisam (ou aceitam) parâmetros; portanto, eles retornam ao singleton da linha de comando para informar quais parâmetros devem ser aceitos e, no tempo de execução, eles retornam para descobrir se o usuário realmente especificou algum parâmetro. deles.

De várias maneiras, um singleton para manipular parâmetros CGI funcionaria da mesma forma se você estivesse usando apenas um processo por consulta (outros métodos mod_ * não fazem isso, então seria ruim lá - portanto, o argumento que diz que você não deve ' Não use singletons no mundo mod_cgi para o port_modl ou qualquer outro mundo.

Tanktalus
fonte
-1

Um exemplo com código, talvez.

Aqui, o ConcreteRegistry é um singleton em um jogo de pôquer que permite que os comportamentos de toda a árvore de pacotes acessem as poucas interfaces principais do jogo (ou seja, as fachadas do modelo, visualização, controlador, ambiente etc.):

http://www.edmundkirwan.com/servlet/fractal/cs1/frac-cs40.html

Ed.


fonte
1
O link agora está quebrado, mas se você estiver registrando informações de exibição em um singleton, que serão acessadas em todo o aplicativo, estará perdendo o objetivo do MVC. Uma visão é atualizada por (e se comunica com) um controlador, que usa o modelo. Como parece aqui, provavelmente é um mau uso de Singleton e uma refatoração está em ordem.
Drharris 27/05
-9

1 - Um comentário sobre a primeira resposta:

Não concordo com uma classe estática de logger. isso pode ser prático para uma implementação, mas não pode ser substituível para testes de unidade. Uma classe estática não pode ser substituída por um teste duplo. Se você não fizer o teste de unidade, não verá o problema aqui.

2 - Tento não criar um singleton manualmente. Acabei de criar um objeto simples com construtores que me permitem injetar colaboradores no objeto. Se eu precisasse de um singleton, usaria uma estrutura de inyection de dependência (Spring.NET, Unity para .NET, Spring para Java) ou alguma outra.

bloparod
fonte
11
Você deve comentar as respostas diretamente, clicando no link na parte inferior de uma resposta; é muito mais fácil ler dessa maneira. Além disso, a resposta que você viu no topo provavelmente não é a primeira. As respostas são reordenadas o tempo todo.
Ross
por que você deseja log de teste de unidade?
Enerccio 18/04/19
Há uma enorme diferença entre "uma classe Logger estática" e uma instância estática do Logger . O padrão singleton não diz "tornar sua classe estática", diz para tornar o acesso à instância de um objeto estático. Por exemplo, ILogger logger = Logger.SingleInstance();quando esse método é estático e retorna uma instância armazenada estaticamente de um ILogger. Você usou o exemplo de "uma estrutura de injeção de dependência". Quase todos os contêineres DI são singletons; suas configurações são definidas estaticamente e, finalmente, acessadas de / armazenadas em uma única interface de provedor de serviços.
Jon Davis