Como outra linguagem popular evitaria ter que usar o padrão de fábrica ao gerenciar uma complexidade semelhante à do Java / Java EE?

23

O padrão de fábrica (ou pelo menos o uso de FactoryFactory..) é alvo de muitas piadas, como aqui .

Além de ter nomes detalhados e "criativos", como RequestProcessorFactoryFactory.RequestProcessorFactory , há algo de fundamentalmente errado com o padrão de fábrica se você precisar programar em Java / C ++ e houver uma base de dados para Abstract_factory_pattern ?

Como outro idioma popular (por exemplo, Ruby ou Scala ) evitava usá-lo enquanto gerenciava uma complexidade semelhante?

A razão pela qual estou perguntando é que vejo principalmente as críticas às fábricas mencionadas no contexto do ecossistema Java / Java EE , mas elas nunca explicam como outras linguagens / estruturas as resolvem.

senseiwu
fonte
4
O problema não é o uso de fábricas - é um padrão perfeitamente adequado. É o uso excessivo de fábricas que é especialmente prevalente no mundo empresarial de Java.
código é o seguinte
Link de piada muito bom. Eu adoraria ver uma interpretação funcional da linguagem para recuperar as ferramentas para construir um rack de especiarias. Isso realmente o levaria para casa, eu acho.
Patrick M

Respostas:

28

Sua pergunta está marcada com "Java", não é surpresa que você esteja se perguntando por que o padrão Factory está sendo ridicularizado: o próprio Java vem com abusos bem embalados desse padrão.

Por exemplo, tente carregar um documento XML de um arquivo e execute uma consulta XPath nele. Você precisa de algo como 10 linhas de código apenas para configurar os Fábricas e Construtores:

DocumentBuilderFactory builderFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = builderFactory.newDocumentBuilder(); 

Document xmlDocument = builder.parse(new FileInputStream("c:\\employees.xml"));
XPath xPath =  XPathFactory.newInstance().newXPath();

xPath.compile(expression).evaluate(xmlDocument);

Eu me pergunto se os caras que criaram essa API já trabalharam como desenvolvedores ou apenas leram livros e jogaram coisas por aí. Eu entendo que eles simplesmente não queriam escrever um analisador e deixaram a tarefa para os outros, mas ainda é uma implementação feia.

Como você está perguntando qual é a alternativa, aqui está o carregamento do arquivo XML em C #:

XDocument xml = XDocument.Load("c:\\employees.xml");
var nodes = xml.XPathSelectElements(expression);

Suspeito que desenvolvedores de Java recém-nascidos vejam a loucura da Factory e acho que é bom fazer isso - se os gênios que construíram o Java os usaram muito.

A fábrica e quaisquer outros padrões são ferramentas, cada uma adequada para um trabalho específico. Se você aplicá-los a trabalhos que não são adequados, você provavelmente terá um código feio.

Sten Petrov
fonte
1
Apenas observe que sua alternativa C # está usando o LINQ to XML mais recente - a alternativa DOM mais antiga teria algo parecido com isto: XmlDocument xml = new XmlDocument(); xml.Load("c:\\employees.xml"); XmlNodeList nodes = xml.SelectNodes(expression); (No geral, o LINQ to XML é muito mais fácil de usar, embora principalmente em outras áreas.) E aqui está um artigo da KB I encontrado, com um método recomendadas MS-: support.microsoft.com/kb/308333
Bob
36

Como tantas vezes, as pessoas entendem mal o que está acontecendo (e isso inclui muitos dos risos).
Não é o padrão de fábrica em si que é tão ruim quanto o modo como muitas (talvez até a maioria) as pessoas o usam.

E isso sem dúvida decorre da maneira como a programação (e os padrões) são ensinados. Os alunos (geralmente chamados de "estudantes") são instruídos a "criar X usando o padrão Y" e, depois de algumas iterações, pensam que esse é o caminho para abordar qualquer problema de programação.
Então eles começam a aplicar um padrão específico de que gostam na escola contra tudo e qualquer coisa, seja apropriado ou não.

E isso inclui professores universitários que escrevem livros sobre design de software, infelizmente.
O ponto culminante disso foi um sistema que eu tinha o distinto desagrado de manter, criado por um desses (o homem tinha até vários livros sobre design orientado a objetos em seu nome e uma posição de professor no departamento de CS de uma grande universidade). )
Foi baseado em um padrão de três camadas, com cada camada em si um sistema de três camadas (é necessário desacoplar ...). Na interface entre cada conjunto de camadas, em ambos os lados, havia uma fábrica para produzir os objetos para transferir dados para a outra camada e uma fábrica para produzir o objeto para converter o objeto recebido em um pertencente à camada de recebimento.
Para cada fábrica, havia uma fábrica abstrata (quem sabe, a fábrica pode precisar mudar e você não deseja que o código de chamada precise mudar ...).
E toda essa bagunça era obviamente completamente sem documentos.

O sistema tinha um banco de dados normalizado para a quinta forma normal (não estou brincando).

Um sistema que era essencialmente pouco mais que um catálogo de endereços que poderia ser usado para registrar e rastrear documentos recebidos e enviados, tinha uma base de código de 100 MB em C ++ e mais de 50 tabelas de banco de dados. A impressão de 500 cartas levaria 72 horas usando uma impressora conectada diretamente ao computador executando o software.

É por isso que as pessoas zombam dos padrões e, especificamente, as pessoas que se concentram isoladamente em um único padrão específico.

jwenting
fonte
40
A outra razão é que alguns dos padrões da Gangue dos Quatro são apenas hacks detalhados para coisas que podem ser feitas trivialmente em uma linguagem com funções de primeira classe, o que meio que os torna inevitáveis ​​anti-padrões. Os padrões "Strategy", "Observer", "Factory", "Command" e "Template Method" são hacks para passar funções como argumentos; "Visitor" é um truque para executar uma correspondência de padrão em uma união de tipo de soma / variante / marcada. O fato de algumas pessoas se incomodarem com algo que é literalmente trivial em muitas outras linguagens é o que leva as pessoas a zombar de C ++, Java e linguagens semelhantes.
Doval
7
Obrigado por esta anedota. Você também pode elaborar um pouco sobre "quais são as alternativas?" parte da pergunta para que não nos tornemos assim também?
Philipp
1
@Doval Você pode me dizer como você retorna valor do comando executado ou da estratégia chamada quando você só pode passar a função? E se você diz encerramentos, lembre-se de que é exatamente a mesma coisa que criar uma classe, exceto mais explícita.
eufórico
2
@Euphoric Não estou vendo o problema, você pode elaborar? De qualquer forma, eles não são exatamente a mesma coisa. Você pode dizer que um objeto com um único campo é exatamente o mesmo que um ponteiro / referência a uma variável ou que um número inteiro é exatamente o mesmo que uma enumeração (real). Existem diferenças na prática: não faz sentido comparar funções; Ainda estou para ver uma sintaxe concisa para classes anônimas; e todas as funções com os mesmos tipos de argumentos e retorno têm o mesmo tipo (ao contrário de classes / interfaces com diferentes nomes, que são tipos diferentes, mesmo quando elas são idênticas.)
Doval
2
@Euphoric Correct. Isso seria considerado um estilo funcional ruim, se houver uma maneira de fazer o trabalho sem efeitos colaterais. Uma alternativa simples seria usar um tipo de soma / união marcada para retornar um dos N tipos de valores. Independentemente disso, vamos supor que não há uma maneira prática; não é o mesmo que uma classe. Na verdade, é uma interface (no sentido de POO). Não é difícil ver se você considera que qualquer par de funções com as mesmas assinaturas seria intercambiável, enquanto duas classes nunca são intercambiáveis, mesmo que tenham exatamente o mesmo conteúdo.
Doval
17

As fábricas têm muitas vantagens que permitem o design elegante de aplicativos em algumas situações. Uma é que você pode definir as propriedades dos objetos que deseja criar posteriormente em um só lugar, criando uma fábrica e depois entregá-la. Mas muitas vezes você realmente não precisa fazer isso. Nesse caso, o uso de uma fábrica apenas adiciona complexidade adicional sem fornecer nada em troca. Vamos pegar esta fábrica, por exemplo:

WidgetFactory redWidgetFactory = new ColoredWidgetFactory(COLOR_RED);
Widget widget = redWidgetFactory.create();

Uma alternativa ao padrão Factory é o padrão Builder muito semelhante. A principal diferença é que as propriedades dos objetos criados por um Factory são definidas quando o Factory é inicializado, enquanto um Builder é inicializado com um estado padrão e todas as propriedades são definidas posteriormente.

WidgetBuilder widgetBuilder = new WidgetBuilder();
widgetBuilder.setColor(COLOR_RED);
Widget widget = widgetBuilder.create();

Mas quando a superengenharia é o seu problema, substituir uma fábrica por um construtor provavelmente não é uma grande melhoria.

A substituição mais simples para qualquer padrão é, obviamente, criar instâncias de objetos com um construtor simples com o newoperador:

Widget widget = new ColoredWidget(COLOR_RED);

Os construtores, no entanto, têm uma desvantagem crucial na maioria das linguagens orientadas a objetos: eles devem retornar um objeto dessa classe exata e não podem retornar um subtipo.

Quando você precisar escolher o subtipo em tempo de execução, mas não desejar recorrer à criação de uma nova classe Builder ou Factory para isso, poderá usar um método de fábrica. Este é um método estático de uma classe que retorna uma nova instância dessa classe ou de uma de suas subclasses. Uma fábrica que não mantém nenhum estado interno geralmente pode ser substituída por um método de fábrica:

 Widget widget = Widget.createColoredWidget(COLOR_RED); // returns an object of class RedColoredWidget

Um novo recurso no Java 8 são as referências de métodos que permitem transmitir métodos, como faria com uma fábrica sem estado. Convenientemente, qualquer coisa que aceite uma referência de método também aceita qualquer objeto que implemente a mesma interface funcional, que também pode ser uma Fábrica de pleno direito com estado interno, para que você possa facilmente introduzir fábricas mais tarde, quando perceber uma razão para fazê-lo.

Philipp
fonte
2
Uma fábrica também pode ser configurada após a criação. A principal diferença, para um construtor, é que um construtor é normalmente usado para criar uma instância única e complexa, enquanto as fábricas são usadas para criar muitas instâncias semelhantes.
Cefalópode #
1
obrigado (+1), mas a resposta falha em explicar como outros PLs o resolveriam, no entanto, obrigado por apontar as referências do método Java 8.
senseiwu
1
Eu sempre pensei que faria sentido, em uma estrutura orientada a objetos, limitar a construção real do objeto ao tipo em questão, e que foo = new Bar(23);seja equivalente a foo = Bar._createInstance(23);. Um objeto deve poder consultar seu próprio tipo de maneira confiável usando um getRealType()método privado , mas os objetos devem poder designar um supertipo a ser retornado por chamadas externas para getType(). O código externo não deve se importar se uma chamada para new String("A")realmente retorna uma instância de String[em oposição a, por exemplo, uma instância de SingleCharacterString].
Supercat #
1
Utilizo muitas fábricas de métodos estáticos quando preciso escolher uma subclasse com base no contexto, mas as diferenças devem ser opacas para o chamador. Por exemplo, eu tenho uma série de classes de imagem que todas contêm, draw(OutputStream out)mas geram html um pouco diferente, a fábrica de métodos estáticos cria a classe certa para a situação e o chamador pode apenas usar o método draw.
Michael Shopsin
1
@ supercat você pode estar interessado em dar uma olhada no objetivo-c. A construção de objetos consiste em chamadas de método simples, geralmente [[SomeClass alloc] init]. É possível retornar uma classe instância completamente diferente ou outro objeto (como um valor em cache)
axelarge
3

Fábricas de um tipo ou de outro são encontradas em praticamente qualquer linguagem orientada a objetos em circunstâncias apropriadas. Às vezes, você simplesmente precisa de uma maneira de selecionar que tipo de objeto criar com base em um parâmetro simples, como uma string.

Algumas pessoas levam isso muito longe e tentam arquitetar seu código para nunca precisar chamar um construtor, exceto dentro de uma fábrica. As coisas começam a ficar ridículas quando você tem fábricas.

O que me impressionou quando aprendi Scala, e não conheço Ruby, mas acredito que é da mesma maneira, é que a linguagem é expressiva o suficiente para que os programadores nem sempre estejam tentando empurrar o trabalho de "encanamento" para arquivos de configuração externos . Em vez de ser tentado a criar fábricas de fábrica para conectar suas classes em diferentes configurações, no Scala você usa objetos com características misturadas. Também é relativamente fácil criar DSLs simples na linguagem para tarefas que geralmente são superdimensionadas em Java.

Além disso, como outros comentários e respostas apontaram, fechamentos e funções de primeira classe evitam a necessidade de muitos padrões. Por esse motivo, acredito que muitos dos antipadrões começarão a desaparecer à medida que o C ++ 11 e o Java 8 obtiverem ampla adoção.

Karl Bielefeldt
fonte
obrigado (+1). Sua resposta explica algumas das minhas dúvidas sobre como Scala evitaria isso. Honestamente, você não acha que uma vez que (se) o Scala se torne pelo menos tão popular quanto Java no nível corporativo, exércitos de estruturas, padrões, servidores de aplicativos pagos etc. apareceriam?
senseiwu
Eu acho que se o Scala ultrapassar o Java, será porque as pessoas preferem o "jeito Scala" de arquitetar software. O que me parece mais provável é que o Java continue adotando mais recursos que levam as pessoas a mudarem para o Scala, como os genéricos do Java 5 e os lambdas e fluxos do Java 8, que, com sorte, acabarão resultando em programadores que abandonam os antipadrões que surgiram quando esses recursos não estavam disponíveis. Sempre haverá adeptos do FactoryFactory, não importa quão bons idiomas sejam. Obviamente, algumas pessoas gostam dessas arquiteturas, ou elas não seriam tão comuns.
Karl Bielefeldt
2

Em geral, existe uma tendência de que os programas Java sejam terrivelmente superprojetados [citação necessário]. Ter muitas fábricas é um dos sintomas mais comuns do excesso de engenharia; é por isso que as pessoas tiram sarro disso.

Em particular, o problema que Java tem com as fábricas é que em Java a) os construtores não são funções eb) as funções não são cidadãos de primeira classe.

Imagine que você poderia escrever algo assim (que Functionseja uma interface bem conhecida do JRE )

// Framework code
public Node buildTree(Function<BranchNode, Node, Node> branchNodeFactory) {
    Node current = nextNode();
    while (hasNextNode()) {
        current = branchNodeFactory.call(current, nextNode());
    }
    return current
}

// MyDataNode.java
public class MyBranchNode implements BranchNode {

    public MyBranchNode(Node left, Node right) { ... }
}

// Client code
Node root = buildTree(MyBranchNode::new);

Veja, não há interfaces ou classes de fábrica. Muitas linguagens dinâmicas possuem funções de primeira classe. Infelizmente, isso não é possível com o Java 7 ou anterior (implementar o mesmo com fábricas é deixado como um exercício para o leitor).

Portanto, a alternativa não é tanto "usar um padrão diferente", mas mais "usar uma linguagem com um modelo de objeto melhor" ou "não exagerar na engenharia de problemas simples".

Cefalópode
fonte
2
isso nem tenta responder à pergunta: "quais são as alternativas?"
Gnat #
1
Leia o segundo parágrafo e você verá a parte "como outros idiomas fazem isso". (PS: a outra resposta também não mostra alternativas)
Cefalópode
4
@gnat Ele não está dizendo indiretamente que a alternativa é usar funções de primeira classe? Se o que você deseja é uma caixa preta que possa criar objetos, suas opções são uma fábrica ou uma função e uma fábrica é apenas uma função disfarçada.
Doval
3
"Em muitas linguagens dinâmicas" é um pouco enganador, pois o que é realmente necessário é uma linguagem com funções de primeira classe. "Dinâmico" é ortogonal a isso.
187 Andres F.
É verdade, mas é uma propriedade frequentemente encontrada em linguagens dinâmicas. Mencionei a necessidade da função de primeira classe no início da minha resposta.
Cefalópode