O padrão de fábrica viola o princípio de aberto / fechado?

13

Por que este ShapeFactory usa instruções condicionais para determinar qual objeto instanciar. Não precisamos modificar o ShapeFactory se quisermos adicionar outras classes no futuro? Por que isso não viola o princípio aberto e fechado?

Design de padrão de fábrica

Design de fábrica

Armon Safai
fonte
2
A qual “padrão de fábrica” você se refere com precisão? Em geral, uma fábrica é qualquer objeto ou método que serve para instanciar um objeto. Depois, há variações específicas dessa idéia geral, como o Abstract Factory Pattern, em que cada instância de fábrica representa uma paleta específica de opções - geralmente gerenciadas por subclassificação em vez de condicionais.
amon
3
Obrigado por essa informação, isso esclarece muito. Esse é definitivamente um exemplo de padrão de fábrica, mas não do padrão abstrato de fábrica comumente associado aos padrões de fábrica. O código desse artigo é bastante questionável, e eu não esperaria ver algo assim no código real.
amon
@ArmonSafai: Você está vinculando muito este post do blog, mas não está realmente explicando o porquê. De alguma forma, todos somos ignorantes do padrão? Também temos o Google, assim como você.
Robert Harvey
1
@RobertHarvey Estou ligando este blog para mostrar como o padrão de fábrica em que página está usando condicionais
Armon Safai

Respostas:

20

A sabedoria convencional orientada a objetos é evitar ifdeclarações e substituí-las pelo despacho dinâmico de métodos substituídos nas subclasses de uma classe abstrata. Por enquanto, tudo bem.

Mas o objetivo do padrão de fábrica é dispensar você de conhecer as subclasses individuais e trabalhar apenas com a superclasse abstrata . A idéia é que a fábrica saiba melhor que você qual classe específica instanciar, e será melhor trabalhar apenas com os métodos publicados pela superclasse. Isso geralmente é verdade e um padrão valioso.

Portanto, não há como escrever uma classe de fábrica renunciar às ifinstruções. Isso mudaria o fardo de escolher uma classe específica para o chamador, que é exatamente o que o padrão deve evitar. Nem todos os princípios são absolutos (na verdade, nenhum princípio é absoluto) e, se você usar esse padrão, presumiria que o benefício dele é maior do que o benefício de não usar um if.

Kilian Foth
fonte
2
É perfeitamente possível criar um padrão de fábrica sem muitos ifs. Veja a resposta de @ BЈовић para um exemplo simples de como conseguir isso. Votado.
David Arno
11
@DavidArno Naturalmente, existem diferentes maneiras de escolher a classe concreta. O Service Locator é um, um Container IoC configurável é outro. Esses são apenas detalhes de implementação; eles não prejudicam a mensagem principal de Killian, que é que a Fábrica alivia o chamador de ter que decidir qual classe concreta instanciar. Não fique atolado nos detalhes.
21715 Robert Downey
1
Declaração fantástica que de forma alguma responde à pergunta.
Martin Maat
1
@ R.Schmitz Eu acho que você está incorreto em sua suposição. Acho que muitas pessoas perderam essa pergunta do OP: "Não precisamos modificar o ShapeFactory se queremos adicionar outras classes no futuro?" CLARAMENTE, o OP está confuso ao pensar que esse padrão viola o OCP porque, para adicionar novas funcionalidades, é necessário modificar o código existente. A resposta correta para esta pergunta pode ser encontrada na minha resposta. A resposta curta: você deixa esse código em paz e aplica o padrão Abstract Factory para EXTENDER (não modificar) sua funcionalidade existente. Por causa dessa resposta de Kilian, NÃO aborda a questão.
Hbfanez
4

O exemplo provavelmente usa uma declaração condicional porque é a mais simples. Uma implementação mais complexa pode usar um mapa ou configuração ou (se você quiser mesmo) algum tipo de registro em que as classes possam se registrar. No entanto, não há nada de errado em usar uma condição se o número de classes for pequeno e mudar com pouca frequência.

Estender o condicional para adicionar suporte a uma nova subclasse no futuro seria, de fato, estritamente falando, uma violação do princípio de aberto / fechado. A solução "correta" seria criar uma nova fábrica com a mesma interface. Dito isto, a adesão ao princípio O / C deve sempre ser ponderada em relação a outros princípios de design, como o KISS e o YAGNI.

Dito isto, o código exibido é claramente um exemplo de código que é projetado para mostrar o conceito de fábrica e nada mais. Por exemplo, é um estilo muito ruim retornar nulo como o exemplo, mas o tratamento de erros mais elaborado apenas obscureceria o ponto. O código de exemplo não é um código de qualidade de produção; você não deve esperar que seja.

JacquesB
fonte
Você pode explicar como um mapa / configuração / registro funcionaria?
Armon Safai
@ArmonSafai: Aqui está um exemplo: jkfill.com/2010/12/29/self-registering-factories-in-c-sharp
JacquesB
As fábricas de auto-registro são, AFAIK, impossíveis em C ++ nas bibliotecas estáticas, pois as variáveis ​​globais não utilizadas (ou seja, usadas por odr) são descartadas pelas cadeias de ferramentas.
void.pointer
@ArmonSafai leia isto para mais compreensão goo.gl/RYuNSM
AZ_
2

O próprio padrão não viola o Princípio Aberto / Fechado (OCP). No entanto, violamos o OCP quando usamos o padrão incorretamente.

A resposta simples para esta pergunta é a seguinte:

  1. Crie sua funcionalidade básica usando o Factory Method Pattern.
  2. ESTENDER sua funcionalidade usando o Abstract Factory Pattern

No exemplo fornecido, sua funcionalidade base suporta três formas: Círculo, Retângulo e Quadrado. Suponha que você precise apoiar Triângulo, Pentágono e Hexágono no futuro. Para fazer isso SEM violar o OCP, você deve criar uma fábrica adicional para dar suporte às novas formas (vamos chamar AdvancedShapeFactory) e depois usar o AbstractFactory para decidir qual fábrica você precisa criar para criar as formas necessárias.

hfontanez
fonte
Eu prefiro a solução de fábricas de auto-registro (na ausência de um contêiner IoC verdadeiro e configurável, o melhor), pois, caso contrário, o que obtemos da sua proposta são basicamente fábricas de fábricas , e é aí que as coisas ficam complexas demais.
Nom1fan 25/01
1

Se você está falando sobre o padrão Abstract Factory, a tomada de decisões geralmente não está no próprio Factory, mas no código do aplicativo. É esse código que escolhe qual fábrica de concreto instanciar e repassar para o código do cliente que usará objetos produzidos pela fábrica. Veja o final do exemplo de Java aqui: https://en.wikipedia.org/wiki/Abstract_factory_pattern

A tomada de decisão não implica necessariamente ifdeclarações. Poderia ler o tipo de fábrica concreto de um arquivo de configuração, derivá-lo de uma estrutura de mapa etc.

guillaume31
fonte
Se eu, o interlocutor, estou tomando a decisão de qual classe concreta instanciar, então por que estou me incomodando com uma Fábrica Abstrata?
Robert Harvey
Por favor, defina "o chamador". Como descrevo na minha resposta, há o código global do aplicativo e o código que precisa gerar objetos usando uma Factory. Enquanto este último fato precisa ser inconsciente da classe concreta para instanciar, algum outro código contextual tem de saber sobre ele e nova-lo ...
guillaume31
0

Se você pensa no Open-Close no nível da classe com esta fábrica, você está criando outra classe em seu sistema Open-Close, por exemplo, se você tem outra classe que usa uma Shape e calcula a área (exemplo típico), essa classe é OpenClose porque Ele pode calcular a área para novos tipos de formas sem modificação. Então você tem outra classe que desenha a forma, outra classe que toma as formas N e retorna a maior e você pode pensar em geral que as outras classes em seu sistema que lidam com formas são de abertura e fechamento (pelo menos sobre formas). Observando o projeto, a fábrica permite que o restante do sistema seja Aberto-Fechado e, obviamente, a própria fábrica NÃO É Aberto-Fechado.

É claro que você também pode abrir e fechar esta fábrica, através de algum tipo de carregamento dinâmico, e todo o sistema pode ser aberto / fechado (você pode adicionar novas formas, deixando cair alguns jarros no caminho de classe, por exemplo). Você precisa avaliar se essa complexidade extra vale a pena, dependendo do sistema que você está construindo, nem todos os sistemas precisam de recursos conectáveis ​​e nem todo o sistema precisa ser completamente aberto / fechado.

AlfredoCasado
fonte
0

O princípio aberto-fechado, como o princípio de substituição de Liskov, aplica-se a árvores de classes, a hierarquias de herança. No seu exemplo, a classe factory não está na árvore genealógica das classes instanciadas, portanto não pode violar essas regras. Haveria uma violação se seu GetShape (ou mais apropriadamente chamado, CreateShape) fosse implementado na classe base Shape.

Martin Maat
fonte
-2

Tudo depende de como você o implementa. Você pode usar std::mappara segurar ponteiros de função para funções que criam objetos. Então, o princípio de abrir / fechar não é violado. Ou mudar / caso.

De qualquer forma, se você não gostar do padrão de fábrica, sempre poderá usar a injeção de dependência.

BЈовић
fonte
6
Como o switch / case é melhor que os condicionais? Usar um mapa / dict / table para representar código como dados é bom, se você realmente precisar de um registro de implementações diferentes - por exemplo, em algumas implementações de contêineres DI. Mas ter retornos de chamada diferentes do mesmo tipo não é necessário para a maioria das fábricas! Não entendo bem por que você está sugerindo isso. Além disso, muitos contêineres de DI são implementados em termos de objetos de fábrica; portanto, sugerir o uso de DI em vez de fábricas parece um pouco circular.
amon
1
@ amon Eu pretendia usar outros tipos de DI - não a fábrica.
BЈовић
1
Como sua fábrica vai decidir qual ponteiro usar? Eventualmente, você tem que tomar uma decisão.
Whatsisname
@ArmonSafai ???
BЈовић