Fábrica estática vs fábrica como um singleton

24

Em alguns dos meus códigos, tenho uma fábrica estática semelhante a esta:

public class SomeFactory {

    // Static class
    private SomeFactory() {...}

    public static Foo createFoo() {...}

    public static Foo createFooerFoo() {...}
}

Durante uma revisão de código, foi proposto que este fosse um singleton e injetado. Portanto, deve ficar assim:

public class SomeFactory {

    public SomeFactory() {}

    public Foo createFoo() {...}

    public Foo createFooerFoo() {...}
}

Algumas coisas a destacar:

  • Ambas as fábricas são apátridas.
  • A única diferença entre os métodos são seus escopos (instância vs estática). As implementações são as mesmas.
  • Foo é um bean que não possui uma interface.

Os argumentos que tive para ficar estático foram:

  • A classe é sem estado, portanto, não precisa ser instanciada
  • Parece mais natural poder chamar um método estático do que ter que instanciar uma fábrica

Os argumentos para a fábrica como um singleton foram:

  • É bom injetar tudo
  • Apesar da apatridia da fábrica, o teste é mais fácil com a injeção (fácil de zombar)
  • Deve ser ridicularizado ao testar o consumidor

Eu tenho alguns problemas sérios com a abordagem singleton, pois parece sugerir que nenhum método deve ser estático. Também parece sugerir que utilitários como StringUtilsdevem ser embalados e injetados, o que parece bobagem. Por fim, isso implica que vou precisar zombar da fábrica em algum momento, o que não parece certo. Não consigo pensar em quando precisaria zombar da fábrica.

O que a comunidade pensa? Embora eu não goste da abordagem singleton, não pareço ter um argumento muito forte contra isso.

bstempi
fonte
2
A fábrica está sendo usada por alguma coisa . Para testar essa coisa isoladamente, você precisa fornecer uma simulação da sua fábrica. Se você não zombar de sua fábrica, um bug pode causar um teste de unidade com falha quando não deveria.
20913 Mike
Então, você está advogando contra utilitários estáticos em geral, ou apenas fábricas estáticas?
bstempi
11
Eu estou advogando contra eles em geral. Em C #, por exemplo, as classes DateTimee Filesão notoriamente difíceis de testar pelas mesmas razões. Se você tem uma classe, por exemplo, que define a Createddata DateTime.Nowno construtor, como criar um teste de unidade com dois desses objetos que foram criados com 5 minutos de intervalo? E quanto a anos separados? Você realmente não pode fazer isso (sem muito trabalho).
20913 Mike
2
Sua fábrica de singleton não deveria ter um privateconstrutor e um getInstance()método? Desculpe, nit-picker incorrigível!
TMN
11
@ Mike - Concordo - eu frequentemente usei uma classe DateTimeProvider que retorna DateTime.Now trivialmente. Em seguida, você pode trocá-lo por uma simulação que retorna um tempo pré-determinado em seus testes. É um trabalho extra passá-la para todos os construtores - a maior dor de cabeça é quando colegas não comprar a ele 100% e deslizamento referências explícitas para DateTime.Now por preguiça ... :)
Julia Hayward

Respostas:

15

Por que você separaria sua fábrica do tipo de objeto que ela cria?

public class Foo {

    // Private constructor
    private Foo(...) {...}

    public static Foo of(...) { return new Foo(...); }
}

Isso é o que Joshua Bloch descreve como seu Item 1 na página 5 de seu livro "Java Efetivo". Java é detalhado o suficiente sem adicionar classes e singletons extras e outros enfeites. Existem alguns casos em que uma classe de fábrica separada faz sentido, mas são poucas e distantes entre si.

Parafraseando o Item 1 de Joshua Bloch, diferentemente dos construtores, os métodos estáticos de fábrica podem:

  • Tenha nomes que possam descrever tipos específicos de criação de objetos (Bloch usa probablePrime (int, int, Random) como exemplo)
  • Retornar um objeto existente (pense em flyweight)
  • Retorna um subtipo da classe original ou uma interface que ele implementa
  • Reduza a verbosidade (de especificar parâmetros de tipo - veja Bloch, por exemplo)

Desvantagens:

  • Classes sem um construtor público ou protegido não podem ser subclassificadas (mas você pode usar construtores protegidos e / ou o Item 16: favorecer a composição sobre a herança)
  • Os métodos estáticos de fábrica não se destacam de outros métodos estáticos (use um nome padrão como of () ou valueOf ())

Realmente, você deve levar o livro de Bloch ao revisor de código e ver se vocês dois podem concordar com o estilo de Bloch.

GlenPeterson
fonte
Também pensei sobre isso e acho que gosto. Não me lembro porque os separei em primeiro lugar.
bstempi
De um modo geral, concordamos com o estilo de Bloch. Por fim, vamos mudar nossos métodos de fábrica para a classe que eles estão instanciando. A única diferença entre a nossa solução e a sua é que o construtor permanecerá público, para que as pessoas ainda possam instanciar diretamente a classe. Obrigado pela sua resposta!
bstempi
Mas ... isso é horrível. Você provavelmente deseja uma classe que implemente o IFoo e a passe. A utilização desse padrão não é mais possível; você precisa codificar IFoo para significar Foo para obter instâncias. Se você possui o IFooFactory que contém um IFoo create (), o código do cliente não precisa saber os detalhes da implementação de qual Foo concreto foi escolhido como IFoo.
21713 Wilbert
@ Wilbert public static IFoo of(...) { return new Foo(...); } Qual é o problema? Bloch lista a satisfação de sua preocupação como uma das vantagens desse design (segundo ponto acima). Não devo estar entendendo o seu comentário.
GlenPeterson
Para criar uma instância, esse design espera: Foo.of (...). No entanto, a existência de Foo nunca deve ser exposta, apenas o IFoo deve ser conhecido (como Foo é apenas um implemento concreto do IFoo). Ter um método estático de fábrica no implemento de concreto quebra isso e leva a um acoplamento entre o código do cliente e a implementação do concreto.
Wilbert #
5

Logo de cara, aqui estão alguns dos problemas com estática em Java:

  • métodos estáticos não são compatíveis com as "regras" do OOP. Você não pode forçar uma classe a implementar métodos estáticos específicos (tanto quanto eu sei). Portanto, você não pode ter várias classes implementando o mesmo conjunto de métodos estáticos com as mesmas assinaturas. Então você está preso a um dos itens que está fazendo. Você também não pode subclassificar uma classe estática. Portanto, sem polimorfismo, etc.

  • Como os métodos não são objetos, os métodos estáticos não podem ser contornados e não podem ser usados ​​(sem fazer uma tonelada de codificação padrão), exceto pelo código que está vinculado a eles - o que torna o código do cliente altamente acoplado. (Para ser justo, isso não é apenas culpa da estática).

  • campos estáticos são bastante semelhantes aos globais


Você mencionou:

Eu tenho alguns problemas sérios com a abordagem singleton, pois parece sugerir que nenhum método deve ser estático. Também parece sugerir que utilitários como o StringUtils sejam embalados e injetados, o que parece bobagem. Por fim, isso implica que vou precisar zombar da fábrica em algum momento, o que não parece certo. Não consigo pensar em quando precisaria zombar da fábrica.

O que me deixa curioso para perguntar:

  • quais são, na sua opinião, as vantagens dos métodos estáticos?
  • você acredita que StringUtilsfoi corretamente projetado e implementado?
  • por que você acha que nunca precisará zombar da fábrica?

fonte
Ei, obrigado pela resposta! Na ordem em que você perguntou: (1) As vantagens dos métodos estáticos são o açúcar sintático e a falta de uma instância desnecessária. É bom ler o código que tem uma importação estática e dizer algo como, em Foo f = createFoo();vez de ter uma instância nomeada. A instância em si não fornece utilidade. (2) Eu acho que sim. Ele foi projetado para superar o fato de que muitos desses métodos não existem na Stringclasse. Substituir algo tão fundamental quanto Stringnão é uma opção, portanto, os utilitários são o caminho a percorrer. (continuação)
bstempi
Eles são fáceis de usar e não exigem que você se preocupe com nenhuma instância. Eles se sentem naturais. (3) Porque minha fábrica é muito parecida com os métodos de fábrica Integer. Eles são muito simples, têm muito pouca lógica e existem apenas para oferecer conveniência (por exemplo, não há outro motivo de OO para tê-los). A parte principal do meu argumento é que eles não dependem de nenhum estado - não há razão para isolá-los durante os testes. As pessoas não zombam StringUtilsdurante o teste - por que precisariam zombar dessa fábrica simples de conveniência?
bstempi
3
Eles não zombam StringUtilsporque há uma garantia razoável de que os métodos não têm efeitos colaterais.
Robert Harvey
@RobertHarvey Suponho que estou fazendo a mesma afirmação sobre minha fábrica - ela não modifica nada. Assim como StringUtilsretorna algum resultado sem alterar as entradas, minha fábrica também não modifica nada.
bstempi
@bstempi Eu estava indo para um pouco de método socrático lá. Eu acho que sou péssimo nisso.
0

Eu acho que o aplicativo que você está construindo deve ser bastante simples, ou você está relativamente adiantado em seu ciclo de vida. O único outro cenário em que posso pensar em que você já não teria problemas com sua estatística é se você conseguiu limitar as outras partes do seu código que conhecem sua fábrica a um ou dois lugares.

Imagine que seu Aplicativo evolua e agora, em alguns casos, você precisa produzir uma FooBar(subclasse de Foo). Agora você precisa percorrer todos os lugares do seu código que conhecem o seu SomeFactorye escrever algum tipo de lógica que analise someConditione chame SomeFactoryou SomeOtherFactory. Na verdade, olhando para o seu código de exemplo, é pior do que isso. Você planeja apenas adicionar método após método para criar Foos diferentes e todo o seu código de cliente deve verificar uma condição antes de descobrir qual Foo fazer. O que significa que todos os clientes precisam ter um conhecimento íntimo de como sua fábrica funciona e todos precisam mudar sempre que um novo Foo for necessário (ou removido!).

Agora imagine se você acabou de criar um IFooFactoryque cria um IFoo. Agora, produzir uma subclasse diferente ou mesmo uma implementação completamente diferente é brincadeira de criança - você apenas alimenta o IFooFactory certo na cadeia e, quando o cliente obtém, ele chama gimmeAFoo()e obtém o resultado certo porque obteve a Fábrica certa. .

Você pode estar se perguntando como isso ocorre no código de produção. Para dar um exemplo da base de código em que estou trabalhando, que está começando a se estabilizar após pouco mais de um ano de projetos em andamento, tenho várias Fábricas usadas para criar objetos de dados de perguntas (são como modelos de apresentação ) Eu tenho um QuestionFactoryMapque é basicamente um hash para procurar qual fábrica de perguntas usar quando. Então, para criar um Aplicativo que faça perguntas diferentes, coloquei Fábricas diferentes no mapa.

As perguntas podem ser apresentados em qualquer instruções ou exercícios (que tanto implementar IContentBlock), de modo que cada InstructionsFactoryou ExerciseFactoryreceberá uma cópia do QuestionFactoryMap. Em seguida, as várias fábricas de Instruções e Exercícios são entregues às ContentBlockBuilderquais, novamente, mantém um hash do qual usar quando. E então, quando o XML é carregado, o ContentBlockBuilder chama a Fábrica de Exercícios ou Instruções a partir do hash e solicita createContent(), e a Fábrica delega a construção das perguntas para o que extrai de seu QuestionFactoryMap interno, com base no que está em cada nó XML vs. o hash. Infelizmente , essa coisa é um em BaseQuestionvez de umIQuestion, mas isso só mostra que mesmo quando você toma cuidado com o design, pode cometer erros que podem assombrá-lo.

Seu intestino está lhe dizendo que essas estáticas vão machucá-lo, e certamente há muito na literatura para apoiá-lo. Você nunca perderá com a Injeção de Dependência que acaba usando apenas para seus Testes de Unidade (não que eu acredite que, se você criar um bom código, não conseguirá usá-lo mais do que isso), mas a estática pode destruir completamente sua capacidade para modificar seu código de maneira rápida e fácil. Então, por que ir até lá?

Amy Blankenship
fonte
Pense por um momento nos métodos de fábrica da Integerclasse - coisas simples como converter de uma String. Isso é o que Foofaz. Meu Fooobjetivo não é ser estendido (minha culpa por não declarar isso), portanto, não tenho seus problemas polimórficos. Entendo o valor de ter fábricas polimórficas e as usei no passado ... só não acho que sejam apropriadas aqui. Você pode imaginar se você precisar instanciar uma fábrica para executar as operações simples Integerque atualmente são instaladas através de fábricas estáticas?
precisa saber é
Além disso, suas suposições sobre o aplicativo são bastante diferentes. O aplicativo está bastante envolvido. Isso Foo, no entanto, é muito mais simples do que muitas das classes. É apenas um POJO simples.
precisa saber é
Se o Foo for estendido, existe uma razão para a Factory ser uma instância - você fornece a instância correta da fábrica para fornecer a subclasse correta, em vez de chamar if (this) then {makeThisFoo()} else {makeThatFoo()}todo o código. Uma coisa que notei sobre pessoas com arquitetura crônica ruim é que elas são como pessoas com dor crônica. Na verdade, eles não sabem como é não sentir dor; portanto, a dor em que sentem não parece tão ruim assim.
Amy Blankenship
Além disso, você pode querer verificar para fora este link sobre os problemas com métodos estáticos (mesmo os de matemática!)
Amy Blankenship
Eu atualizei a pergunta. Você e uma outra pessoa mencionaram a extensão Foo. Com razão - nunca o declarei final. Fooé um bean que não deve ser estendido.
precisa saber é o seguinte