Alternativas ao padrão singleton


27

Eu li opiniões diferentes sobre o padrão singleton. Alguns sustentam que deve ser evitado a todo custo e outros que podem ser úteis em determinadas situações.

Uma situação em que eu uso singletons é quando eu preciso de uma factory (digamos, um objeto f do tipo F) para criar objetos de uma determinada classe A. A factory é criada uma vez usando alguns parâmetros de configuração e é usada sempre que um objeto de o tipo A é instanciado. Portanto, toda parte do código que deseja instanciar A busca o singleton f e cria a nova instância, por exemplo

F& f                   = F::instance();
boost::shared_ptr<A> a = f.createA();

Então, o meu cenário geral é que

  1. Preciso de apenas uma instância de uma classe por motivos de otimização (não preciso de vários objetos de fábrica) ou para compartilhar o estado comum (por exemplo, a fábrica sabe quantas instâncias de A ainda podem ser criadas)
  2. Eu preciso de uma maneira de ter acesso a esta instância f de F em diferentes locais do código.

Não estou interessado em discutir se esse padrão é bom ou ruim, mas supondo que eu queira evitar o uso de um singleton, que outro padrão posso usar?

As idéias que tive foram: (1) obter o objeto de fábrica de um registro ou (2) criar a fábrica em algum momento durante a inicialização do programa e depois passar a fábrica como um parâmetro.

Na solução (1), o próprio registro é um singleton, então acabei de mudar o problema de não usar um singleton da fábrica para o registro.

No caso (2), preciso de alguma fonte inicial (objeto) da qual o objeto factory vem, por isso tenho medo de voltar a usar outro singleton (o objeto que fornece minha instância de fábrica). Seguindo essa cadeia de singletons, talvez eu possa reduzir o problema a um singleton (o aplicativo inteiro) pelo qual todos os outros singletons são gerenciados direta ou indiretamente.

Essa última opção (usando um singleton inicial que cria todos os outros objetos exclusivos e injeta todos os outros singletons nos lugares certos) seria uma solução aceitável? Essa é a solução sugerida implicitamente quando alguém aconselha não usar singletons ou quais são outras soluções, por exemplo, no exemplo ilustrado acima?

EDITAR

Como acho que o ponto da minha pergunta foi mal interpretado por alguns, aqui estão mais algumas informações. Conforme explicado aqui , por exemplo , a palavra singleton pode indicar (a) uma classe com um objeto de instância única e (b) um padrão de design usado para criar e acessar esse objeto.

Para tornar as coisas mais claras, vamos usar o termo objeto único para (a) e padrão singleton para (b). Então, eu sei o que são o padrão singleton e a injeção de dependência (BTW, ultimamente tenho usado DI intensamente para remover instâncias do padrão singleton de algum código no qual estou trabalhando).

O que quero dizer é que, a menos que o gráfico inteiro de objetos seja instanciado a partir de um único objeto vivendo na pilha do método principal, sempre haverá a necessidade de acessar alguns objetos exclusivos através do padrão singleton.

Minha pergunta é se a criação e a fiação completas de gráficos de objetos dependem do método principal (por exemplo, através de uma poderosa estrutura DI que não usa o próprio padrão) é a única solução livre de padrão singleton .


8
Dependency Injection ...
Falcon

1
@ Falcon: Injeção de Dependência ... o que? O DI não faz nada para resolver esse problema, embora muitas vezes forneça uma maneira útil de ocultá-lo.
PDR

@Falcon você quer dizer IoC container? Isso permitirá que você resolva dependências em uma única linha. Por exemplo, Dependency.Resolve <IFoo> (); ou Dependency.Resolve <IFoo> () .DoSomething ();
CodeART

Esta é uma pergunta bastante antiga e já foi respondida. Eu ainda acho que seria bom vincular este: stackoverflow.com/questions/162042/…
TheSilverBullet 5/12/12 /

Respostas:


7

Sua segunda opção é um bom caminho a percorrer - é um tipo de injeção de dependência, que é o padrão usado para compartilhar o estado em seu programa quando você deseja evitar singletons e variáveis ​​globais.

Você não pode contornar o fato de que algo precisa criar sua fábrica. Se esse é o aplicativo, que assim seja. O ponto importante é que sua fábrica não deve se importar com o objeto que o criou e os objetos que recebem a fábrica não devem depender de onde a fábrica veio. Não faça com que seus objetos obtenham um ponteiro para o aplicativo singleton e solicitem a fábrica; faça com que seu aplicativo crie a fábrica e dê-o aos objetos que precisarão dele.


17

O objetivo de um singleton é impor que apenas uma instância possa existir em um determinado domínio. Isso significa que um singleton é útil se você tiver motivos fortes para impor o comportamento de singleton; na prática, esse raramente é o caso, e o multiprocessamento e o multiencadeamento certamente obscurecem o significado de 'exclusivo' - é uma instância por máquina, por processo, por encadeamento, por solicitação? E sua implementação de singleton cuida das condições da corrida?

Em vez de singleton, prefiro usar um dos seguintes:

  • instâncias locais de vida curta, por exemplo, para uma fábrica: as classes típicas de fábrica têm quantidades mínimas de estado, se houver, e não há motivo real para mantê-las vivas depois de terem cumprido seu objetivo; a sobrecarga de criar e excluir classes não é motivo de preocupação em 99% de todos os cenários do mundo real
  • passando uma instância, por exemplo, para um gerenciador de recursos: eles precisam durar muito, porque carregar recursos é caro e você deseja mantê-los na memória, mas não há absolutamente nenhuma razão para impedir a criação de outras instâncias - quem sabe, talvez faça sentido ter um segundo gerenciador de recursos daqui a alguns meses ...

A razão é que um singleton é um estado global disfarçado, o que significa que ele apresenta um alto grau de acoplamento em todo o aplicativo - qualquer parte do seu código pode pegar a instância singleton de qualquer lugar com o mínimo esforço. Se você usar objetos locais ou passar instâncias, terá muito mais controle e poderá manter seus escopos pequenos e suas dependências estreitas.


3
Essa é uma resposta muito boa, mas eu realmente não estava perguntando por que deveria ou não usar o padrão singleton. Eu sei os problemas que podemos ter com o estado global. De qualquer forma, o tópico da pergunta era como ter objetos únicos e ainda uma implementação livre de padrão de singleton.
Giorgio

3

A maioria das pessoas (incluindo você) não entende completamente qual é o padrão Singleton. O padrão Singleton significa apenas que existe uma instância de uma classe e existe algum mecanismo para o código em todo o aplicativo obter uma referência a essa instância.

No livro do GoF, o método estático de fábrica que retorna uma referência a um campo estático era apenas um exemplo de como esse mecanismo seria e um com graves inconvenientes. Infelizmente, todos e seus cães se prenderam a esse mecanismo e pensaram que era disso que Singleton se tratava.

As alternativas que você cita também são singletons, apenas com um mecanismo diferente para obter a referência. (2) resulta claramente em muita repercussão, a menos que você precise da referência em apenas alguns lugares próximos à raiz da pilha de chamadas. (1) soa como uma estrutura de injeção de dependência bruta - então por que não usar uma?

A vantagem é que as estruturas DI existentes são flexíveis, poderosas e bem testadas. Eles podem fazer muito mais do que apenas gerenciar Singletons. No entanto, para usar plenamente seus recursos, eles funcionam melhor se você estruturar seu aplicativo de uma certa maneira, o que nem sempre é possível: idealmente, existem objetos centrais que são adquiridos através da estrutura DI e têm todas as suas dependências preenchidas transitivamente e são então executado.

Edit: Em última análise, tudo depende do método principal de qualquer maneira. Mas você está certo: a única maneira de evitar o uso de global / estático completamente é configurando tudo a partir do método principal. Observe que o DI é mais popular em ambientes de servidor em que o método principal é opaco e configura o servidor e o código do aplicativo consiste basicamente em retornos de chamada que são instanciados e chamados pelo código do servidor. Mas a maioria das estruturas de DI também permite acesso direto ao registro central, por exemplo, ApplicationContext da Spring, para "casos especiais".

Então, basicamente, a melhor coisa que as pessoas surgiram até agora é uma combinação inteligente das duas alternativas que você mencionou.


Você quer dizer que a injeção de dependência da idéia básica é basicamente a primeira abordagem que eu esboçamos acima (por exemplo, usando um registro), finalmente a idéia básica?
Giorgio

@Giorgio: DI sempre inclui esse registro, mas o ponto principal que o torna melhor é que, em vez de consultar o registro onde quer que você precise de um objeto, você tem os objetos centrais que são preenchidos de forma transitória, como escrevi acima.
Michael Borgwardt

@ Giorgio: É mais fácil entender com um exemplo concreto: digamos que você tenha um aplicativo Java EE. Sua lógica de front-end está em um método de ação de um Bean Gerenciado JSF. Quando o usuário pressiona um botão, o contêiner JSF cria uma instância do Managed Bean e chama o método de ação. Porém, antes disso, o componente DI analisa os campos da classe Managed Bean, e aqueles que possuem uma certa anotação são preenchidos com uma referência a um objeto Service conforme a configuração da DI, e esses objetos Service têm o mesmo que lhes acontece. . O método de ação pode chamar os serviços.
Michael Borgwardt

Algumas pessoas (inclusive você) não leem as perguntas com atenção e não entendem o que realmente está sendo solicitado.
Giorgio

1
@ Giorgio: nada na sua pergunta original indicava que você já estava familiarizado com a injeção de dependência. E veja a resposta atualizada.
Michael Borgwardt

3

Existem algumas alternativas:

Injeção de dependência

Todo objeto tem suas dependências transmitidas a ele quando foi criado. Normalmente, uma estrutura ou um pequeno número de classes de fábrica são responsáveis ​​por criar e conectar os objetos

Registro de Serviço

Cada objeto recebe um objeto de registro de serviço. Ele fornece métodos para solicitar vários objetos diferentes que fornecem serviços diferentes. A estrutura do Android usa esse padrão

Root Manager

Há um único objeto que é a raiz do gráfico de objetos. Seguindo os links desse objeto, qualquer outro objeto pode ser encontrado. O código tende a se parecer com:

GetSomeManager()->GetRootManager()->GetAnotherManager()->DoActualThingICareAbout()

Além da zombaria, que vantagem o DI tem sobre o uso de um singleton? Esta é uma pergunta que me incomoda há muito tempo. Quanto ao gerenciador de registro e raiz, eles não são implementados como um singleton ou via DI? (Na verdade, o contêiner IoC é um registro, né?)
Konrad Rudolph

1
@KonradRudolph, com DI, as dependências são explícitas e o objeto não faz suposições sobre como um determinado recurso é fornecido. Com singletons, as dependências são implícitas e todo uso pressupõe que só haverá um único objeto desse tipo.
Winston Ewert

@KonradRudolph, os outros métodos são implementados usando Singletons ou DI? Sim e não. No final, você precisa passar objetos ou armazenar objetos no estado global. Mas existem diferenças sutis na maneira como você faz isso. As três versões mencionadas acima evitam o uso do estado global; mas a maneira pela qual o código final obtém as referências é diferente.
Winston Ewert

O uso de singletons não assume nenhum objeto único se o singleton implementa uma interface (ou mesmo se não) e você passa a instância singleton para métodos, em vez de consultar Singleton::instanceem qualquer lugar no código do cliente.
Konrad Rudolph

@KonradRudolph, então você está realmente usando alguma abordagem híbrida Singleton / DI. Meu lado purista acha que você deveria adotar uma abordagem de DI pura. No entanto, a maioria dos problemas com o singleton não se aplica realmente a ele.
Winston Ewert

1

Se suas necessidades do singleton podem ser reduzidas a uma única função, por que não usar apenas uma simples função de fábrica? Funções globais (provavelmente um método estático da classe F no seu exemplo) são inerentemente singletons, com exclusividade imposta pelo compilador e vinculador.

class Factory
{
public:
    static Object* createObject(...);
};

Object* obj = Factory::createObject(...);

É certo que isso ocorre quando a operação do singleton não pode ser reduzida a uma única chamada de função, embora talvez um pequeno conjunto de funções relacionadas possa ajudá-lo.

Tudo isso dito, os itens 1 e 2 da sua pergunta deixam claro que você realmente quer apenas algo. Dependendo da sua definição do padrão singleton, você já o está usando ou está muito próximo. Eu não acho que você possa ter um de algo sem que seja um singleton ou pelo menos muito próximo de um. É muito perto do significado de singleton.

Como você sugere, em algum momento você precisa ter um de algo, talvez o problema não esteja em ter uma única instância de algo, mas em tomar medidas para evitar (ou pelo menos desencorajar ou minimizar) o abuso. Mover o máximo de estado para fora do "singleton" e para os parâmetros possível é um bom começo. A restrição da interface ao singleton também ajuda, pois há menos oportunidades de abuso dessa maneira. Às vezes, você apenas precisa absorver e tornar o singleton muito robusto, como o heap ou o sistema de arquivos.


4
Pessoalmente, vejo isso apenas como uma implementação diferente do padrão singleton. Nesse caso, a instância singleton é a própria classe. Especificamente: possui as mesmas desvantagens de um singleton "real".
Joachim Sauer

@ Randall Cook: Acho que sua resposta capta meu ponto de vista. Eu sempre leio que o singleton é muito, muito ruim e deve ser evitado a todo custo. Concordo com isso, mas, por outro lado, acho que é tecnicamente impossível sempre evitá-lo. Quando você precisa de "um de algo", é necessário criá-lo em algum lugar e acessá-lo de alguma forma.
Giorgio

1

Se você estiver usando uma linguagem multiparadigmática como C ++ ou Python, uma alternativa a uma classe singleton é um conjunto de funções / variáveis ​​envolvidas em um espaço para nome.

Conceitualmente, um arquivo C ++ com variáveis ​​globais livres, funções globais livres e variáveis ​​estáticas usadas para ocultar informações, todas agrupadas em um espaço para nome, oferece quase o mesmo efeito que uma "classe" singleton.

Ele só quebra se você deseja herança. Eu já vi muitos singletons que seriam melhores assim.


0

Encapsulamento de singletons

Cenário do caso. Seu aplicativo é orientado a objetos e requer 3 ou 4 singletons específicos, mesmo se você tiver mais aulas.

Antes do exemplo (C ++ como pseudocódigo):

// network info
class Session {
  // ...
};

// current user connection info
class CurrentUser {
  // ...
};

// configuration upon user
class UserConfiguration {
  // ...
};

// configuration upon P.C.,
// example: monitor resolution
class TerminalConfiguration {
  // ...
};

Uma maneira é remover parcialmente alguns singletons (globais), encapsulando todos eles em um singleton único, que tem como membros os outros singletons.

Dessa forma, em vez de "vários singletons, abordagem versus versus nenhum singleton, abordagem", temos o "encapsular todos os singletons em uma abordagem única".

Após o exemplo (C ++ como pseudocódigo):

// network info
class NetworkInfoClass {
  // ...
};

// current user connection info
class CurrentUserClass {
  // ...
};

// configuration upon user
// favorite options menues
class UserConfigurationClass {
  // ...
};

// configuration upon P.C.,
// example: monitor resolution
class TerminalConfigurationClass {
  // ...
};

// this class is instantiated as a singleton
class SessionClass {
  public: NetworkInfoClass NetworkInfo;
  public: CurrentUserClass CurrentUser;
  public: UserConfigurationClass UserConfiguration;
  public: TerminalConfigurationClass TerminalConfiguration;

  public: static SessionClass Instance();
};

Observe que os exemplos são mais parecidos com pseudocódigo e ignora pequenos bugs ou erros de sintaxe e considere a solução para a pergunta.

Também há outra coisa a considerar, porque a linguagem de programação usada pode afetar como implementar sua implementação singleton ou não singleton.

Felicidades.


0

Seus requisitos originais:

Então, o meu cenário geral é que

  1. Preciso de apenas uma instância de uma classe por motivos de otimização (não preciso de vários objetos de fábrica) ou para compartilhar o estado comum (por exemplo, a fábrica sabe quantas instâncias de A ainda pode criar)
  2. Eu preciso de uma maneira de ter acesso a esta instância f de F em diferentes locais do código.

Não alinhe com a definição de um singleton (e com o que você se refere posteriormente). Do GoF (minha versão é 1995) página 127:

Garanta que uma classe tenha apenas uma instância e forneça um ponto de acesso global a ela.

Se você precisar apenas de uma instância, isso não impede você de fazer mais.

Se você deseja uma única instância globalmente acessível, crie uma única instância globalmente acessível . Não há necessidade de ter um padrão nomeado para tudo o que você faz. E sim, instâncias únicas acessíveis globalmente geralmente são ruins. Dar-lhes um nome não os torna menos ruins .

Para evitar a acessibilidade global, as abordagens comuns "criar uma instância e transmiti-la" ou "fazer com que o proprietário de dois objetos os cole" são bem-vindas.


0

Que tal usar o contêiner IoC? Com algum pensamento, você pode acabar com algo nas linhas de:

Dependency.Resolve<IFoo>(); 

Você precisaria especificar qual implementação IFoousar em uma inicialização, mas essa é uma configuração única que pode ser substituída facilmente mais tarde. A vida útil de uma instância resolvida pode ser normalmente controlada por um contêiner de IoC.

O método estático de singleton void pode ser substituído por:

Dependency.Resolve<IFoo>().DoSomething();

O método estático getter singleton pode ser substituído por:

var result = Dependency.Resolve<IFoo>().GetFoo();

O exemplo é relevante para o .NET, mas tenho certeza de que algo semelhante pode ser alcançado em outros idiomas.

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.