Então Singletons são ruins, e daí?


554

Ultimamente, tem havido muita discussão sobre os problemas com o uso (e o uso excessivo) de Singletons. Também fui uma dessas pessoas no início da minha carreira. Posso ver qual é o problema agora e, no entanto, ainda existem muitos casos em que não vejo uma boa alternativa - e poucas discussões anti-Singleton realmente fornecem uma.

Aqui está um exemplo real de um grande projeto recente em que estive envolvido:

O aplicativo era um cliente espesso com muitas telas e componentes separados, que usam grandes quantidades de dados de um estado de servidor que não é atualizado com muita frequência. Esses dados foram basicamente armazenados em cache em um objeto "gerenciador" de Singleton - o temido "estado global". A idéia era ter esse lugar no aplicativo que mantém os dados armazenados e sincronizados e, em seguida, qualquer nova tela aberta pode apenas consultar a maior parte do que precisa a partir daí, sem fazer pedidos repetidos de vários dados de suporte do servidor. Solicitar constantemente ao servidor exigiria muita largura de banda - e eu estou falando milhares de dólares em contas extras da Internet por semana, o que era inaceitável.

Existe alguma outra abordagem que possa ser apropriada aqui, além de basicamente ter esse tipo de objeto de cache do gerenciador de dados global? Esse objeto não precisa oficialmente ser um "Singleton", é claro, mas conceitualmente faz sentido ser um. O que é uma boa alternativa limpa aqui?


10
Que problema o uso de um Singleton deve resolver? Como é melhor resolver esse problema do que as alternativas (como uma classe estática)?
Anon.

14
@ Anon: Como o uso de uma classe estática melhora a situação. Ainda há acoplamento apertado?
Martin York

5
@ Martin: Eu não estou sugerindo que torna "melhor". Estou sugerindo que, na maioria dos casos, um singleton é uma solução em busca de um problema.
Anon.

9
@ Anon: Não é verdade. As classes estáticas não dão a você (quase) nenhum controle sobre a instanciação e tornam a multithreading ainda mais difícil do que os Singletons (já que você precisa serializar o acesso a cada método individual em vez de apenas a instância). Singletons também podem pelo menos implementar uma interface que classes estáticas não podem. As classes estáticas certamente têm suas vantagens, mas neste caso o Singleton é definitivamente o menor de dois males consideráveis. Uma classe estática que implementa qualquer estado mutável é como um grande néon intermitente "AVISO: DESIGN RUIM!" placa.
Aaronaught 27/01

7
@Aaronaught: se você está apenas sincronizando o acesso ao singleton, sua concorrência está quebrada . Seu encadeamento pode ser interrompido logo após a busca do objeto singleton, outro encadeamento é acionado e responsabiliza a condição de corrida. Usar um Singleton em vez de uma classe estática, na maioria dos casos, é apenas retirar os sinais de alerta e pensar que resolve o problema .
Anon.

Respostas:


809

É importante distinguir aqui entre instâncias únicas e o padrão de design Singleton .

Instâncias únicas são simplesmente uma realidade. A maioria dos aplicativos é projetada para funcionar apenas com uma configuração por vez, uma interface do usuário por vez, um sistema de arquivos por vez e assim por diante. Se houver muitos estados ou dados a serem mantidos, certamente você desejaria ter apenas uma instância e mantê-los ativos o maior tempo possível.

O padrão de design Singleton é um tipo muito específico de instância única, especificamente uma que é:

  • Acessível através de um campo de instância estático global;
  • Criado na inicialização do programa ou no primeiro acesso;
  • Nenhum construtor público (não pode instanciar diretamente);
  • Nunca liberado explicitamente (liberado implicitamente na finalização do programa).

É por causa dessa escolha de design específico que o padrão apresenta vários problemas em potencial a longo prazo:

  • Incapacidade de usar classes abstratas ou de interface;
  • Incapacidade de subclasse;
  • Alto acoplamento em todo o aplicativo (difícil de modificar);
  • Difícil de testar (não pode falsificar / zombar em testes de unidade);
  • Difícil de paralelizar no caso de estado mutável (requer travamento extenso);
  • e assim por diante.

Na verdade, nenhum desses sintomas é endêmico para instâncias únicas, apenas o padrão Singleton.

O que você pode fazer? Simplesmente não use o padrão Singleton.

Citando a pergunta:

A idéia era ter esse lugar no aplicativo que mantém os dados armazenados e sincronizados e, em seguida, qualquer nova tela aberta pode apenas consultar a maior parte do que precisa a partir daí, sem fazer pedidos repetidos de vários dados de suporte do servidor. Solicitar constantemente ao servidor exigiria muita largura de banda - e eu estou falando milhares de dólares em contas extras da Internet por semana, o que era inaceitável.

Esse conceito tem um nome, como você sugere, mas parece incerto. É chamado de cache . Se você quiser se divertir, pode chamá-lo de "cache offline" ou apenas uma cópia offline de dados remotos.

Um cache não precisa ser um singleton. Pode ser necessário que seja uma única instância se você deseja evitar a busca dos mesmos dados para várias instâncias de cache; mas isso não significa que você realmente exponha tudo a todos .

A primeira coisa que eu faria é separar as diferentes áreas funcionais do cache em interfaces separadas. Por exemplo, digamos que você estivesse criando o pior clone do YouTube do mundo com base no Microsoft Access:

                          MSAccessCache
                                ▲
                                |
              + ----------------- + ----------------- +
              | | |
         IMediaCache IProfileCache IPageCache
              | | |
              | | |
          VideoPage MyAccountPage MostPopularPage

Aqui você tem várias interfaces que descrevem os tipos específicos de dados aos quais uma determinada classe pode precisar acessar - mídia, perfis de usuário e páginas estáticas (como a primeira página). Tudo isso é implementado por um mega cache, mas você projeta suas classes individuais para aceitar as interfaces, para que elas não se importem com o tipo de instância que possuem. Você inicializa a instância física uma vez, quando o programa é iniciado e, em seguida, começa a passar pelas instâncias (convertidas para um tipo de interface específico) por meio de construtores e propriedades públicas.

Isso é chamado de injeção de dependência , a propósito; você não precisa usar o Spring ou qualquer contêiner IoC especial, desde que o design de sua classe geral aceite suas dependências do chamador, em vez de instanciá-las por conta própria ou fazer referência ao estado global .

Por que você deve usar o design baseado em interface? Três razões:

  1. Facilita a leitura do código; você pode entender claramente pelas interfaces exatamente de quais dados as classes dependentes dependem.

  2. Se e quando você perceber que o Microsoft Access não era a melhor opção para um back-end de dados, poderá substituí-lo por algo melhor - digamos, SQL Server.

  3. Se e quando você perceber que o SQL Server não é a melhor opção especificamente para a mídia , poderá interromper sua implementação sem afetar nenhuma outra parte do sistema . É aí que entra o verdadeiro poder da abstração.

Se você quiser dar um passo adiante, use um contêiner IoC (estrutura DI) como Spring (Java) ou Unity (.NET). Quase toda estrutura de DI fará seu próprio gerenciamento vitalício e permitirá especificamente que você defina um serviço específico como uma instância única (geralmente denominando "singleton", mas isso é apenas para familiaridade). Basicamente, essas estruturas economizam a maior parte do trabalho do macaco de passar manualmente as instâncias, mas elas não são estritamente necessárias. Você não precisa de nenhuma ferramenta especial para implementar esse design.

Por uma questão de integridade, devo salientar que o design acima também não é ideal. Ao lidar com um cache (como você é), você deve realmente ter uma camada totalmente separada . Em outras palavras, um design como este:

                                                        + - IMediaRepository
                                                        |
                          Cache (Genérico) --------------- + - IProfileRepository
                                ▲
                                | + - IPageRepository
              + ----------------- + ----------------- +
              | | |
         IMediaCache IProfileCache IPageCache
              | | |
              | | |
          VideoPage MyAccountPage MostPopularPage

O benefício disso é que você nunca precisará interromper sua Cacheinstância se decidir refatorar; você pode alterar como a Mídia é armazenada simplesmente alimentando uma implementação alternativa de IMediaRepository. Se você pensar em como isso se encaixa, verá que ele ainda cria apenas uma instância física de um cache, para que você nunca precise buscar os mesmos dados duas vezes.

Nada disso significa dizer que todo software do mundo precisa ser arquitetado para esses padrões exigentes de alta coesão e acoplamento flexível; depende do tamanho e escopo do projeto, sua equipe, seu orçamento, prazos etc. Mas se você está perguntando qual é o melhor design (para usar no lugar de um singleton), então é isso.

PS Como outros já declararam, provavelmente não é a melhor idéia para as classes dependentes estarem cientes de que estão usando um cache - que é um detalhe de implementação com o qual simplesmente nunca deveriam se preocupar. Dito isto, a arquitetura geral ainda seria muito parecida com a mostrada acima, você simplesmente não se referiria às interfaces individuais como Caches . Em vez disso, você os chamaria Serviços ou algo semelhante.


131
Primeiro post que eu já li sobre o que realmente explica o DI como uma alternativa ao estado global. Obrigado pelo tempo e esforço dedicados a isso. Estamos todos melhores como resultado deste post.
MrLane

4
Por que o cache não pode ser um singleton? Não é um singleton se você o distribuir e usar a injeção de dependência? Singleton é apenas limitar-nos a uma instância, não sobre como é acessado, certo? Ver a minha opinião sobre isso: assoc.tumblr.com/post/51302471844/the-misunderstood-singleton
Erik Engheim

29
@ AdamSmith: Você realmente leu alguma desta resposta? Sua pergunta é respondida nos dois primeiros parágrafos. Padrão Singleton! == Instância única.
Aaronaught 18/09/2013

5
@Cawas e Adam Smith - Lendo seus links, sinto que você realmente não leu esta resposta - tudo já está lá.
Wilbert #

19
@Cawas Eu sinto que a base desta resposta é a distinção entre instância única e singleton. Singleton é ruim, instância única não é. A injeção de dependência é uma maneira geral e agradável de usar instâncias únicas sem precisar usar singletons.
Wilbert #

48

No caso de você indicar, parece que o uso de um Singleton não é o problema, mas o sintoma de um problema - um problema arquitetônico maior.

Por que as telas estão consultando o objeto de cache em busca de dados? O armazenamento em cache deve ser transparente para o cliente. Deve haver uma abstração apropriada para fornecer os dados, e a implementação dessa abstração pode utilizar armazenamento em cache.

O problema é provável que as dependências entre partes do sistema não estejam configuradas corretamente, e isso provavelmente é sistêmico.

Por que as telas precisam ter conhecimento de onde obtêm seus dados? Por que as telas não recebem um objeto que pode atender às solicitações de dados (atrás das quais um cache está oculto)? Muitas vezes, a responsabilidade pela criação de telas não é centralizada e, portanto, não há um ponto claro de injetar as dependências.

Mais uma vez, estamos analisando questões de arquitetura e design em larga escala.

Além disso, é muito importante entender que a vida útil de um objeto pode ser completamente separada da maneira como o objeto é encontrado para uso.

Um cache terá que permanecer durante todo o tempo de vida do aplicativo (para ser útil), para que o tempo de vida do objeto seja o de um Singleton.

Mas o problema com o Singleton (pelo menos a implementação comum do Singleton como uma classe / propriedade estática), é como as outras classes que o usam procuram encontrá-lo.

Com uma implementação estática de Singleton, a convenção é simplesmente usá-la sempre que necessário. Mas isso esconde completamente a dependência e une firmemente as duas classes.

Se fornecermos a dependência para a classe, essa dependência é explícita e toda a classe consumidora precisa ter conhecimento do contrato disponível para uso.


2
Há uma quantidade gigantesca de dados que determinadas telas podem precisar, mas não necessariamente. E você não sabe até que ações do usuário tenham sido tomadas que definem isso - e há muitas, muitas combinações. Portanto, a maneira como foi feito foi ter alguns dados globais comuns que são mantidos em cache e sincronizados no cliente (principalmente obtidos no logon) e, em seguida, solicitações subsequentes aumentam ainda mais o cache, porque os dados solicitados explicitamente tendem a ser reutilizados novamente em a mesma sessão. O foco está em reduzir a solicitação ao servidor, daí a necessidade de um cache do lado do cliente. <cont>
Bobby Tables

1
<cont> É essencialmente transparente. No sentido de que há um retorno de chamada do servidor se alguns dados necessários ainda não estiverem armazenados em cache. Mas a implementação (lógica e fisicamente) desse gerenciador de cache é um Singleton.
Bobby Tables

6
Estou com qstarin aqui: Os objetos que acessam os dados não devem saber (ou precisam saber) que os dados estão armazenados em cache (isto é, um detalhe de implementação). Os usuários dos dados apenas solicitam os dados (ou solicitam uma interface para recuperar os dados).
Martin York

1
O armazenamento em cache é essencialmente um detalhe de implementação. Existe uma interface através da qual os dados são consultados e os objetos que os obtêm não sabem se vieram do cache ou não. Mas por baixo desse gerenciador de cache está um Singleton.
Bobby Tables

2
@ Tabelas Bobby: então sua situação não é tão terrível quanto parecia. Esse Singleton (supondo que você queira dizer uma classe estática, não apenas um objeto com uma instância que dura enquanto o aplicativo) ainda é problemático. Está ocultando o fato de que seu objeto de fornecimento de dados depende de um provedor de cache. É melhor se isso for explícito e externalizado. Desacople-os. É essencial para a testabilidade que você possa substituir componentes com facilidade, e um provedor de cache é um excelente exemplo desse componente (com que frequência um provedor de cache é suportado pelo ASP.Net).
precisa saber é o seguinte

45

Escrevi um capítulo inteiro sobre essa questão. Principalmente no contexto de jogos, mas a maioria deve se aplicar fora dos jogos.

tl; dr:

O padrão Gang of Four Singleton faz duas coisas: fornece acesso conveniente a um objeto de qualquer lugar e garante que apenas uma instância dele possa ser criada. Em 99% do tempo, tudo o que importa é a primeira metade disso, e carregar ao longo da segunda metade para obtê-lo adiciona uma limitação desnecessária.

Não apenas isso, mas existem soluções melhores para oferecer acesso conveniente. Tornar um objeto global é a opção nuclear para resolver isso e facilita a destruição do seu encapsulamento. Tudo o que é ruim nos globais se aplica completamente aos singletons.

Se você o estiver usando apenas porque possui muitos lugares no código que precisam tocar no mesmo objeto, tente encontrar uma maneira melhor de fornecê-lo apenas a esses objetos sem expô-lo a toda a base de código. Outras soluções:

  • Abandone completamente. Eu já vi muitas classes singleton que não têm nenhum estado e são apenas sacos de funções auxiliares. Aqueles não precisam de uma instância. Apenas torne-as funções estáticas ou mova-as para uma das classes que a função usa como argumento. Você não precisaria de uma Mathaula especial se pudesse 123.Abs().

  • Passe por aí. A solução simples, se um método precisa de algum outro objeto, é apenas passá-lo. Não há nada errado em passar alguns objetos.

  • Coloque-o na classe base. Se você tem muitas classes que precisam acessar algum objeto especial e elas compartilham uma classe base, você pode tornar esse objeto um membro na base. Ao construí-lo, passe o objeto. Agora, todos os objetos derivados podem obtê-lo quando necessário. Se você o proteger, garante que o objeto ainda fique encapsulado.


1
Você pode resumir aqui?
Nicole

1
Concluído, mas ainda o incentivo a ler o capítulo inteiro.
27511 munificent

4
Outra alternativa: injeção de dependência!
Brad Cupit 02/02

1
@ BradCupit ele fala sobre isso no link também ... Devo dizer que ainda estou tentando digerir tudo isso. Mas foi a leitura mais elucidativa de singletons que já li. Até agora, eu tinha certeza de que singletons eram necessários, assim como vars globais, e eu estava promovendo o Toolbox . Agora eu simplesmente não sei mais. Senhor munificient, você pode me dizer se o localizador de serviço é apenas uma caixa de ferramentas estática ? Não seria melhor torná-lo um singleton (portanto, uma caixa de ferramentas)?
cregox

1
"Caixa de ferramentas" é bem parecida com "Localizador de serviço" para mim. Se você usa uma estática para ela ou se torna um singleton, acho que isso não é importante para a maioria dos programas. Costumo me inclinar para a estática, porque por que lidar com a inicialização lenta e a alocação de heap, se você não precisa?
13133 munificent

21

Não é o estado global em si que é o problema.

Realmente você só precisa se preocupar global mutable state. O estado constante não é afetado pelos efeitos colaterais e, portanto, é menos problemático.

A principal preocupação com o singleton é que ele adiciona acoplamento e, portanto, dificulta o teste. Você pode reduzir o acoplamento obtendo o singleton de outra fonte (por exemplo, uma fábrica). Isso permitirá que você desacople o código de uma instância específica (embora você fique mais acoplado à fábrica (mas pelo menos a fábrica possa ter implementações alternativas para diferentes fases)).

Na sua situação, acho que você pode se safar desde que seu singleton realmente implemente uma interface (para que uma alternativa possa ser usada em outras situações).

Mas outra grande desvantagem dos singletons é que, uma vez instalados, removê-los do código e substituí-los por outra coisa torna-se uma tarefa muito difícil (existe esse acoplamento novamente).

// Example from 5 minutes (con't be too critical)
class ServerFactory
{
    public:
        // By default return a RealServer
        ServerInterface& getServer();

        // Set a non default server:
        void setServer(ServerInterface& server);
};

class ServerInterface { /* define Interface */ };

class RealServer: public ServerInterface {}; // This is a singleton (potentially)

class TestServer: public ServerInterface {}; // This need not be.

Isso faz sentido. Isso também me faz pensar que nunca realmente abusei dos Singletons, mas comecei a duvidar de qualquer uso deles. Mas não consigo pensar em nenhum abuso que eu tenha cometido, de acordo com esses pontos. :)
Bobby Tables

2
(você provavelmente quer dizer, por si só, não "por dizer")
nohat 27/01

4
@nohat: Eu sou nativo do "Queens English" e, portanto, rejeito qualquer coisa de aparência francesa, a menos que a melhore (como le weekendoops, é uma das nossas). Obrigado :-)
Martin York

21
per se é latim.
Anon.

2
@ Anon: OK. Isso não é tão ruim, então ;-) #
Martin York

19

Então o que? Como ninguém disse isso: Caixa de ferramentas . Ou seja, se você deseja variáveis ​​globais .

O abuso de singleton pode ser evitado olhando o problema de um ângulo diferente. Suponha que um aplicativo precise apenas de uma instância de uma classe e o aplicativo configure essa classe na inicialização: Por que a própria classe deve ser responsável por ser um singleton? Parece bastante lógico para o aplicativo assumir essa responsabilidade, já que requer esse tipo de comportamento. O aplicativo, não o componente, deve ser o singleton. O aplicativo disponibiliza uma instância do componente para uso de qualquer código específico do aplicativo. Quando um aplicativo usa vários desses componentes, pode agregá-los ao que chamamos de caixa de ferramentas.

Simplificando, a caixa de ferramentas do aplicativo é um singleton que é responsável por se configurar ou por permitir que o mecanismo de inicialização do aplicativo o configure ...

public class Toolbox {
     private static Toolbox _instance; 

     public static Toolbox Instance {
         get {
             if (_instance == null) {
                 _instance = new Toolbox(); 
             }
             return _instance; 
         }
     }

     protected Toolbox() {
         Initialize(); 
     }

     protected void Initialize() {
         // Your code here
     }

     private MyComponent _myComponent; 

     public MyComponent MyComponent() {
         get {
             return _myComponent(); 
         }
     }
     ... 

     // Optional: standard extension allowing
     // runtime registration of global objects. 
     private Map components; 

     public Object GetComponent (String componentName) {
         return components.Get(componentName); 
     }

     public void RegisterComponent(String componentName, Object component) 
     {
         components.Put(componentName, component); 
     }

     public void DeregisterComponent(String componentName) {
         components.Remove(componentName); 
     }

}

Mas adivinhem? É um singleton!

E o que é um singleton?

Talvez seja aí que a confusão começa.

Para mim, o singleton é um objeto imposto para ter apenas uma instância e sempre. Você pode acessá-lo em qualquer lugar, a qualquer momento, sem a necessidade de instanciar. É por isso que está tão intimamente relacionado static. Para comparação, staticé basicamente a mesma coisa, exceto que não é uma instância. Não precisamos instanciar, nem podemos, porque é alocado automaticamente. E isso pode e traz problemas.

Pela minha experiência, a simples substituição staticde Singleton resolveu muitos problemas em um projeto de bolsa de retalhos de tamanho médio em que estou participando. Isso significa apenas que ele tem algum uso para projetos mal projetados. Acho que há muita discussão se o padrão singleton é útil ou não e não posso argumentar se é realmente ruim . Mas ainda existem bons argumentos a favor do singleton sobre métodos estáticos, em geral .

A única coisa que tenho certeza de que é ruim nos singletons é quando os usamos, ignorando as boas práticas. Isso é realmente algo não tão fácil de lidar. Mas más práticas podem ser aplicadas a qualquer padrão. E, eu sei, é genérico demais para dizer isso ... quero dizer, é demais.

Não me interpretem mal!

Simplificando, assim como os vars globais , os singletons ainda devem ser evitados o tempo todo . Especialmente porque eles são abusados ​​demais. Mas os vars globais nem sempre podem ser evitados e devemos usá-los nesse último caso.

Enfim, existem muitas outras sugestões além da Caixa de ferramentas e, assim como a caixa de ferramentas, cada uma tem sua aplicação ...

Outras alternativas

  • O melhor artigo que eu acabei de ler sobre singletons sugere o Service Locator como uma alternativa. Para mim, isso é basicamente uma " Caixa de ferramentas estática ", se você preferir. Em outras palavras, torne o Localizador de Serviço um Singleton e você terá uma Caixa de Ferramentas. Isso contraria sua sugestão inicial de evitar o singleton, é claro, mas isso é apenas para reforçar a questão do singleton, como ele é usado, não o padrão em si.

  • Outros sugerem o padrão de fábrica como uma alternativa. Foi a primeira alternativa que ouvi de um colega e a eliminamos rapidamente para nosso uso como var global . Com certeza tem seu uso, mas o mesmo acontece com os singletons.

Ambas as alternativas acima são boas alternativas. Mas tudo depende do seu uso.

Agora, implicar singletons deve ser evitado a todo custo é apenas errado ...

  • A resposta de Aaronaught está sugerindo nunca usar singletons , por uma série de razões. Mas todas são razões contra como ela é mal usada e abusada, não diretamente contra o próprio padrão. Eu concordo com toda a preocupação com esses pontos, como não posso? Eu apenas acho que é enganoso.

As incapacidades (para abstrair ou subclasse) estão realmente presentes, mas e daí? Não é para isso. Não há impossibilidade de interface , pelo que sei . O acoplamento alto também pode estar presente, mas isso é apenas porque é usado com frequência. Não precisa . De fato, o acoplamento em si não tem nada a ver com o padrão singleton. Sendo esclarecido, também já elimina a dificuldade de testar. Quanto à dificuldade de paralelizar, isso depende da linguagem e da plataforma, portanto, novamente, não é um problema no padrão.

Exemplos práticos

Muitas vezes vejo 2 sendo usados, a favor e contra singletons. Cache da Web (meu caso) e serviço de log .

O registro, alguns argumentam , é um exemplo singleton perfeito, porque, e cito:

  • Os solicitantes precisam de um objeto conhecido para o qual enviar solicitações para log. Isso significa um ponto de acesso global.
  • Como o serviço de log é uma única fonte de eventos na qual vários ouvintes podem se registrar, é necessário apenas uma instância.
  • Embora aplicativos diferentes possam registrar em diferentes dispositivos de saída, a maneira como eles registram seus ouvintes é sempre a mesma. Toda a personalização é feita através dos ouvintes. Os clientes podem solicitar o registro sem saber como ou onde o texto será registrado. Todo aplicativo, portanto, usaria o serviço de log exatamente da mesma maneira.
  • Qualquer aplicativo deve conseguir se livrar de apenas uma instância do serviço de log.
  • Qualquer objeto pode ser um solicitante de log, incluindo componentes reutilizáveis, para que não sejam acoplados a nenhum aplicativo específico.

Enquanto outros argumentam, fica difícil expandir o serviço de log quando você perceber que ele não deve ser apenas uma instância.

Bem, eu digo que ambos os argumentos são válidos. O problema aqui, novamente, não está no padrão singleton. É nas decisões de arquitetura e ponderação se a refatoração é um risco viável. É outro problema quando, geralmente, a refatoração é a última medida corretiva necessária.


@gnat Obrigado! Eu estava pensando em editar a resposta para adicionar algum aviso sobre o uso de Singletons ... Sua cotação se encaixa perfeitamente!
Cregox

2
feliz que você gostou. Não tenho certeza se isso vai ajudar a evitar downvotes embora - provavelmente leitores estão tendo dificuldade ponto feito neste post para o problema concreto colocado para fora na questão de conexão, especialmente à luz da análise fantástico previsto no anterior resposta
mosquito

@gnat sim, eu sabia que esta era uma longa batalha. Espero que o tempo diga. ;-)
cregox 13/11/2013

1
Eu concordo com sua orientação geral aqui, embora esteja sendo um pouco entusiasmado demais com uma biblioteca que parece não ser muito mais que um contêiner de IoC extremamente simples (ainda mais básico do que, por exemplo, o Funq ). O Localizador de Serviço é na verdade um antipadrão; é uma ferramenta útil principalmente em projetos herdados / brownfield, junto com o "DI do pobre", onde seria muito caro refatorar tudo para usar adequadamente um contêiner de IoC.
Aaronaught

1
@Cawas: Ah, desculpe - fiquei confuso java.awt.Toolkit. Meu argumento é o mesmo: Toolboxsoa como uma sacola de pedaços não relacionados, em vez de uma classe coerente com um único objetivo. Não parece um bom design para mim. (Note-se que o artigo que você se refere é a partir de 2001, antes da injeção de dependência e recipientes DI tinha se tornado comum.)
Jon Skeet

5

Meu principal problema com o padrão de design singleton é que é muito difícil escrever bons testes de unidade para seu aplicativo.

Todo componente que tem uma dependência desse "gerenciador" faz isso consultando sua instância singleton. E se você quiser escrever um teste de unidade para esse componente, precisará injetar dados nessa instância singleton, o que pode não ser fácil.

Se, por outro lado, seu "gerente" for injetado nos componentes dependentes por meio de um parâmetro construtor, e o componente não souber o tipo concreto do gerente, apenas uma interface ou classe base abstrata que o gerente implementa, uma unidade O teste poderia fornecer implementações alternativas do gerente ao testar dependências.

Se você usar contêineres IOC para configurar e instanciar os componentes que compõem seu aplicativo, poderá configurar facilmente seu contêiner IOC para criar apenas uma instância do "gerenciador", permitindo obter o mesmo, apenas uma instância controlando o cache global de aplicativos .

Mas se você não se importa com testes de unidade, um padrão de design único pode ser perfeitamente adequado. (mas eu não faria isso de qualquer maneira)


Bem explicado, esta é a resposta que melhor explica a questão sobre o teste de Singletons
José Tomás Tocino

4

Um singleton não é fundamentalmente ruim , no sentido de que qualquer coisa que a computação de design possa ser boa ou ruim. Só pode estar correto (fornece os resultados esperados) ou não. Também pode ser útil ou não, se tornar o código mais claro ou mais eficiente.

Um caso em que singletons são úteis é quando eles representam uma entidade que é realmente única. Na maioria dos ambientes, os bancos de dados são únicos, na verdade existe apenas um banco de dados. A conexão com esse banco de dados pode ser complicada porque requer permissões especiais ou atravessar vários tipos de conexão. Organizar essa conexão em um singleton provavelmente faz muito sentido apenas por esse motivo.

Mas você também precisa ter certeza de que o singleton é realmente um singleton, e não uma variável global. Isso é importante quando o banco de dados único e único é realmente de 4 bancos de dados, um para os equipamentos de produção, preparação, desenvolvimento e teste. Um Singleton do banco de dados descobrirá a quem ele deve se conectar, pegue a instância única desse banco de dados, conecte-o, se necessário, e devolva-o ao chamador.

Quando um singleton não é realmente um singleton (é quando a maioria dos programadores fica chateado), é um global preguiçosamente instanciado, não há oportunidade de injetar uma instância correta.

Outra característica útil de um padrão singleton bem projetado é que ele geralmente não é observável. O chamador pede uma conexão. O serviço que fornece pode retornar um objeto em pool ou, se estiver executando um teste, pode criar um novo para cada chamador ou fornecer um objeto simulado.


3

O uso do padrão singleton que representa objetos reais é perfeitamente aceitável. Eu escrevo para o iPhone e há muitos singletons na estrutura do Cocoa Touch. O aplicativo em si é representado por um singleton da classe UIApplication. Como você é apenas um aplicativo, é apropriado representá-lo com um singleton.

Usar um singleton como uma classe de gerenciador de dados é bom, desde que seja projetado corretamente. Se é um balde de propriedades de dados, isso não é melhor que o escopo global. Se é um conjunto de getters e setters, é melhor, mas ainda não é ótimo. Se é uma classe que realmente gerencia toda a interface de dados, incluindo talvez buscar dados remotos, cache, configuração e desmontagem ... Isso pode ser muito útil.


2

Singletons são apenas a projeção de uma arquitetura orientada a serviços em um programa.

Uma API é um exemplo de um singleton no nível do protocolo. Você acessa o Twitter, o Google etc. por meio de essencialmente singletons. Então, por que os singletons se tornam ruins dentro de um programa?

Depende de como você pensa em um programa. Se você pensa em um programa como uma sociedade de serviços, em vez de instâncias em cache vinculadas aleatoriamente, os singletons fazem todo o sentido.

Singletons são um ponto de acesso ao serviço. A interface pública para uma biblioteca de funcionalidades bem restrita que talvez oculte uma arquitetura interna muito sofisticada.

Portanto, não vejo um singleton tão diferente de uma fábrica. O singleton pode ter parâmetros de construtor passados. Ele pode ser criado por algum contexto que sabe como resolver a impressora padrão em relação a todos os mecanismos de seleção possíveis, por exemplo. Para testar, você pode inserir seu próprio mock. Portanto, pode ser bastante flexível.

A chave está internamente em um programa quando executo e preciso de um pouco de funcionalidade. Posso acessar o singleton com total confiança de que o serviço está instalado e pronto para uso. Essa é a chave quando existem diferentes threads iniciando em um processo que devem passar por uma máquina de estado para serem considerados prontos.

Normalmente, eu envolvia uma XxxServiceclasse que envolve um singleton na classe Xxx. O singleton não está na classe Xxx, é separado em outra classe XxxService,. Isso ocorre porque Xxxpodemos ter várias instâncias, embora não seja provável, mas ainda queremos ter uma Xxxinstância globalmente acessível em cada sistema. XxxServicefornece uma boa separação de preocupações. Xxxnão precisa impor uma política de singleton, mas podemos usar Xxxcomo singleton quando necessário.

Algo como:

//XxxService.h:
/**
 * Provide singleton wrapper for Xxx object. This wrapper
 * can be autogenerated so is not made part of the object.
 */

#include "Xxx/Xxx.h"


class XxxService
{
    public:
    /**
     * Return a Xxx object as a singleton. The double check
     * singleton algorithm is used. A 0 return means there was
     * an error. Developers should use this as the access point to
     * get the Xxx object.
     *
     * <PRE>
     * @@ #include "Xxx/XxxService.h"
     * @@ Xxx* xxx= XxxService::Singleton();
     * <PRE>
     */

     static Xxx*     Singleton();

     private:
         static Mutex  mProtection;
};


//XxxService.cpp:

#include "Xxx/XxxService.h"                   // class implemented
#include "LockGuard.h"     

// CLASS SCOPE
//
Mutex XxxService::mProtection;

Xxx* XxxService::Singleton()
{
    static Xxx* singleton;  // the variable holding the singleton

    // First check to see if the singleton has been created.
    //
    if (singleton == 0)
    {
        // Block all but the first creator.
        //
        LockGuard lock(mProtection);

        // Check again just in case someone had created it
        // while we were blocked.
        //
        if (singleton == 0)
        {
            // Create the singleton Xxx object. It's assigned
            // to a temporary so other accessors don't see
            // the singleton as created before it really is.
            //
            Xxx* inprocess_singleton= new Xxx;

            // Move the singleton to state online so we know that is has
            // been created and it ready for use.
            //
            if (inprocess_singleton->MoveOnline())
            {
                LOG(0, "XxxService:Service: FAIL MoveOnline");
                return 0;
            }

            // Wait until the module says it's in online state.
            //
            if (inprocess_singleton->WaitTil(Module::MODULE_STATE_ONLINE))
            {
                LOG(0, "XxxService:Service: FAIL move to online");
                return 0;
            }

            // The singleton is created successfully so assign it.
            //
            singleton= inprocess_singleton;


        }// still not created
    }// not created

    // Return the created singleton.
    //
    return singleton;

}// Singleton  

1

Primeira pergunta, você encontra muitos erros no aplicativo? talvez esquecendo de atualizar o cache ou um cache ruim ou ache difícil mudar? (Lembro-me de que um aplicativo não mudaria de tamanho, a menos que você também alterasse a cor ... você pode alterar a cor novamente e manter o tamanho).

O que você faria é ter essa classe, mas REMOVER TODOS OS MEMBROS ESTÁTICOS. Ok, isso não é necessário, mas eu recomendo. Realmente, você apenas inicializa a classe como uma classe normal e passa o ponteiro. Não diga ClassIWant.APtr (). LetMeChange.ANYTHINGATALL (). Andhave_no_structure ()

É mais trabalho, mas realmente, é menos confuso. Alguns lugares onde você não deve mudar as coisas que não pode agora, já que não são mais globais. Todas as minhas aulas de gerente são classes regulares, apenas trate-as assim.


1

IMO, seu exemplo parece bom. Eu sugeriria fatorar o seguinte: objeto de cache para cada (e atrás de cada) objeto de dados; objetos de cache e objetos de acessador de banco de dados têm a mesma interface. Isso permite trocar caches dentro e fora do código; Além disso, oferece uma rota de expansão fácil.

Gráfico:

DB
|
DB Accessor for OBJ A
| 
Cache for OBJ A
|
OBJ A Client requesting

O acessador e o cache do banco de dados podem herdar do mesmo objeto ou tipo de pato e parecer com o mesmo objeto, qualquer que seja. Contanto que você possa conectar / compilar / testar e ainda funcione.

Isso desacopla as coisas para que você possa adicionar novos caches sem precisar entrar e modificar algum objeto Uber-Cache. YMMV. IANAL. ETC.


1

Um pouco atrasado para a festa, mas de qualquer maneira.

Singleton é uma ferramenta em uma caixa de ferramentas, como qualquer outra coisa. Espero que você tenha mais na sua caixa de ferramentas do que apenas um martelo.

Considere isto:

public void DoSomething()
{
    MySingleton.Instance.Work();
}

vs

public void DoSomething(MySingleton singleton)
{
    singleton.Work();
}
DoSomething(MySingleton.instance);

1º caso leva a acoplamento alto etc; O segundo caminho não tem problemas @Aaronaught está descrevendo, até onde eu sei. É tudo sobre como você o usa.


Discordo. Embora a segunda maneira seja "melhor" (menor acoplamento), ela ainda apresenta problemas resolvidos pelo DI. Você não deve confiar no consumidor de sua classe para fornecer a implementação de seus serviços - isso é melhor feito no construtor quando a classe é criada. Sua interface deve exigir apenas o mínimo necessário em termos de argumentos. Também há uma chance decente de que sua classe exija uma única instância para operar - novamente, contar com o consumidor para aplicar essa regra é arriscado e desnecessário.
AlexFoxGill

Às vezes, Di é um exagero para uma determinada tarefa. O método em um código de exemplo pode ser um construtor, mas não necessariamente - sem olhar um exemplo concreto, é um argumento discutível. Além disso, o DoSomething poderia levar ISomething, e MySingleton poderia estar implementando essa interface - ok, isso não está em uma amostra ... mas é apenas uma amostra.
precisa

1

Peça a cada tela que apareça no Manager em seu construtor.

Ao iniciar seu aplicativo, você cria uma instância do gerente e a repassa.

Isso se chama Inversão de controle e permite que você troque o controlador quando a configuração for alterada e nos testes. Além disso, você pode executar várias instâncias do aplicativo ou partes dele em paralelo (bom para testar!). Por fim, seu gerente morrerá com seu próprio objeto (a classe de inicialização).

Portanto, estruture seu aplicativo como uma árvore, onde as coisas acima possuem tudo o que é usado abaixo delas. Não implemente um aplicativo como uma malha, onde todos conhecem todos e se encontram através de métodos globais.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.