Design Patterns

Factory Method

Define uma interface para criar objetos, deixando as subclasses decidirem qual instanciar

Conceito e intenção

Factory Method define uma interface (ou método abstrato) para criar objetos, mas delega a decisão de qual classe concreta instanciar às subclasses ou a um método especializado. O padrão remove o new ConcreteClass() espalhado pelo código de uso, concentrando a lógica de criação em um único lugar.

O problema motivador é o acoplamento com classes concretas. Quando código de negócio chama new EmailNotification() diretamente, ele está acoplado a essa implementação específica. Adicionar SMS exige alterar esse código. Testar requer a classe real. Factory Method inverte o controle: o código de uso fala apenas com a interface; a factory decide qual implementação entregar.

É importante distinguir três conceitos relacionados:

  • Simple Factory (não é um padrão GoF): método estático que centraliza new — útil, mas não extensível por herança
  • Factory Method (padrão GoF): método abstrato ou virtual em uma classe base que subclasses sobrescrevem para decidir o que criar
  • Abstract Factory: família de factories coordenadas para criar grupos de objetos relacionados

Estrutura

┌───────────────────────────────┐
│       Creator (abstract)      │
│  + factoryMethod(): Product   │◀── subclasses sobrescrevem
│  + operacao()                 │
└───────────────────────────────┘
          ▲               ▲
┌─────────┴──┐     ┌──────┴─────┐
│ConcreteA   │     │ConcreteB   │
│+factory()  │     │+factory()  │
└─────────┬──┘     └──────┬─────┘
          │               │
          ▼               ▼
   ProductA           ProductB
(ambos implementam Product)

Participantes:

  • Product — interface do objeto a ser criado
  • ConcreteProduct — implementação específica do produto
  • Creator — declara o factory method; pode ter implementação padrão
  • ConcreteCreator — sobrescreve o factory method para retornar tipo específico

Implementação Java — canais de notificação

// Product — interface do que a factory cria
public interface Notification {
    void send(String recipient, String message);
    String getChannelName();
}

// Produtos concretos
public class EmailNotification implements Notification {
    private final EmailClient emailClient;

    public EmailNotification(EmailClient emailClient) {
        this.emailClient = emailClient;
    }

    @Override
    public void send(String recipient, String message) {
        emailClient.sendMail(recipient, "Notificação", message);
    }

    @Override
    public String getChannelName() { return "email"; }
}

public class SmsNotification implements Notification {
    private final SmsGateway smsGateway;

    public SmsNotification(SmsGateway smsGateway) {
        this.smsGateway = smsGateway;
    }

    @Override
    public void send(String recipient, String message) {
        smsGateway.sendSms(recipient, message);
    }

    @Override
    public String getChannelName() { return "sms"; }
}

public class PushNotification implements Notification {
    private final PushService pushService;

    public PushNotification(PushService pushService) {
        this.pushService = pushService;
    }

    @Override
    public void send(String recipient, String message) {
        pushService.sendPush(recipient, PushMessage.of(message));
    }

    @Override
    public String getChannelName() { return "push"; }
}

// Factory Method (GoF puro) — subclasses definem o produto
public abstract class NotificationCreator {
    // Factory Method — subclasses implementam
    protected abstract Notification createNotification();

    // Lógica comum que usa o produto sem saber qual é
    public void notifyUser(String userId, String message) {
        Notification notification = createNotification();
        String recipient = userService.getContactFor(userId, notification.getChannelName());
        notification.send(recipient, message);
    }
}

public class EmailNotificationCreator extends NotificationCreator {
    private final EmailClient emailClient;

    public EmailNotificationCreator(EmailClient emailClient) {
        this.emailClient = emailClient;
    }

    @Override
    protected Notification createNotification() {
        return new EmailNotification(emailClient);
    }
}

public class SmsNotificationCreator extends NotificationCreator {
    private final SmsGateway smsGateway;

    public SmsNotificationCreator(SmsGateway smsGateway) {
        this.smsGateway = smsGateway;
    }

    @Override
    protected Notification createNotification() {
        return new SmsNotification(smsGateway);
    }
}

Simple Factory com Registry — extensível sem switch

Na prática, a variação com registro é mais comum e flexível que a hierarquia de creators:

public class NotificationFactory {
    private final Map<String, Supplier<Notification>> registry = new HashMap<>();

    // Registro em inicialização — nenhum canal hardcoded
    public NotificationFactory register(String channel, Supplier<Notification> supplier) {
        registry.put(channel, supplier);
        return this;
    }

    public Notification create(String channel) {
        Supplier<Notification> supplier = registry.get(channel.toLowerCase());
        if (supplier == null) {
            throw new IllegalArgumentException("Canal desconhecido: " + channel);
        }
        return supplier.get();
    }

    public boolean supports(String channel) {
        return registry.containsKey(channel.toLowerCase());
    }
}

// Configuração (ex: classe @Configuration do Spring)
NotificationFactory factory = new NotificationFactory()
    .register("email", () -> new EmailNotification(emailClient))
    .register("sms",   () -> new SmsNotification(smsGateway))
    .register("push",  () -> new PushNotification(pushService));

// Adicionar WhatsApp no futuro: factory.register("whatsapp", WhatsAppNotification::new)
// O AlertService não muda

// Serviço de uso — desacoplado das implementações
public class AlertService {
    private final NotificationFactory factory;

    public void sendAlert(String channel, String userId, String message) {
        Notification notification = factory.create(channel);
        notification.send(userId, message);
    }

    public void sendToAllChannels(String userId, String message) {
        List.of("email", "sms", "push").stream()
            .filter(factory::supports)
            .map(factory::create)
            .forEach(n -> n.send(userId, message));
    }
}

Abstract Factory — família de produtos relacionados

Quando precisar criar grupos coesos de objetos (ex: UI para diferentes plataformas):

// Abstract Factory
public interface NotificationBundle {
    Notification createConfirmationNotification();
    Notification createAlertNotification();
    Notification createMarketingNotification();
}

// Família para clientes premium (todos os canais)
public class PremiumNotificationBundle implements NotificationBundle {
    @Override
    public Notification createConfirmationNotification() { return new EmailNotification(emailClient); }
    @Override
    public Notification createAlertNotification()        { return new SmsNotification(smsGateway); }
    @Override
    public Notification createMarketingNotification()    { return new PushNotification(pushService); }
}

// Família para clientes básicos (somente email)
public class BasicNotificationBundle implements NotificationBundle {
    @Override
    public Notification createConfirmationNotification() { return new EmailNotification(emailClient); }
    @Override
    public Notification createAlertNotification()        { return new EmailNotification(emailClient); }
    @Override
    public Notification createMarketingNotification()    { return new NoopNotification(); }
}

Implementação TypeScript

// Product
interface Notification {
  send(recipient: string, message: string): Promise<void>;
  readonly channelName: string;
}

// Produtos concretos
class EmailNotification implements Notification {
  readonly channelName = 'email';

  constructor(private readonly emailClient: EmailClient) {}

  async send(recipient: string, message: string): Promise<void> {
    await this.emailClient.sendMail(recipient, 'Notificação', message);
  }
}

class SmsNotification implements Notification {
  readonly channelName = 'sms';

  constructor(private readonly smsGateway: SmsGateway) {}

  async send(recipient: string, message: string): Promise<void> {
    await this.smsGateway.sendSms(recipient, message);
  }
}

// Factory com registry
class NotificationFactory {
  private readonly registry = new Map<string, () => Notification>();

  register(channel: string, factory: () => Notification): this {
    this.registry.set(channel.toLowerCase(), factory);
    return this;
  }

  create(channel: string): Notification {
    const factory = this.registry.get(channel.toLowerCase());
    if (!factory) throw new Error(`Canal desconhecido: ${channel}`);
    return factory();
  }
}

// Uso funcional — factory como função simples
type NotificationFactory2 = (channel: string) => Notification;

const makeFactory = (deps: Dependencies): NotificationFactory2 => {
  const map: Record<string, () => Notification> = {
    email: () => new EmailNotification(deps.emailClient),
    sms:   () => new SmsNotification(deps.smsGateway),
  };
  return (channel) => {
    const fn = map[channel];
    if (!fn) throw new Error(`Canal: ${channel}`);
    return fn();
  };
};

No mundo real

JDBC DriverManager.getConnection(url) é um factory method clássico. A URL define qual driver usar (jdbc:postgresql://... vs jdbc:mysql://...); o método retorna uma Connection sem expor a classe concreta.

Spring @Bean methods são factory methods gerenciados pelo container:

@Bean
public DataSource dataSource() {
    // Spring chama esse método uma vez; o retorno é o produto
    return DataSourceBuilder.create().url(url).build();
}

Spring BeanFactory é literalmente uma AbstractFactory de beans — cria objetos por nome ou tipo, delegando ao container a decisão de qual implementação instanciar.

List.of(), Map.of(), Optional.of() — factory methods estáticos no SDK Java. Retornam implementações concretas sem expor os tipos (ImmutableCollections$List12, etc.).

LoggerFactory.getLogger(Class) do SLF4J — factory que retorna um logger concreto dependendo da implementação no classpath (Logback, Log4j2, etc.).

React createElement() / JSX — factory de elementos virtuais. React.createElement(Button, {onClick}, 'texto') retorna um objeto que o reconciliador saberá transformar em DOM real.


Quando usar

  • O código de uso não deve conhecer a classe concreta que recebe
  • O tipo do objeto a criar depende de configuração, parâmetros de runtime ou contexto
  • Você precisa adicionar novos tipos sem modificar código existente (Open/Closed Principle)
  • Múltiplos pontos do código criam instâncias do mesmo tipo — centralizar reduz duplicação

Quando NÃO usar

  • Quando há apenas uma implementação e não há plano de variar — injeção direta é mais simples
  • Quando a criação do objeto é trivial (new SimpleObject()) sem parâmetros ou variações
  • Quando a hierarquia de Creators torna o código mais complexo sem benefício real — simple factory (método estático) resolve o mesmo problema com menos cerimônia
  • Quando todas as variações são conhecidas em tempo de compilação e nunca vão crescer — enum com comportamento pode ser mais direto

Combinações com outros padrões

Factory Method + Strategy: A factory seleciona qual Strategy usar com base em parâmetros. O cliente obtém a Strategy correta sem conhecer as implementações.

Factory Method + Singleton: A factory em si pode ser singleton (um único ponto de criação), enquanto os produtos criados podem ou não ser singletons.

Factory Method + Builder: Use Factory Method para decidir qual Builder instanciar, então use o Builder para construir o objeto complexo:

public NotificationBuilder createBuilder(String channel) {
    return switch (channel) {
        case "email" -> new EmailNotificationBuilder(emailClient);
        case "sms"   -> new SmsNotificationBuilder(smsGateway);
        default -> throw new IllegalArgumentException(channel);
    };
}

Abstract Factory + Facade: Uma Facade pode usar uma Abstract Factory internamente para criar seus subsistemas sem expor os detalhes ao chamador.