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.