Em Um Sistema De Vendas Online O Padrão Abstract Factory
Factory de Fábricas: quando seu sistema de checkout pede desculpas por existir
Você já tentou adicionar um produto ao carrinho e o sistema simplesmente travou porque a forma de pagamento não combinava com o tipo de cliente? Isso acontece quando ninguém pensou na estrutura antes de empilhar funcionalidades. O padrão Abstract Factory resolve exatamente isso, mas não da maneira que a maioria dos tutoriais ensina. A ideia central é simples: você cria uma interface que representa uma família de objetos relacionados e deixa que classes concretas implementem essa família inteira de uma vez. No lugar de ter um factory para pagamentos, outro para frete, outro para impostos, você tem um único ponto de entrada que entrega tudo coordinated. A classe ConcreteFactoryBR entrega gateway de pagamento brasileiro, calculadora de frete nacional e regra de ICMS. A ConcreteFactoryUS faz o mesmo para o mercado americano. Isso parece redundante no começo. Por que criar tantas classes? A resposta vem quando o produto muda. Digamos que seu sistema precise aceitar criptomoedas para um segmento de clientes. Sem o padrão, você vai acabar espalhando conditionais IF pelo código todo, virando uma colcha de retalhos que ninguém consegue mainter. Com o Abstract Factory, você cria uma nova ConcreteFactory e implementa apenas os produtos daquela família. O resto do sistema nem precisa saber que existe.
Eu briguei com isso na prática há algum tempo. Estávamos integrando PIX num sistema que já existia e processava cartão de crédito e boleto. O desenvolvedor da equipe anterior tinha usado factories isoladas. Cada módulo conhecia sua própria instância de factory. Quando chegou a exigência do PIX, tive que refatorar três camadas separadas porque nenhuma delas conversava entre si. Decidi reestruturar tudo com Abstract Factory. Criei a interface PaymentGateway com o método processar(). Criei também interfaces para ShippingCalculator e TaxRule. A AbstractFactory de vendas implementou essas três interfaces. A ConcreteFactoryPix implementou PaymentGateway com a lógica específica do PIX, mas reutilizou a ShippingCalculator e TaxRule já existentes da ConcreteFactoryCartao. A economia foi real: em vez de reescrever frete e impostos para o novo fluxo, reutilizei o que já funcionava. O prazo de entrega caiu de duas semanas para cerca de quatro dias úteis. O detalhe que pouca gente menciona é o problema do produto órfão. Digamos que você tenha uma família de produtos de pagamento e frete, mas no futuro precise adicionar um quarto produto que pertence à mesma família mas não tem equivalência em todas as fábricas concretas. O padrão Abstract Factory puro exige que todas as ConcreteFactory implementem todos os produtos da família. Isso gera classes cheias de métodos que levantam exceção NotImplemented ou retornam valores nulos. Eu já vi isso acontecer em sistemas legados onde a equipe tentou aplicar o padrão cegamente. A solução que funcionou foi introduzir uma sub-família. Em vez de uma única interface AbstractFactory com cinco métodos, dividi em AbstractPaymentFactory e AbstractShippingFactory. Cada ConcreteFactory implementa apenas o que faz sentido para aquele contexto. O custo é mais interfaces para manter, mas evita a explosão de métodos vazios. Outro ponto que causa dor de cabeça é o teste. Quando seu sistema depende de uma factory concreta, você precisa injetar a fábrica certa em cada contexto de teste. O conselho prático é usar injeção de dependência com contêineres como Spring ou Windsor. Registre a ConcreteFactoryBR como singleton no escopo da aplicação e a ConcreteFactoryTest como substituta nos testes. Sem isso, cada teste acaba criando instâncias novas manualmente, o que desacelera a suite e introduz variabilidade nos resultados.
Para implementar, comece definindo os produtos. Crie uma interface para cada tipo de objeto que sua família produz. PaymentGateway, ShippingStrategy, TaxCalculator. Depois, a interface da factory que declara métodos para criar cada produto. Por fim, as concrete factories. A BR implementa os três produtos com lógica brasileira. A US com lógica americana. A EU com lógica europeia, incluindo IVA e regras de transporte distintas. Você nunca chama new diretamente nas concrete factories fora do ponto de entrada da aplicação. Isso mantém o acoplamento baixo. O trade-off que você precisa aceitar é o aumento inicial de classes. Um sistema pequeno com dois tipos de cliente e três produtos gera cerca de dez classes novas só para a estrutura. Se o projeto tiver menos de cinquenta mil linhas de código, talvez valha mais a pena usar um factory method simples ou até uma abordagem baseada em estratégia pura. O Abstract Factory brilha quando a família de produtos cresce e quando diferentes mercados exigem combinações diferentes. Nestes cenários, a economia de tempo na manutenção compensa o investimento inicial. Um erro comum é confundir Abstract Factory com Builder. Builder concentra-se em construir um objeto complexo passo a passo. Abstract Factory concentra-se em familias de objetos relacionados. Se você precisa montar um pedido com várias etapas dependentes, use Builder. Se precisa trocar todo o conjunto de comportamentos conforme o mercado ou segmento do cliente, use Abstract Factory. Misturar os dois no mesmo módulo é receita para confusão na equipe.
Na prática, eu recomendo começar pequeno. Implemente apenas a família que mais muda no seu sistema. Se pagamentos são o único ponto de variaçao entre Brasil e exterior, comece com uma AbstractFactory voltada apenas a isso. Deixe frete e impostos para depois. Assim você valida o padrão com risco controlado e ganha confiança antes de estender para outras famílias.
Galeria
-
Padrão Abstract Factory - DevMedia -
Rem Soft Sistemas - Design Patterns na Prática - Parte II: Abstract Factory -
Abstract Factory -
Padrões Criacionais: Factory Method e Abstract Factory