O que é o padrão Factory (e por que ele existe)
Factory é uma forma de centralizar a criação de objetos quando instanciar diretamente com new começa a gerar problemas: construção complexa, escolha entre implementações, repetição de regras e acoplamento. Em vez de espalhar new ImplementacaoX(...) pelo código, você delega a criação para um ponto único e bem nomeado.
Quando usar (sinais práticos)
- Construção complexa: muitos parâmetros, validações, dependências, configuração condicional, defaults.
- Escolha entre implementações: dependendo de um tipo, canal, ambiente, feature flag, região, etc.
- Redução de acoplamento: o código cliente deveria depender de uma abstração (interface/classe base), não de classes concretas.
- Regras de criação repetidas: o mesmo
if/elsee os mesmos parâmetros aparecem em vários lugares. - Testabilidade: você quer trocar facilmente uma implementação real por uma fake/stub em testes.
Simple Factory vs Factory Method
Simple Factory (fábrica simples)
É uma classe (ou método) que recebe algum critério (por exemplo, um enum) e devolve a implementação correta. Não é um “padrão GoF” formal, mas é extremamente útil e comum.
Factory Method
É um padrão formal: uma classe base define um método de criação (factory method) e subclasses decidem qual produto concreto instanciar. Ele é útil quando a escolha do produto varia por “família” de criadores (por exemplo, por ambiente, por tenant, por integração).
Exemplo prático 1: Notificadores com Simple Factory
Objetivo: criar notificadores (Email, SMS, Push) sem espalhar new e sem o cliente conhecer classes concretas.
Passo 1: definir o contrato
public interface Notificador { void enviar(String destino, String mensagem);}Passo 2: implementar variações
public final class NotificadorEmail implements Notificador { private final ServicoEmail servico; public NotificadorEmail(ServicoEmail servico) { this.servico = servico; } @Override public void enviar(String destino, String mensagem) { servico.enviarEmail(destino, mensagem); }}public final class NotificadorSms implements Notificador { private final GatewaySms gateway; public NotificadorSms(GatewaySms gateway) { this.gateway = gateway; } @Override public void enviar(String destino, String mensagem) { gateway.enviarSms(destino, mensagem); }}public final class NotificadorPush implements Notificador { private final PushClient client; public NotificadorPush(PushClient client) { this.client = client; } @Override public void enviar(String destino, String mensagem) { client.enviarPush(destino, mensagem); }}Passo 3: criar um tipo para a escolha
public enum CanalNotificacao { EMAIL, SMS, PUSH }Passo 4: implementar a Factory (centralizando variações)
public final class NotificadorFactory { private final ServicoEmail servicoEmail; private final GatewaySms gatewaySms; private final PushClient pushClient; public NotificadorFactory(ServicoEmail servicoEmail, GatewaySms gatewaySms, PushClient pushClient) { this.servicoEmail = servicoEmail; this.gatewaySms = gatewaySms; this.pushClient = pushClient; } public Notificador criar(CanalNotificacao canal) { return switch (canal) { case EMAIL -> new NotificadorEmail(servicoEmail); case SMS -> new NotificadorSms(gatewaySms); case PUSH -> new NotificadorPush(pushClient); }; }}Passo 5: usar no cliente (sem conhecer concretos)
public final class CentralDeMensagens { private final NotificadorFactory factory; public CentralDeMensagens(NotificadorFactory factory) { this.factory = factory; } public void notificar(CanalNotificacao canal, String destino, String mensagem) { Notificador notificador = factory.criar(canal); notificador.enviar(destino, mensagem); }}O que melhorou
- O cliente depende de
Notificadore da factory, não deNotificadorEmail/NotificadorSms/NotificadorPush. - Se amanhã surgir
WHATSAPP, a mudança fica concentrada na factory (e na nova implementação). - Regras de criação (dependências, defaults) ficam em um lugar só.
Critérios de design: o que fica na Factory e o que fica no construtor
| Decisão | Colocar no construtor | Colocar na factory |
|---|---|---|
| Invariantes do objeto | Sim. Validações essenciais para o objeto existir corretamente. | Não como substituto. A factory pode pré-validar, mas o construtor deve garantir. |
| Escolha entre implementações | Não. Construtor não deveria decidir “qual classe eu sou”. | Sim. A factory decide qual implementação criar. |
| Defaults e configuração | Somente se forem intrínsecos ao tipo. | Sim, quando dependem de contexto (ambiente, canal, feature flag). |
| Montagem de dependências | Evite. Construtor deve receber dependências prontas. | Sim. A factory pode compor dependências e passá-las ao construtor. |
| Cache/reuso de instâncias | Não é papel do construtor. | Pode ser. A factory pode retornar singleton, pool, ou instância compartilhada quando fizer sentido. |
Regra prática: construtor garante consistência do objeto; factory orquestra variações e contexto de criação.
- Ouça o áudio com a tela desligada
- Ganhe Certificado após a conclusão
- + de 5000 cursos para você explorar!
Baixar o aplicativo
Testabilidade: como testar código com Factory
Teste do cliente (sem depender de implementações reais)
Uma vantagem de centralizar a criação é poder substituir a factory por uma versão de teste que devolve doubles previsíveis.
public final class NotificadorFactoryFake extends NotificadorFactory { private final Notificador fixo; public NotificadorFactoryFake(Notificador fixo) { super(null, null, null); this.fixo = fixo; } @Override public Notificador criar(CanalNotificacao canal) { return fixo; }}Alternativa mais simples: definir uma interface para a factory e injetar uma implementação fake.
public interface CriadorDeNotificador { Notificador criar(CanalNotificacao canal);}public final class NotificadorFactory implements CriadorDeNotificador { // mesma implementação de antes}Teste da factory (garantindo o mapeamento)
Teste a factory como uma unidade: dado um canal, retorna o tipo esperado, e injeta as dependências corretas.
// Exemplo conceitual (sem framework):NotificadorFactory factory = new NotificadorFactory(servicoEmail, gatewaySms, pushClient);Notificador n = factory.criar(CanalNotificacao.SMS);assert n instanceof NotificadorSms;Exemplo prático 2: Factory Method para escolher família de criação
Agora o critério não é apenas “qual canal”, mas “qual ambiente/integração” define uma família de objetos. Exemplo: em desenvolvimento você quer um notificador que apenas registra em log; em produção, notificador real.
Passo 1: Creator com factory method
public abstract class NotificadorCreator { public final Notificador obterNotificador(CanalNotificacao canal) { // template simples: validações comuns podem ficar aqui return criarNotificador(canal); } protected abstract Notificador criarNotificador(CanalNotificacao canal);}Passo 2: Criadores concretos
public final class NotificadorCreatorDev extends NotificadorCreator { @Override protected Notificador criarNotificador(CanalNotificacao canal) { return (destino, mensagem) -> System.out.println("[DEV] " + canal + ": " + destino + " - " + mensagem); }}public final class NotificadorCreatorProd extends NotificadorCreator { private final NotificadorFactory factory; public NotificadorCreatorProd(NotificadorFactory factory) { this.factory = factory; } @Override protected Notificador criarNotificador(CanalNotificacao canal) { return factory.criar(canal); }}Uso
NotificadorCreator creator = ambiente.equals("prod") ? new NotificadorCreatorProd(factoryReal) : new NotificadorCreatorDev();Notificador n = creator.obterNotificador(CanalNotificacao.EMAIL);n.enviar("user@dominio.com", "Olá!");Esse formato é útil quando você quer variar a estratégia de criação por contexto (ambiente, cliente, tenant), mantendo um ponto de extensão claro.
Armadilhas comuns (e como evitar)
- Factory “Deus”: uma factory gigante criando tudo do sistema. Prefira factories por domínio (ex.:
NotificadorFactory,ParserFactory), com responsabilidade coesa. - Factory escondendo regras de negócio: a factory deve decidir criação, não regras do tipo “quem pode receber notificação”.
- Excesso de parâmetros: se a factory recebe muitos parâmetros por chamada, talvez ela devesse ser um objeto com dependências no construtor e um método
criar(...)mais simples (como no exemplo). - Switch espalhado: se o
switchaparece em vários lugares, ele pertence a uma factory (ou a um registro/mapa de criadores).
Exercício: substituir new espalhado por uma factory bem nomeada
Cenário: você tem um serviço que calcula frete e escolhe a estratégia com new em vários pontos.
Código inicial (para refatorar)
public enum TipoFrete { ECONOMICO, EXPRESSO }public interface CalculadoraFrete { double calcular(double pesoKg, double distanciaKm);}public final class FreteEconomico implements CalculadoraFrete { @Override public double calcular(double pesoKg, double distanciaKm) { return 5.0 + (0.5 * pesoKg) + (0.1 * distanciaKm); }}public final class FreteExpresso implements CalculadoraFrete { @Override public double calcular(double pesoKg, double distanciaKm) { return 15.0 + (1.2 * pesoKg) + (0.25 * distanciaKm); }}public final class CheckoutService { public double cotar(TipoFrete tipo, double pesoKg, double distanciaKm) { CalculadoraFrete calc; if (tipo == TipoFrete.ECONOMICO) { calc = new FreteEconomico(); } else { calc = new FreteExpresso(); } return calc.calcular(pesoKg, distanciaKm); } public double recotar(TipoFrete tipo, double pesoKg, double distanciaKm) { // repetição do mesmo if/else em outro método CalculadoraFrete calc; if (tipo == TipoFrete.ECONOMICO) { calc = new FreteEconomico(); } else { calc = new FreteExpresso(); } return calc.calcular(pesoKg, distanciaKm); }}Tarefas
- Crie uma factory bem nomeada (ex.:
CalculadoraFreteFactoryouFreteCalculadoraFactory) com um métodocriar(TipoFrete tipo). - Remova o
if/elseduplicado doCheckoutService, usando a factory. - Adicione um novo tipo
RETIRADA_NA_LOJAque sempre retorna0e implemente a variação apenas adicionando uma nova classe e ajustando a factory. - Escreva um teste simples para garantir que
TipoFrete.EXPRESSOretorna uma instância deFreteExpresso(ou que calcula um valor esperado).
Dica de implementação (estrutura esperada)
public final class CalculadoraFreteFactory { public CalculadoraFrete criar(TipoFrete tipo) { return switch (tipo) { case ECONOMICO -> new FreteEconomico(); case EXPRESSO -> new FreteExpresso(); // case RETIRADA_NA_LOJA -> new FreteRetiradaNaLoja(); }; }}public final class CheckoutService { private final CalculadoraFreteFactory factory; public CheckoutService(CalculadoraFreteFactory factory) { this.factory = factory; } public double cotar(TipoFrete tipo, double pesoKg, double distanciaKm) { return factory.criar(tipo).calcular(pesoKg, distanciaKm); } public double recotar(TipoFrete tipo, double pesoKg, double distanciaKm) { return factory.criar(tipo).calcular(pesoKg, distanciaKm); }}