O que há de alternativo para o solteirão

114

Temos uma classe que contém informações de configuração do aplicativo. Costumava ser um único. Após alguma revisão arquitetônica, fomos orientados a remover o singleton. Vimos alguns benefícios de não usar o singleton no teste de unidade porque podemos testar diferentes configurações ao mesmo tempo.

Sem o singleton, temos que passar a instância por toda parte em nosso código. Está ficando tão confuso, então escrevemos um invólucro de singleton. Agora que estamos portando o mesmo código para PHP e .NET, gostaria de saber se há um padrão melhor que podemos usar para o objeto de configuração.

John Mill
fonte

Respostas:

131

O blog do Google Testing tem uma série de entradas sobre como evitar Singleton (a fim de criar código testável). Talvez isso possa te ajudar:

O último artigo explica em detalhes como mover a criação de novos objetos para uma fábrica, para evitar o uso de singletons. Vale a pena ler com certeza.

Resumindo, transferimos todos os novos operadores para uma fábrica. Agrupamos todos os objetos de vida útil semelhante em uma única fábrica.

FrankS
fonte
3
*** Usando injeção de dependência para evitar solteiros
Justin
Esses artigos são tão bons quanto os Padrões de programação C ++ do Google!
2
Bem, na verdade não. O conselho 'Não use métodos estáticos' vai direto contra o princípio de interface mínima de Scott Meyers / Herb Sutters, por exemplo. Existem conselhos úteis, mas carecem da contribuição de várias mentes.
Matthieu M.
@FrankS por que você mudou a ordem dos links? Estava em boa ordem cronológica no início.
cregox
@Cawas na verdade não faço ideia, isso foi há mais de quatro anos, então eu acho que tinha alguns motivos para isso naquela época :-)
FrankS
15

A melhor maneira é usar um padrão de fábrica. Quando você constrói uma nova instância de sua classe (na fábrica), você pode inserir os dados "globais" no objeto recém-construído, seja como uma referência a uma única instância (que você armazena na classe de fábrica) ou copiando os dados relevantes dados no novo objeto.

Todos os seus objetos conterão os dados que costumavam viver no singleton. Não acho que haja muita diferença no geral, mas pode tornar seu código mais fácil de ler.

gbjbaanb
fonte
1
Não concordo com a afirmação de "melhor maneira", mas +1 para uma boa alternativa.
tylermac de
O problema com essa abordagem é que cada novo objeto contém (ou faz referência) o que pode ser potencialmente uma grande quantidade de dados. var_dump () em qualquer um desses objetos contendo gob resulta muito rapidamente em listas enormes salpicadas livremente com avisos de recursão . É horrível, não pode ser terrivelmente eficiente e faz as coisas parecerem confusas. No entanto, pessoalmente não encontrei uma maneira melhor. Eu curvei o método "fábrica" ​​para usar __construct () para fazer referência a um global. Parece que tudo está dobrado para trás, no entanto, para evitar o temido Singleton ...
FYA
2
@EastGhostCom: podemos também usar o singleton e parar de tentar tornar as coisas difíceis para nós mesmos :)
gbjbaanb
5

Posso estar afirmando o óbvio aqui, mas há uma razão pela qual você não pode usar uma estrutura de injeção de dependência como Spring ou Guice ? (Eu acredito que o Spring também está disponível para .NET agora).

Dessa forma, a estrutura pode conter uma única cópia dos objetos de configuração e seus beans (serviços, DAOs, qualquer que seja) não precisam se preocupar em procurá-los.

Esta é a abordagem que costumo adotar!


fonte
4

Se você usa Spring Framework , pode apenas criar um bean regular. Por padrão (ou se você definir explicitamente scope="singleton") apenas uma instância do bean é criada e essa instância é retornada toda vez que o bean é usado em uma dependência ou recuperado via getBean().

Você obtém a vantagem da instância única, sem o acoplamento do padrão Singleton.

Gene Gotimer
fonte
3
Que ironia - use (singleton) Spring beans para substituir seu singleton ...
Zack Macomber
4

A alternativa é passar o que você precisa em vez de pedir coisas a um objeto.

Koen
fonte
4

não acumule responsabilidades para um único objeto de configuração, pois ele terminará em um objeto muito grande que é difícil de entender e frágil.

Por exemplo, se você precisar de outro parâmetro para uma classe específica, você altera o Configurationobjeto e, a seguir, recompila todas as classes que o utilizam. Isso é um tanto problemático.

Tente refatorar seu código para evitar um Configurationobjeto comum, global e grande . Passe apenas os parâmetros necessários para as classes de cliente:

class Server {

    int port;

    Server(Configuration config) {
        this.port = config.getServerPort();
    } 

}

deve ser refatorado para:

 class Server {

    public Server(int port) {
       this.port = port;
    }
 }

uma estrutura de injeção de dependência ajudará muito aqui, mas não é estritamente necessária.

dfa
fonte
Sim, é um ponto muito bom. Eu já fiz isso antes. Meu grande objeto de configuração era implementar interfaces como MailServiceConf, ServerConf ... e o framework de injeção de dependência passa a configuração para as classes para que minhas classes não dependessem do grande objeto Configuration.
caltuntas
1

Você pode realizar o mesmo comportamento do singleton usando métodos estáticos. Steve Yegge explica isso muito bem neste post.

Youssef
fonte
Na verdade, o artigo é muito bom e não diz que você deve usar métodos estáticos. Em vez disso, ele afirma que os métodos estáticos também são singletons e, no final, recomenda o uso do padrão de método de fábrica: "Concluirei dizendo que se você ainda sentir necessidade de usar objetos Singleton, considere usar o padrão Factory Method. ... "
FrankS
0

É possível uma classe que contenha apenas métodos e campos estáticos? Não tenho certeza de qual é a sua situação, mas pode valer a pena investigar.

Thomas Owens
fonte
1
Se a classe for sem estado, deve ser uma classe estática.
AlbertoPL
1
Ele está em C ++ - o padrão é conhecido como Monoestado.
0

Depende de quais ferramentas / frameworks etc. estão sendo usados. Com as ferramentas de injeção de dependência / ioc, muitas vezes ainda é possível obter desempenho / otimizações singleton fazendo com que o contêiner di / ioc use o comportamento singleton para a classe necessária - (como uma interface IConfigSettings) criando apenas uma instância da classe. Isso ainda pode ser substituído por teste

Como alternativa, pode-se usar uma fábrica para criar a classe e retornar a mesma instância cada vez que você solicitá-la - mas, para testar, pode retornar uma versão fragmentada / simulada

saret
fonte
0

Reveja a possibilidade de fazer configuração como interface de retorno de chamada. Portanto, seu código sensível à configuração ficará:

MyReuseCode.Configure(IConfiguration)

O código de inicialização do sistema parecerá:

Library.init(MyIConfigurationImpl)
Orvalhado
fonte
0

Você poderia usar uma estrutura de injeção de dependência para aliviar a dor de passar no objeto de configuração. Um decente é o ninject, que tem a vantagem de usar código em vez de xml.

Colin Gravill
fonte
0

Talvez não seja muito limpo também, mas você poderia passar os bits de informação que deseja alterar para o método que cria o singleton - em vez de usar

public static Singleton getInstance() {
    if(singleton != null)
        createSingleton();
        return singleton;
    }
}

você pode chamar createSingleton(Information info)diretamente na inicialização do aplicativo (e nos métodos de configuração dos testes de unidade).

Roland Ewald
fonte
-1

Singletons não são maus, mas o padrão de design é defeituoso. Eu tenho uma classe que desejo apenas criar uma única instância durante o tempo de execução, mas quero criar várias instâncias isoladas durante o teste de unidade para garantir resultados determinísticos.

DI usando Spring, etc, é uma opção muito boa, mas não a única opção.

codematrix
fonte