Padrão Criacional: Abstract Factory
Aqui está um artigo que detalha um padrão de design criacional que nos permite criar uma família de produtos de maneira flexível e extensível.

O que é isso?
No último artigo, aprendemos sobre o padrão de método de fábrica . Então, vimos como poderíamos modelar as variantes do F-16 usando o método de fábrica. Mas este artigo abordará como representar vários aviões além do F-16. Por exemplo, digamos que um cliente compre um Boeing 747 para seu CEO viajar e deseja que seu software suporte esse novo tipo de aeronave.
O padrão fábrica abstrato resolve o problema de criar famílias de produtos relacionados. Por exemplo, o F-16 precisa de um motor, um cockpit e asas. O Boeing-747 exigiria as mesmas peças, mas seriam específicas da Boeing. Qualquer avião precisaria dessas três partes relacionadas; no entanto, as peças serão específicas do plano. Você pode ver um padrão emergindo aqui? Primeiro, precisamos de uma estrutura para criar as peças associadas para cada avião.
O padrão fábrica abstrata é um padrão de projeto que permite criar famílias de objetos relacionados ou dependentes sem especificar suas classes concretas.
Diagrama de classe
O diagrama de classes inclui as seguintes entidades:
- fábrica abstrata
- fábrica de concreto
- Produto Abstrato
- Produto Concreto
- Cliente

O padrão de fábrica abstrato permite criar uma família de objetos relacionados sem especificar suas classes concretas. Vamos considerar um exemplo. Digamos que você esteja criando um software de simulação para a indústria da aviação e precise representar diferentes aeronaves como objetos. Mas antes de representar uma aeronave, você também precisa definir as outras peças de uma aeronave como objetos. Vamos escolher três: asas, cockpit e motor. Agora digamos que a primeira aeronave que você deseja representar é o poderoso F-16. Você provavelmente escreverá três classes, uma para cada peça específica do F-16. Em seu código, você provavelmente consumirá essas três classes recém-criadas como esta:

Esse trecho de código parece inofensivo, mas pode causar muita dor de cabeça se o software de simulação decolar e você precisar expandi-lo para outras aeronaves. Estes são alguns dos problemas com o código acima:
- As classes de concreto para as três peças foram expostas diretamente ao consumidor.
- O F-16 possui diversas variantes com diferentes motores. Se você deseja retornar um objeto de mecanismo que corresponda à variante, precisará criar uma subclasse da classe F-16Engine, o que também exigiria a alteração do snippet do consumidor.
- A lista no trecho de código é declarada parametrizada com uma classe concreta; se você adicionar outro motor de aeronave ao seu programa, ele não será reconhecido pela Lista, embora os motores de todas as aeronaves sejam um pouco semelhantes.
Trate uma interface como um conceito abstrato em vez de uma implementação
Um bom design orientado a objetos significa ocultar as classes concretas usadas em seu aplicativo e expor as interfaces aos clientes. Um objeto responde a um conjunto de requisições que podem ser capturadas por uma interface implementada pela classe do objeto. Os clientes devem saber a quais demandas um objeto responde, e não a implementação.
Em nosso exemplo, criaremos uma interface que expõe um método chamado start() . A classe F16Engine mudará assim:

Veja como o código do consumidor correspondente muda com a alteração acima.

O código do consumidor agora está livre dos detalhes de implementação de como o motor F-16 funciona e qual classe o implementa. No entanto, ainda não queremos revelar a parte F16Engine() do código. Queremos manter nossos consumidores tentando adivinhar qual classe estamos instanciando. Isso é discutido a seguir.
Criando uma fábrica
Em vez de criar objetos no código do cliente, teremos uma classe fábrica que cria os objetos solicitados e os retorna ao cliente. Chamaremos essa classe de F16Factory porque ela pode produzir várias partes da aeronave militar F16 e entregá-las ao cliente solicitante. A classe teria a seguinte forma.

Suponha agora que passamos o objeto F16Factory para o código do cliente como um argumento do construtor e ele pode criar objetos como este:

Observe como esta configuração nos permite alterar a classe concreta que representa o F16Engine , desde que ele se comprometa com a interface IEngine . Podemos renomear, aprimorar ou modificar nossa classe sem causar uma alteração significativa no cliente. Observe também que alterando apenas a classe de fábrica passada para o construtor do cliente, podemos fornecer ao cliente peças diferentes para uma aeronave completamente nova.
Fábrica de Fábricas: Grande fábrica que fabrica outras fábricas
Não seria ótimo usar o mesmo client snippet para diferentes tipos de aeronaves, como um Boeing 747 ou um MiG-29 russo ? Se todas as fábricas que implementam createEngine() concordarem com uma interface padrão, nosso código cliente continuará funcionando para todas as fábricas de aeronaves. Mas todas as fábricas teriam que se comprometer com essa interface comum cujos métodos irão implementar, e essa interface familiar é uma fábrica abstrata .
Implementação
Vamos começar com uma interface que define os métodos que diferentes fábricas de aeronaves precisam implementar. O código do cliente é escrito nessa interface, mas será composto em tempo de execução com uma fábrica concreta.

Quando nos referimos a “ interface ”, queremos dizer uma interface Java ou uma classe abstrata. Nesse caso, poderíamos ter usado uma classe abstrata se houvesse implementações padrão para qualquer um dos produtos. Os métodos create não retornam produtos concretos — eles retornam interfaces para desacoplar os consumidores da fábrica da implementação concreta das partes.
A definição formal do padrão fábrica abstrata diz que é um padrão de projeto que define uma interface para criar famílias de produtos relacionados sem especificar as classes concretas. Nesse caso, IAircraftFactory é essa interface e seus métodos create não estão retornando partes substanciais, mas interfaces implementadas pelas classes das partes concretas.
Em seguida, vamos configurar as fábricas para nossas duas aeronaves

Uma fábrica de concreto produzirá um motor, cockpit e asas específicos para F-16 ou Boeing. Cada parte tem uma interface de produto correspondente que não listamos aqui para fins de brevidade. As interfaces do produto representando as partes seriam:
IEngine
ICockpit
IWings
No artigo anterior , criamos uma classe para F-16 que incluía um método fly() . Esse método invocou makeF16() , que construiu uma instância de F-16 e, em seguida, invocou taxi() . Uma vez na pista, voou para longe. Podemos aplicar essa mesma abordagem ao nosso exemplo do Boeing-747. Todas as aeronaves seguem o mesmo padrão: são fabricadas, taxiam na pista e voam para longe. Podemos assim criar uma classe para uma aeronave que faça essas três tarefas. Observe como não estamos criando classes separadas para representar as duas aeronaves (ou seja, F-16 e Boeing-747), mas sim uma única classe de aeronave que pode representar ambas.

Por enquanto, manteremos o método makeAircraft vazio. Então, vamos primeiro ver como um cliente solicitará os objetos F-16 e Boeing-747.

Para adicionar um construtor à nossa classe Aircraft , precisaremos criar uma nova variável de instância chamada fábrica. A fábrica armazena o objeto que será responsável pela criação das peças da aeronave. Podemos obter uma aeronave diferente compondo o objeto aeronave com outra fábrica. A versão completa da classe Aircraft está abaixo:

O cliente deve instanciar a fábrica correta, que passa para a classe Aircraft . A classe Aircraft representa o cliente. Poderíamos ter criado uma interface IAircraft para representar todas as aeronaves que nossa fábrica pode criar, mas para nosso exemplo limitado, é desnecessário.
O código resultante é facilmente estendido e modificado.
Para continuar nosso exemplo do artigo padrão de método de fábrica , podemos usar a subclasse da classe F16Factory para criar fábricas para as variantes A e B do F-16 ou parametrizá-la com uma enumeração especificando o modelo variante e retornar a parte correta em um switch declaração.
Outros exemplos
- O padrão de fábrica abstrato ajuda a construir famílias de produtos relacionados. Por exemplo, se sua biblioteca fornece widgets sofisticados de interface do usuário e suporta macOS e Windows, você pode precisar de uma família de produtos que funcionem nessas plataformas. Da mesma forma, os temas usados em IDEs podem ser outro exemplo. Por exemplo, se seu IDE oferece suporte a temas claros e escuros, ele pode usar o padrão de fábrica abstrata para criar widgets que pertencem ao tema claro ou escuro, variando a fábrica concreta que cria os widgets.
javax.xml.parsers.DocumentBuilderFactory.newInstance()
vai te devolver uma fábrica.javax.xml.transform.TransformerFactory.newInstance()
vai te devolver uma fábrica.
- O método de fábrica e o padrão de fábrica abstrato são semelhantes porque ajudam a gerenciar a criação de objetos. A diferença entre os dois padrões está em suas motivações. O padrão de método de fábrica geralmente é responsável pela criação de um único produto, enquanto um padrão de fábrica abstrato cria famílias inteiras de produtos relacionados. Além disso, usamos herança no padrão de método de fábrica para construir produtos mais especializados. Por fim, praticamos a composição de objetos em um padrão de fábrica abstrato passando fábricas consumidas para criar os resultados desejados.
- Criamos uma fábrica de concreto quando adicionamos uma nova aeronave à nossa frota. Mas suponha que um helicóptero seja adicionado à frota e exija uma peça que uma aeronave não possui. Nesse caso, precisaremos estender a interface IAircraftFactory com outro método criativo para a peça necessária apenas para o helicóptero. Isso fará com que a mudança ocorra em cascata nas fábricas existentes, que precisam retornar null, pois o novo componente não faz parte dos jatos.
- Fábricas de concreto são melhor implementadas como objetos singleton.