ti-enxame.com

Singleton: Como deve ser usado?

Edit: De outra pergunta eu forneci uma resposta que tem links para um monte de perguntas/respostas sobre singletons: Mais informações sobre singletons aqui:

Então eu li o tópico Singletons: bom design ou uma muleta?
E a discussão ainda continua.

Eu vejo singletons como um padrão de design (bom e ruim).

O problema com o Singleton não é o padrão, mas sim os usuários (desculpe, todo mundo). Todo mundo e seu pai acham que podem implementar um corretamente (e das muitas entrevistas que fiz, a maioria das pessoas não consegue). Também porque todos pensam que podem implementar um Singleton correto, eles abusam do Pattern e o usam em situações que não são apropriadas (substituindo variáveis ​​globais por Singletons!).

Portanto, as principais questões que precisam ser respondidas são:

  • Quando você deve usar um Singleton
  • Como você implementa um singleton corretamente?

Minha esperança para este artigo é que possamos coletar juntos em um único lugar (ao invés de ter que pesquisar no Google e em vários sites) uma fonte autorizada de quando (e como) usar um Singleton corretamente. Também apropriado seria uma lista de anti-usos e implementações ruins comuns explicando por que eles não conseguem trabalhar e para boas implementações suas fraquezas.


Então pegue a bola rolando:
Eu vou levantar a mão e dizer que é isso que eu uso, mas provavelmente tem problemas.
Eu gosto de "Scott Myers" lidando com o assunto em seus livros "Effective C++"

Boas Situações para usar Singletons (não muitos):

  • Estruturas de registro
  • Conjuntos de reciclagem de threads
/*
 * C++ Singleton
 * Limitation: Single Threaded Design
 * See: http://www.aristeia.com/Papers/DDJ_Jul_Aug_2004_revised.pdf
 *      For problems associated with locking in multi threaded applications
 *
 * Limitation:
 * If you use this Singleton (A) within a destructor of another Singleton (B)
 * This Singleton (A) must be fully constructed before the constructor of (B)
 * is called.
 */
class MySingleton
{
    private:
        // Private Constructor
        MySingleton();
        // Stop the compiler generating methods of copy the object
        MySingleton(MySingleton const& copy);            // Not Implemented
        MySingleton& operator=(MySingleton const& copy); // Not Implemented

    public:
        static MySingleton& getInstance()
        {
            // The only instance
            // Guaranteed to be lazy initialized
            // Guaranteed that it will be destroyed correctly
            static MySingleton instance;
            return instance;
        }
};

ESTÁ BEM. Permite obter algumas críticas e outras implementações em conjunto.
:-)

288
Martin York

Todos vocês estão errados. Leia a pergunta. Responda:

Use um Singleton se:

  • Você precisa ter um e apenas um objeto de um tipo no sistema

Não use um Singleton se:

  • Você quer economizar memória
  • Você quer tentar algo novo
  • Você quer mostrar o quanto você sabe
  • Porque todo mundo está fazendo isso (veja programador de cultos de carga na wikipedia)
  • Nos widgets da interface do usuário
  • É suposto ser um cache
  • Em cordas
  • Em sessões
  • Eu posso ir o dia todo

Como criar o melhor singleton:

  • Quanto menor, melhor. Eu sou um minimalista
  • Certifique-se de que é thread-safe
  • Certifique-se de que nunca é nulo
  • Certifique-se de que é criado apenas uma vez
  • Lazy ou inicialização do sistema? Até as suas necessidades
  • Às vezes, o sistema operacional ou a JVM cria singletons para você (por exemplo, em Java cada definição de classe é um singleton)
  • Forneça um destruidor ou de alguma forma descubra como descartar recursos
  • Use pouca memória
165
Javaxpert

Singletons dão a você a habilidade de combinar duas características ruins em uma classe. Isso está errado em praticamente todos os sentidos.

Um singleton te dá:

  1. Acesso global a um objeto e
  2. Uma garantia de que não mais do que um objeto deste tipo pode ser criado

O número um é simples. Globals geralmente são ruins. Nunca devemos tornar objetos globalmente acessíveis a menos que nós realmente precisem.

O número dois pode parecer que faz sentido, mas vamos pensar sobre isso. Quando foi a última vez que você * acidentalmente * criou um novo objeto em vez de fazer referência a um existente? Como isso é marcado como C++, vamos usar um exemplo dessa linguagem. Você costuma escrever acidentalmente

std::ostream os;
os << "hello world\n";

Quando você pretendia escrever

std::cout << "hello world\n";

Claro que não. Nós não precisamos de proteção contra esse erro, porque esse tipo de erro simplesmente não acontece. Se isso acontecer, a resposta correta é ir para casa e dormir por 12 a 20 horas e esperar que você se sinta melhor.

Se apenas um objeto for necessário, basta criar uma instância. Se um objeto deve ser globalmente acessível, torne-o global. Mas isso não significa que deve ser impossível criar outras instâncias dele.

A restrição "apenas uma instância é possível" realmente não nos protege contra possíveis erros. Mas ele faz tornar nosso código muito difícil de refatorar e manter. Porque muitas vezes descobrimos mais tarde que precisamos de mais de uma instância. Nós do temos mais de um banco de dados, nós do temos mais de um objeto de configuração, queremos vários loggers. Nossos testes de unidade podem ser capazes de criar e recriar esses objetos a cada teste, para dar um exemplo comum.

Assim, um singleton deve ser usado se, e somente se, precisarmos de ambos os traços que ele oferece: Se nós precisarmos de acesso global (o que é raro, porque globals são geralmente desencorajados) e we need para evitar que alguém de ever criando mais de uma instância de um classe (o que me parece uma questão de design). A única razão pela qual posso ver isso é se a criação de duas instâncias corromperia o estado do nosso aplicativo - provavelmente porque a classe contém vários membros estáticos ou bobagens semelhantes. Nesse caso, a resposta óbvia é consertar essa classe. Não deve depender de ser a única instância.

Se você precisar de acesso global a um objeto, torne-o global, como std::cout. Mas não restrinja o número de instâncias que podem ser criadas.

Se você absolutamente, positivamente, precisa restringir o número de instâncias de uma classe a apenas uma, e não há como a criação de uma segunda instância poder ser tratada com segurança e, em seguida, impor isso. Mas não o torne globalmente acessível também.

Se você precisa dos dois traços, então 1) faça um singleton, e 2) deixe-me saber o que você precisa para isso, porque eu estou tendo dificuldade em imaginar um caso desses.

71
jalf

O problema com singletons não é sua implementação. É que eles combinam dois conceitos diferentes, nenhum dos quais é obviamente desejável.

1) Singletons fornecem um mecanismo de acesso global a um objeto. Embora possam ser marginalmente mais seguros em termos de thread ou mais confiáveis ​​em linguagens sem uma ordem de inicialização bem definida, esse uso ainda é o equivalente moral de uma variável global. É uma variável global vestida com alguma sintaxe desajeitada (foo :: get_instance () em vez de g_foo, digamos), mas serve exatamente o mesmo propósito (um único objeto acessível em todo o programa) e tem as mesmas desvantagens.

2) Singletons impedem múltiplas instanciações de uma classe. É raro, IME, que esse tipo de recurso seja incluído em uma classe. Normalmente é uma coisa muito mais contextual; Muitas das coisas que são consideradas como um-e-somente-um são realmente apenas acontecer-para-ser-apenas-um. OMI, uma solução mais apropriada, é apenas criar uma única instância - até você perceber que precisa de mais de uma instância.

35
DrPizza

Uma coisa com padrões: não generalize. Eles têm todos os casos em que são úteis e quando falham.

Singleton pode ser desagradável quando você tem que teste o código. Você geralmente fica preso a uma instância da classe e pode escolher entre abrir uma porta no construtor ou algum método para redefinir o estado e assim por diante.

Outro problema é que o Singleton, na verdade, nada mais é do que uma variável global disfarçada. Quando você tem muito estado compartilhado global sobre o seu programa, as coisas tendem a voltar, todos nós sabemos disso.

Pode tornar rastreamento de dependência mais difícil. Quando tudo depende do seu Singleton, é mais difícil mudá-lo, dividir para dois, etc. Você geralmente fica preso a ele. Isso também dificulta a flexibilidade. Investigue alguns Injeção de Dependência framework para tentar aliviar este problema.

26
Paweł Hajdan

Singletons basicamente permitem que você tenha um estado global complexo em linguagens que, de outra forma, tornam difícil ou impossível ter variáveis ​​globais complexas.

Java em particular usa singletons como um substituto para variáveis ​​globais, já que tudo deve estar contido dentro de uma classe. O mais próximo das variáveis ​​globais são variáveis ​​estáticas públicas, que podem ser usadas como se fossem globais com import static

C++ tem variáveis ​​globais, mas a ordem em que os construtores de variáveis ​​de classe globais são invocados é indefinida. Como tal, um singleton permite que você adie a criação de uma variável global até a primeira vez que a variável é necessária.

Idiomas como Python e Ruby usam singletons muito pouco porque você pode usar variáveis ​​globais dentro de um módulo.

Então, quando é bom/ruim usar um singleton? Praticamente quando seria bom/ruim usar uma variável global.

12
Eli Courtwright
  • Como você implementa um singleton corretamente?

Há um problema que eu nunca vi mencionado, algo que eu encontrei em um trabalho anterior. Tínhamos singletons de C++ que eram compartilhados entre DLLs, e a mecânica usual de garantir uma única instância de uma classe simplesmente não funciona. O problema é que cada DLL obtém seu próprio conjunto de variáveis ​​estáticas, junto com o EXE. Se sua função get_instance for inline ou parte de uma biblioteca estática, cada DLL terminará com sua própria cópia do "singleton".

A solução é garantir que o código singleton seja definido apenas em um DLL ou EXE, ou crie um gerenciador de singleton com essas propriedades para parcelar instâncias.

6
Mark Ransom

Modern C++ Design por Alexandrescu tem um singleton genérico hermético, thread-safe.

Para meu valor de 2p, acho importante ter vidas definidas para seus singletons (quando é absolutamente necessário usá-los). Eu normalmente não deixo a função get() estática instanciar qualquer coisa e deixar a configuração e a destruição para alguma seção dedicada do aplicativo principal. Isso ajuda a destacar as dependências entre singletons - mas, como enfatizado acima, é melhor evitá-las, se possível.

6
tenpn

O primeiro exemplo não é thread-safe - se dois threads chamarem getInstance ao mesmo tempo, essa static será PITA. Alguma forma de mutex ajudaria.

5
Rob

Como outros notaram, as principais desvantagens dos singletons incluem a incapacidade de ampliá-las e a perda do poder de instanciar mais de uma instância, por ex. para fins de teste.

Alguns aspectos úteis dos singletons:

  1. instanciação preguiçosa ou adiantada
  2. útil para um objeto que requer configuração e/ou estado

No entanto, você não precisa usar um singleton para obter esses benefícios. Você pode escrever um objeto normal que realiza o trabalho e depois fazer com que as pessoas o acessem por meio de uma fábrica (um objeto separado). A fábrica pode se preocupar em apenas instanciar um e reutilizá-lo, etc., se necessário. Além disso, se você programar para uma interface em vez de uma classe concreta, a fábrica pode usar estratégias, ou seja, você pode alternar entre várias implementações da interface.

Finalmente, uma fábrica presta-se a tecnologias de injeção de dependência, como Spring etc.

4
lexh

Como um singleton permite que apenas uma instância seja criada, ele controla efetivamente a replicação da instância. por exemplo, você não precisaria de várias instâncias de uma pesquisa - um mapa de pesquisa morse, por exemplo, assim, encapsulando-o em uma classe singleton é apt. E só porque você tem uma única instância da classe não significa que você também está limitado no número de referências a essa instância. Você pode enfileirar chamadas (para evitar problemas de encadeamento) na instância e efetuar as alterações necessárias. Sim, a forma geral de um singleton é globalmente pública, você pode certamente modificar o design para criar um singleton com acesso mais restrito. Eu não me cansei disso antes, mas sei que é possível. E para todos aqueles que comentaram dizendo que o padrão singleton é totalmente malvado, você deve saber isto: sim, é mal se você não usá-lo adequadamente ou dentro dele confina funcionalidade efetiva e comportamento previsível: não GENERALIZE.

3
gogole

Singletons são úteis quando você tem um monte de código sendo executado quando você inicializa e objeto. Por exemplo, quando você usa iBatis quando configura um objeto de persistência, ele precisa ler todas as configurações, analisar os mapas, certificar-se de que está tudo correto, etc. antes de acessar seu código.

Se você fizesse isso toda vez, o desempenho seria muito degradado. Usá-lo em um singleton, você toma esse hit uma vez e, em seguida, todas as chamadas subseqüentes não têm que fazê-lo.

3
Brian

A maioria das pessoas usa singletons quando estão tentando se sentir bem em usar uma variável global. Há usos legítimos, mas na maioria das vezes quando as pessoas os utilizam, o fato de que só pode haver uma instância é apenas um fato trivial comparado ao fato de ser globalmente acessível.

3
Brad Barker

A queda real dos Singletons é que eles quebram a herança. Você não pode derivar uma nova classe para fornecer funcionalidade estendida a menos que tenha acesso ao código em que o Singleton é referenciado. Então, além do fato de o Singleton tornar seu código fortemente acoplado (corrigível por um Strategy Pattern ... também conhecido como Dependency Injection) ele também impedirá que você feche seções do código da revisão (bibliotecas compartilhadas).

Portanto, mesmo os exemplos de registradores ou conjuntos de encadeamentos são inválidos e devem ser substituídos por Estratégias.

3
ZebZiggle

Mas quando eu preciso de algo como um Singleton, muitas vezes acabo usando um contador de Schwarz para instanciá-lo.

2
Matt Cruikshank

Eu uso Singletons como um teste de entrevista.

Quando peço a um desenvolvedor para nomear alguns padrões de design, se tudo o que eles podem nomear é Singleton, eles não são contratados.

1
Matt Cruikshank

Abaixo está a melhor abordagem para implementar um padrão singleton seguro de thread com desalocando a memória no próprio destruidor. Mas acho que o destruidor deve ser opcional porque a instância singleton será destruída automaticamente quando o programa terminar:

#include<iostream>
#include<mutex>

using namespace std;
std::mutex mtx;

class MySingleton{
private:
    static MySingleton * singletonInstance;
    MySingleton();
    ~MySingleton();
public:
    static MySingleton* GetInstance();
    MySingleton(const MySingleton&) = delete;
    const MySingleton& operator=(const MySingleton&) = delete;
    MySingleton(MySingleton&& other) noexcept = delete;
    MySingleton& operator=(MySingleton&& other) noexcept = delete;
};

MySingleton* MySingleton::singletonInstance = nullptr;
MySingleton::MySingleton(){ };
MySingleton::~MySingleton(){
    delete singletonInstance;
};

MySingleton* MySingleton::GetInstance(){
    if (singletonInstance == NULL){
        std::lock_guard<std::mutex> lock(mtx);
        if (singletonInstance == NULL)
            singletonInstance = new MySingleton();
    }
    return singletonInstance;
}

Em relação às situações em que precisamos usar singleton classes pode ser- Se quisermos manter o estado da instância durante a execução do programa Se estamos envolvidos em escrever em log de execução de um aplicativo onde apenas uma instância do arquivo precisa ser usado .... e assim por diante. Será apreciável se alguém puder sugerir otimização em meu código acima.

1
A. Gupta

O padrão singleton de Meyers funciona bem o suficiente na maior parte do tempo e, nas ocasiões em que o faz, não é necessariamente necessário procurar algo melhor. Contanto que o construtor nunca jogue e não haja dependências entre singletons.

Um singleton é uma implementação para um objeto acessível globalmente (GAO a partir de agora), embora nem todos os GAOs sejam singletons.

Os registradores em si não devem ser singletons, mas os meios para log devem idealmente ser acessíveis globalmente, para separar onde a mensagem de log está sendo gerada de onde ou como ela é registrada.

A avaliação preguiçosa/lenta é um conceito diferente e o singleton geralmente também implementa isso. Ele vem com muitos dos seus próprios problemas, em particular segurança de threads e problemas se falhar com exceções, de modo que o que parecia ser uma boa ideia na época não é tão bom assim. (Um pouco como COW implementação em seqüências de caracteres).

Com isso em mente, os GOAs podem ser inicializados assim:

namespace {

T1 * pt1 = NULL;
T2 * pt2 = NULL;
T3 * pt3 = NULL;
T4 * pt4 = NULL;

}

int main( int argc, char* argv[])
{
   T1 t1(args1);
   T2 t2(args2);
   T3 t3(args3);
   T4 t4(args4);

   pt1 = &t1;
   pt2 = &t2;
   pt3 = &t3;
   pt4 = &t4;

   dostuff();

}

T1& getT1()
{
   return *pt1;
}

T2& getT2()
{
   return *pt2;
}

T3& getT3()
{
  return *pt3;
}

T4& getT4()
{
  return *pt4;
}

Ele não precisa ser feito tão grosseiramente quanto isso, e claramente em uma biblioteca carregada que contém objetos você provavelmente quer algum outro mecanismo para gerenciar sua vida útil. (Colocá-los em um objeto que você recebe quando você carrega a biblioteca).

Quanto a quando eu uso singletons? Eu usei-os para 2 coisas - Uma tabela singleton que indica quais bibliotecas foram carregadas com dlopen - Um manipulador de mensagens que os registradores podem se inscrever e para o qual você pode enviar mensagens. Obrigatório especificamente para manipuladores de sinal.

0
CashCow

Se você é quem criou o singleton e quem o usa, não o faça como singleton (não faz sentido, porque você pode controlar a singularidade do objeto sem torná-lo singleton), mas faz sentido quando você é um desenvolvedor de um biblioteca e você deseja fornecer apenas um objeto para seus usuários (nesse caso, você é quem criou o singleton, mas você não é o usuário).

Singletons são objetos, então use-os como objetos, muitas pessoas acessam singletons diretamente através da chamada do método que retorna, mas isso é prejudicial porque você está fazendo o seu código sabe que objeto é singleton, eu prefiro usar singletons como objetos, eu os passo através do construtor e eu os uso como objetos ordinários, dessa forma, o seu código não sabe se esses objetos são singletons ou não e isso deixa as dependências mais claras e ajuda um pouco na refatoração ...

0
La VloZ Merrill

Anti-uso:

Um grande problema com o uso excessivo de singletons é que o padrão impede a fácil extensão e a troca de implementações alternativas. O nome da classe é codificado sempre que o singleton é usado.

0
Adam Franco

Eu os acho úteis quando eu tenho uma classe que encapsula muita memória. Por exemplo, em um jogo recente em que estive trabalhando, tenho uma classe de mapa de influência que contém uma coleção de matrizes muito grandes de memória contígua. Eu quero que todos alocados na inicialização, tudo liberado no desligamento e eu definitivamente quero apenas uma cópia do mesmo. Eu também tenho que acessá-lo de muitos lugares. Acho que o padrão singleton é muito útil nesse caso.

Tenho certeza de que existem outras soluções, mas acho isso muito útil e fácil de implementar.

0
Michael Avraamides

Eu acho que esta é a versão mais robusta para c #:

using System;
using System.Collections;
using System.Threading;

namespace DoFactory.GangOfFour.Singleton.RealWorld
{

  // MainApp test application

  class MainApp
  {
    static void Main()
    {
      LoadBalancer b1 = LoadBalancer.GetLoadBalancer();
      LoadBalancer b2 = LoadBalancer.GetLoadBalancer();
      LoadBalancer b3 = LoadBalancer.GetLoadBalancer();
      LoadBalancer b4 = LoadBalancer.GetLoadBalancer();

      // Same instance?
      if (b1 == b2 && b2 == b3 && b3 == b4)
      {
        Console.WriteLine("Same instance\n");
      }

      // All are the same instance -- use b1 arbitrarily
      // Load balance 15 server requests
      for (int i = 0; i < 15; i++)
      {
        Console.WriteLine(b1.Server);
      }

      // Wait for user
      Console.Read();    
    }
  }

  // "Singleton"

  class LoadBalancer
  {
    private static LoadBalancer instance;
    private ArrayList servers = new ArrayList();

    private Random random = new Random();

    // Lock synchronization object
    private static object syncLock = new object();

    // Constructor (protected)
    protected LoadBalancer()
    {
      // List of available servers
      servers.Add("ServerI");
      servers.Add("ServerII");
      servers.Add("ServerIII");
      servers.Add("ServerIV");
      servers.Add("ServerV");
    }

    public static LoadBalancer GetLoadBalancer()
    {
      // Support multithreaded applications through
      // 'Double checked locking' pattern which (once
      // the instance exists) avoids locking each
      // time the method is invoked
      if (instance == null)
      {
        lock (syncLock)
        {
          if (instance == null)
          {
            instance = new LoadBalancer();
          }
        }
      }

      return instance;
    }

    // Simple, but effective random load balancer

    public string Server
    {
      get
      {
        int r = random.Next(servers.Count);
        return servers[r].ToString();
      }
    }
  }
}

Aqui está a versão . NET otimizada :

using System;
using System.Collections;

namespace DoFactory.GangOfFour.Singleton.NETOptimized
{

  // MainApp test application

  class MainApp
  {

    static void Main()
    {
      LoadBalancer b1 = LoadBalancer.GetLoadBalancer();
      LoadBalancer b2 = LoadBalancer.GetLoadBalancer();
      LoadBalancer b3 = LoadBalancer.GetLoadBalancer();
      LoadBalancer b4 = LoadBalancer.GetLoadBalancer();

      // Confirm these are the same instance
      if (b1 == b2 && b2 == b3 && b3 == b4)
      {
        Console.WriteLine("Same instance\n");
      }

      // All are the same instance -- use b1 arbitrarily
      // Load balance 15 requests for a server
      for (int i = 0; i < 15; i++)
      {
        Console.WriteLine(b1.Server);
      }

      // Wait for user
      Console.Read();    
    }
  }

  // Singleton

  sealed class LoadBalancer
  {
    // Static members are lazily initialized.
    // .NET guarantees thread safety for static initialization
    private static readonly LoadBalancer instance =
      new LoadBalancer();

    private ArrayList servers = new ArrayList();
    private Random random = new Random();

    // Note: constructor is private.
    private LoadBalancer()
    {
      // List of available servers
      servers.Add("ServerI");
      servers.Add("ServerII");
      servers.Add("ServerIII");
      servers.Add("ServerIV");
      servers.Add("ServerV");
    }

    public static LoadBalancer GetLoadBalancer()
    {
      return instance;
    }

    // Simple, but effective load balancer
    public string Server
    {
      get
      {
        int r = random.Next(servers.Count);
        return servers[r].ToString();
      }
    }
  }
}

Você pode encontrar este padrão em dotfactory.com .

0
artur02

Eu ainda não entendo porque um singleton tem que ser global.

Eu estava indo para produzir um singleton onde eu escondi um banco de dados dentro da classe como uma variável estática constante privada e fazer funções de classe que utilizam o banco de dados sem nunca expor o banco de dados para o usuário.

Não vejo por que essa funcionalidade seria ruim.

0
Zachary Kraus