Dominando o Abstract Factory em Java 21: Do Básico ao Profissional

## Dominando o Abstract Factory em Java 21: Do Básico ao Profissional - por Thiago Moreira Bianeck

Olá, vamos mergulhar no mundo dos Design Patterns com um dos padrões criacionais mais úteis: o Abstract Factory. Vou te guiar através de um tutorial completo, desde a criação do projeto até a implementação final com testes!

🎯 O que é o Abstract Factory?

Imagine que você tem uma fábrica de carros. Essa fábrica não produz apenas um tipo de carro, mas diferentes famílias de veículos: carros populares, carros de luxo, carros esportivos. Cada família tem suas próprias características e componentes específicos.

Abstract Factory funciona exatamente assim! É um padrão que permite criar famílias de objetos relacionados sem especificar suas classes concretas. É como ter um “catálogo de fábricas” onde cada fábrica sabe produzir uma família completa de produtos.

🚀 Configurando o Ambiente

Passo 1: Criando o Projeto no IntelliJ

  1. Abra o IntelliJ IDEA
  2. Clique em “New Project”
  3. Selecione “Maven Archetype”
  4. Configure os seguintes campos:
    • Namenotification-system
    • Location: Escolha sua pasta preferida
    • Language: Java
    • Build System: Maven
    • JDK: 21 (certifique-se de ter o Java 21 instalado)
    • Archetypemaven-archetype-quickstart
  5. Clique em “Create”

Passo 2: Configurando o pom.xml

Substitua o conteúdo do pom.xml pelo seguinte:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
         http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.bianeck</groupId>
    <artifactId>notification-system</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <properties>
        <maven.compiler.source>21</maven.compiler.source>
        <maven.compiler.target>21</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <junit.version>5.10.1</junit.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>${junit.version}</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.11.0</version>
                <configuration>
                    
                    <target>21</target>
                    <compilerArgs>
                        <arg>--enable-preview</arg>
                    </compilerArgs>
                </configuration>
            </plugin>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-surefire-plugin</artifactId>
                <version>3.2.2</version>
                <configuration>
                    <argLine>--enable-preview</argLine>
                </configuration>
            </plugin>
        </plugins>
    </build>
</project>
Code language: HTML, XML (xml)

📝 Implementação Inicial (Sem Abstract Factory)

Vamos começar com uma implementação simples de um sistema de notificações. Imagine que nossa empresa precisa enviar notificações por diferentes canais: EmailSMS e Push Notification.

Passo 3: Criando as Classes Básicas

3.1 – Usando Records do Java 21

Primeiro, vamos criar um record para representar uma mensagem (novidade do Java 21):

Crie o arquivo src/main/java/com/bianeck/model/Message.java:

package com.bianeck.model;

/**
 * Record para representar uma mensagem
 * Records são uma feature do Java 14+ que simplifica classes de dados
 */
public record Message(
    String recipient,
    String subject,
    String content,
    Priority priority
) {
    
    public enum Priority {
        LOW, MEDIUM, HIGH, URGENT
    }
    
    // Construtor compacto - validação automática
    public Message {
        if (recipient == null || recipient.trim().isEmpty()) {
            throw new IllegalArgumentException("Destinatário não pode ser vazio");
        }
        if (content == null || content.trim().isEmpty()) {
            throw new IllegalArgumentException("Conteúdo não pode ser vazio");
        }
    }
    
    // Método personalizado
    public String getFormattedMessage() {
        return String.format("[%s] %s: %s", priority, subject, content);
    }
}
Code language: JavaScript (javascript)

3.2 – Classes de Notificação Básicas

Crie o arquivo src/main/java/com/bianeck/notification/EmailNotification.java:

package com.bianeck.notification;

import com.bianeck.model.Message;

public class EmailNotification {
    
    public void send(Message message) {
        System.out.println("📧 Enviando EMAIL:");
        System.out.println("Para: " + message.recipient());
        System.out.println("Assunto: " + message.subject());
        System.out.println("Mensagem: " + message.content());
        System.out.println("Prioridade: " + message.priority());
        System.out.println("✅ Email enviado com sucesso!\n");
        
        // Simula processamento
        simulateProcessing();
    }
    
    private void simulateProcessing() {
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}
Code language: JavaScript (javascript)

Crie o arquivo src/main/java/com/bianeck/notification/SmsNotification.java:

package com.bianeck.notification;

import com.bianeck.model.Message;

public class SmsNotification {
    
    public void send(Message message) {
        System.out.println("📱 Enviando SMS:");
        System.out.println("Para: " + message.recipient());
        System.out.println("Mensagem: " + message.content());
        System.out.println("Prioridade: " + message.priority());
        System.out.println("✅ SMS enviado com sucesso!\n");
        
        simulateProcessing();
    }
    
    private void simulateProcessing() {
        try {
            Thread.sleep(50);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}
Code language: JavaScript (javascript)

Crie o arquivo src/main/java/com/bianeck/notification/PushNotification.java:

package com.bianeck.notification;

import com.bianeck.model.Message;

public class PushNotification {
    
    public void send(Message message) {
        System.out.println("🔔 Enviando PUSH NOTIFICATION:");
        System.out.println("Para: " + message.recipient());
        System.out.println("Título: " + message.subject());
        System.out.println("Mensagem: " + message.content());
        System.out.println("Prioridade: " + message.priority());
        System.out.println("✅ Push notification enviado com sucesso!\n");
        
        simulateProcessing();
    }
    
    private void simulateProcessing() {
        try {
            Thread.sleep(30);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}
Code language: JavaScript (javascript)

3.3 – Classe Principal Inicial

Crie o arquivo src/main/java/com/bianeck/NotificationApp.java:

package com.bianeck;

import com.bianeck.model.Message;
import com.bianeck.notification.EmailNotification;
import com.bianeck.notification.PushNotification;
import com.bianeck.notification.SmsNotification;

public class NotificationApp {
    
    public static void main(String[] args) {
        // Criando algumas mensagens de teste
        var message1 = new Message(
            "usuario@email.com",
            "Bem-vindo!",
            "Obrigado por se cadastrar em nossa plataforma!",
            Message.Priority.MEDIUM
        );
        
        var message2 = new Message(
            "+55 46 99999-9999",
            "Código de Verificação",
            "Seu código é: 123456",
            Message.Priority.HIGH
        );
        
        var message3 = new Message(
            "user123",
            "Nova Mensagem",
            "Você recebeu uma nova mensagem!",
            Message.Priority.LOW
        );
        
        // Enviando notificações - IMPLEMENTAÇÃO INICIAL (problemática)
        EmailNotification emailService = new EmailNotification();
        SmsNotification smsService = new SmsNotification();
        PushNotification pushService = new PushNotification();
        
        System.out.println("=== SISTEMA DE NOTIFICAÇÕES V1.0 ===\n");
        
        emailService.send(message1);
        smsService.send(message2);
        pushService.send(message3);
    }
}
Code language: JavaScript (javascript)

Passo 4: Testando a Implementação Inicial

Crie o arquivo src/test/java/com/bianeck/NotificationAppTest.java:

package com.bianeck;

import com.bianeck.model.Message;
import com.bianeck.notification.EmailNotification;
import com.bianeck.notification.PushNotification;
import com.bianeck.notification.SmsNotification;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.BeforeEach;
import static org.junit.jupiter.api.Assertions.*;

class NotificationAppTest {
    
    private Message testMessage;
    
    @BeforeEach
    void setUp() {
        testMessage = new Message(
            "test@email.com",
            "Teste",
            "Mensagem de teste",
            Message.Priority.MEDIUM
        );
    }
    
    @Test
    @DisplayName("Deve enviar email sem erro")
    void shouldSendEmailWithoutError() {
        EmailNotification emailService = new EmailNotification();
        
        // Não deve lançar exceção
        assertDoesNotThrow(() -> emailService.send(testMessage));
    }
    
    @Test
    @DisplayName("Deve enviar SMS sem erro")
    void shouldSendSmsWithoutError() {
        SmsNotification smsService = new SmsNotification();
        
        assertDoesNotThrow(() -> smsService.send(testMessage));
    }
    
    @Test
    @DisplayName("Deve enviar Push sem erro")
    void shouldSendPushWithoutError() {
        PushNotification pushService = new PushNotification();
        
        assertDoesNotThrow(() -> pushService.send(testMessage));
    }
    
    @Test
    @DisplayName("Deve validar mensagem com dados inválidos")
    void shouldValidateMessageWithInvalidData() {
        // Testando validação do record
        assertThrows(IllegalArgumentException.class, () -> {
            new Message("", "Teste", "Conteúdo", Message.Priority.LOW);
        });
        
        assertThrows(IllegalArgumentException.class, () -> {
            new Message("test@email.com", "Teste", "", Message.Priority.LOW);
        });
    }
}
Code language: JavaScript (javascript)

Passo 5: Executando os Testes

No terminal do IntelliJ, execute:

mvn clean test

🚨 Problemas da Implementação Atual

Agora vamos analisar os problemas da nossa implementação atual:

❌ Problemas Identificados:

  1. Acoplamento Forte: Nossa classe principal está “casada” com implementações específicas
  2. Violação do Princípio Aberto/Fechado: Para adicionar um novo tipo de notificação, precisamos modificar código existente
  3. Código Duplicado: Cada classe tem lógica similar de processamento
  4. Falta de Flexibilidade: Não conseguimos trocar implementações facilmente
  5. Teste Difícil: Difícil fazer mocks e testes unitários isolados

🔧 Analogia dos Problemas:

Imagine que você tem uma oficina mecânica. Atualmente, você tem um mecânico que só sabe mexer em carros da Ford, outro que só sabe mexer em carros da Volkswagen, e outro que só sabe mexer em carros da Toyota.

O problema é que você (o dono da oficina) precisa conhecer cada mecânico específico e chamá-los individualmente. Se aparecer um carro da Honda, você precisa contratar um novo mecânico E modificar seu processo de trabalho.

🏭 Implementando o Abstract Factory

Agora vamos refatorar nosso código usando o padrão Abstract Factory. Será como criar um “sistema de oficina universal” onde qualquer mecânico pode consertar qualquer carro, seguindo um padrão estabelecido.

Passo 6: Criando as Interfaces

6.1 – Interface Notification

Crie o arquivo src/main/java/com/bianeck/factory/Notification.java:

package com.bianeck.factory;

import com.bianeck.model.Message;

/**
 * Interface base para todas as notificações
 * Usando sealed interface (Java 17+) para controle de hierarquia
 */
public sealed interface Notification 
    permits EmailNotificationImpl, SmsNotificationImpl, PushNotificationImpl {
    
    void send(Message message);
    String getType();
    int getProcessingTime();
}
Code language: JavaScript (javascript)

6.2 – Interface NotificationFactory (Abstract Factory)

Crie o arquivo src/main/java/com/bianeck/factory/NotificationFactory.java:

package com.bianeck.factory;

import com.bianeck.model.Message;

/**
 * Abstract Factory para criação de notificações
 * Esta é nossa "fábrica de fábricas"
 */
public interface NotificationFactory {
    
    Notification createNotification();
    
    // Método de conveniência usando Pattern Matching (Java 21)
    static NotificationFactory getFactory(NotificationType type) {
        return switch (type) {
            case EMAIL -> new EmailNotificationFactory();
            case SMS -> new SmsNotificationFactory();
            case PUSH -> new PushNotificationFactory();
        };
    }
    
    // Enum para tipos de notificação
    enum NotificationType {
        EMAIL, SMS, PUSH
    }
}
Code language: PHP (php)

Passo 7: Implementando as Classes Concretas

7.1 – Implementações das Notificações

Crie o arquivo src/main/java/com/bianeck/factory/EmailNotificationImpl.java:

package com.bianeck.factory;

import com.bianeck.model.Message;

public final class EmailNotificationImpl implements Notification {
    
    @Override
    public void send(Message message) {
        System.out.println("📧 Enviando EMAIL (via Factory):");
        System.out.println("Para: " + message.recipient());
        System.out.println("Assunto: " + message.subject());
        System.out.println("Mensagem: " + message.content());
        System.out.println("Prioridade: " + message.priority());
        System.out.println("✅ Email enviado com sucesso!\n");
        
        simulateProcessing();
    }
    
    @Override
    public String getType() {
        return "EMAIL";
    }
    
    @Override
    public int getProcessingTime() {
        return 100; // ms
    }
    
    private void simulateProcessing() {
        try {
            Thread.sleep(getProcessingTime());
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}
Code language: PHP (php)

Crie o arquivo src/main/java/com/bianeck/factory/SmsNotificationImpl.java:

package com.bianeck.factory;

import com.bianeck.model.Message;

public final class SmsNotificationImpl implements Notification {
    
    @Override
    public void send(Message message) {
        System.out.println("📱 Enviando SMS (via Factory):");
        System.out.println("Para: " + message.recipient());
        System.out.println("Mensagem: " + message.content());
        System.out.println("Prioridade: " + message.priority());
        System.out.println("✅ SMS enviado com sucesso!\n");
        
        simulateProcessing();
    }
    
    @Override
    public String getType() {
        return "SMS";
    }
    
    @Override
    public int getProcessingTime() {
        return 50; // ms
    }
    
    private void simulateProcessing() {
        try {
            Thread.sleep(getProcessingTime());
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}
Code language: PHP (php)

Crie o arquivo src/main/java/com/bianeck/factory/PushNotificationImpl.java:

package com.bianeck.factory;

import com.bianeck.model.Message;

public final class PushNotificationImpl implements Notification {
    
    @Override
    public void send(Message message) {
        System.out.println("🔔 Enviando PUSH (via Factory):");
        System.out.println("Para: " + message.recipient());
        System.out.println("Título: " + message.subject());
        System.out.println("Mensagem: " + message.content());
        System.out.println("Prioridade: " + message.priority());
        System.out.println("✅ Push notification enviado com sucesso!\n");
        
        simulateProcessing();
    }
    
    @Override
    public String getType() {
        return "PUSH";
    }
    
    @Override
    public int getProcessingTime() {
        return 30; // ms
    }
    
    private void simulateProcessing() {
        try {
            Thread.sleep(getProcessingTime());
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        }
    }
}
Code language: PHP (php)

7.2 – Implementações das Fábricas Concretas

Crie o arquivo src/main/java/com/bianeck/factory/EmailNotificationFactory.java:

package com.bianeck.factory;

public class EmailNotificationFactory implements NotificationFactory {
    
    @Override
    public Notification createNotification() {
        return new EmailNotificationImpl();
    }
}
Code language: PHP (php)

Crie o arquivo src/main/java/com/bianeck/factory/SmsNotificationFactory.java:

package com.bianeck.factory;

public class SmsNotificationFactory implements NotificationFactory {
    
    @Override
    public Notification createNotification() {
        return new SmsNotificationImpl();
    }
}
Code language: PHP (php)

Crie o arquivo src/main/java/com/bianeck/factory/PushNotificationFactory.java:

package com.bianeck.factory;

public class PushNotificationFactory implements NotificationFactory {
    
    @Override
    public Notification createNotification() {
        return new PushNotificationImpl();
    }
}
Code language: PHP (php)

Passo 8: Criando o Serviço de Notificação

Crie o arquivo src/main/java/com/bianeck/service/NotificationService.java:

package com.bianeck.service;

import com.bianeck.factory.Notification;
import com.bianeck.factory.NotificationFactory;
import com.bianeck.model.Message;

import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * Serviço principal de notificações usando Abstract Factory
 * Demonstra o poder do padrão na prática
 */
public class NotificationService {
    
    private final ExecutorService executor;
    
    public NotificationService() {
        this.executor = Executors.newVirtualThreadPerTaskExecutor(); // Java 21!
    }
    
    /**
     * Envia notificação usando uma fábrica específica
     */
    public void sendNotification(NotificationFactory factory, Message message) {
        Notification notification = factory.createNotification();
        notification.send(message);
    }
    
    /**
     * Envia notificação de forma assíncrona
     */
    public CompletableFuture<Void> sendNotificationAsync(
            NotificationFactory factory, Message message) {
        
        return CompletableFuture.runAsync(() -> {
            Notification notification = factory.createNotification();
            notification.send(message);
        }, executor);
    }
    
    /**
     * Envia múltiplas notificações em paralelo
     */
    public CompletableFuture<Void> sendBulkNotifications(
            List<NotificationFactory> factories, Message message) {
        
        List<CompletableFuture<Void>> futures = factories.stream()
            .map(factory -> sendNotificationAsync(factory, message))
            .toList();
        
        return CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
    }
    
    public void shutdown() {
        executor.shutdown();
    }
}
Code language: PHP (php)

Passo 9: Aplicação Final Refatorada

Substitua o conteúdo do arquivo src/main/java/com/bianeck/NotificationApp.java:

package com.bianeck;

import com.bianeck.factory.NotificationFactory;
import com.bianeck.factory.NotificationFactory.NotificationType;
import com.bianeck.model.Message;
import com.bianeck.service.NotificationService;

import java.util.List;
import java.util.concurrent.CompletableFuture;

public class NotificationApp {
    
    public static void main(String[] args) {
        System.out.println("=== SISTEMA DE NOTIFICAÇÕES V2.0 (Abstract Factory) ===\n");
        
        // Criando o serviço
        NotificationService service = new NotificationService();
        
        // Criando mensagens de teste
        var welcomeMessage = new Message(
            "usuario@email.com",
            "Bem-vindo à plataforma!",
            "Obrigado por se cadastrar. Explore nossos recursos!",
            Message.Priority.MEDIUM
        );
        
        var securityMessage = new Message(
            "+55 46 99999-9999",
            "Código de Segurança",
            "Seu código de verificação é: 742159",
            Message.Priority.HIGH
        );
        
        var promotionMessage = new Message(
            "user123",
            "Promoção Especial",
            "Desconto de 20% em todos os produtos!",
            Message.Priority.LOW
        );
        
        // Demonstração 1: Envio individual usando pattern matching
        System.out.println(">>> Demonstração 1: Envio Individual <<<");
        
        var emailFactory = NotificationFactory.getFactory(NotificationType.EMAIL);
        var smsFactory = NotificationFactory.getFactory(NotificationType.SMS);
        var pushFactory = NotificationFactory.getFactory(NotificationType.PUSH);
        
        service.sendNotification(emailFactory, welcomeMessage);
        service.sendNotification(smsFactory, securityMessage);
        service.sendNotification(pushFactory, promotionMessage);
        
        // Demonstração 2: Envio assíncrono
        System.out.println(">>> Demonstração 2: Envio Assíncrono <<<");
        
        var urgentMessage = new Message(
            "admin@empresa.com",
            "Alerta do Sistema",
            "Sistema voltou ao funcionamento normal",
            Message.Priority.URGENT
        );
        
        CompletableFuture<Void> asyncTask = service.sendNotificationAsync(
            NotificationFactory.getFactory(NotificationType.EMAIL), 
            urgentMessage
        );
        
        // Fazendo outra coisa enquanto processa
        System.out.println("⏳ Processando outras tarefas enquanto envia...");
        
        // Aguardando conclusão
        asyncTask.join();
        System.out.println("✅ Notificação assíncrona concluída!\n");
        
        // Demonstração 3: Envio em lote (múltiplos canais)
        System.out.println(">>> Demonstração 3: Envio em Lote <<<");
        
        var broadcastMessage = new Message(
            "todos@empresa.com",
            "Manutenção Programada",
            "Sistema ficará indisponível das 02:00 às 04:00",
            Message.Priority.HIGH
        );
        
        List<NotificationFactory> allFactories = List.of(
            NotificationFactory.getFactory(NotificationType.EMAIL),
            NotificationFactory.getFactory(NotificationType.SMS),
            NotificationFactory.getFactory(NotificationType.PUSH)
        );
        
        CompletableFuture<Void> bulkTask = service.sendBulkNotifications(allFactories, broadcastMessage);
        bulkTask.join();
        
        System.out.println("✅ Todas as notificações em lote foram enviadas!\n");
        
        // Demonstração 4: Flexibilidade do padrão
        System.out.println(">>> Demonstração 4: Flexibilidade <<<");
        demonstrateFlexibility(service);
        
        // Limpeza
        service.shutdown();
        System.out.println("🏁 Aplicação finalizada!");
    }
    
    /**
     * Demonstra como é fácil mudar comportamentos com Abstract Factory
     */
    private static void demonstrateFlexibility(NotificationService service) {
        var testMessage = new Message(
            "teste@email.com",
            "Teste de Flexibilidade",
            "Esta mensagem demonstra a flexibilidade do padrão Abstract Factory",
            Message.Priority.LOW
        );
        
        // Simulando escolha dinâmica baseada em condições
        NotificationType chosenType = selectNotificationTypeBasedOnConditions();
        
        System.out.println("🎯 Tipo selecionado dinamicamente: " + chosenType);
        
        NotificationFactory factory = NotificationFactory.getFactory(chosenType);
        service.sendNotification(factory, testMessage);
    }
    
    /**
     * Simula lógica de negócio para escolher tipo de notificação
     */
    private static NotificationType selectNotificationTypeBasedOnConditions() {
        // Simulando diferentes condições de negócio
        int hourOfDay = java.time.LocalTime.now().getHour();
        
        return switch (hourOfDay) {
            case 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18 -> NotificationType.EMAIL;
            case 19, 20, 21, 22 -> NotificationType.PUSH;
            default -> NotificationType.SMS;
        };
    }
}
Code language: JavaScript (javascript)

Passo 10: Testes Completos

Crie o arquivo src/test/java/com/bianeck/factory/NotificationFactoryTest.java:

package com.bianeck.service;

import com.bianeck.factory.NotificationFactory;
import com.bianeck.model.Message;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;

import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

import static org.junit.jupiter.api.Assertions.*;

class NotificationServiceTest {
    
    private NotificationService service;
    private Message testMessage;
    
    @BeforeEach
    void setUp() {
        service = new NotificationService();
        testMessage = new Message(
            "test@email.com",
            "Teste",
            "Mensagem de teste",
            Message.Priority.MEDIUM
        );
    }
    
    @AfterEach
    void tearDown() {
        service.shutdown();
    }
    
    @Test
    @DisplayName("Deve enviar notificação síncrona")
    void shouldSendSynchronousNotification() {
        // Arrange
        NotificationFactory factory = NotificationFactory.getFactory(
            NotificationFactory.NotificationType.EMAIL
        );
        
        // Act & Assert
        assertDoesNotThrow(() -> service.sendNotification(factory, testMessage));
    }
    
    @Test
    @DisplayName("Deve enviar notificação assíncrona")
    void shouldSendAsynchronousNotification() throws Exception {
        // Arrange
        NotificationFactory factory = NotificationFactory.getFactory(
            NotificationFactory.NotificationType.SMS
        );
        
        // Act
        CompletableFuture<Void> future = service.sendNotificationAsync(factory, testMessage);
        
        // Assert
        assertNotNull(future);
        assertDoesNotThrow(() -> future.get(5, TimeUnit.SECONDS));
        assertTrue(future.isDone());
    }
    
    @Test
    @DisplayName("Deve enviar notificações em lote")
    void shouldSendBulkNotifications() throws Exception {
        // Arrange
        List<NotificationFactory> factories = List.of(
            NotificationFactory.getFactory(NotificationFactory.NotificationType.EMAIL),
            NotificationFactory.getFactory(NotificationFactory.NotificationType.SMS),
            NotificationFactory.getFactory(NotificationFactory.NotificationType.PUSH)
        );
        
        // Act
        CompletableFuture<Void> future = service.sendBulkNotifications(factories, testMessage);
        
        // Assert
        assertNotNull(future);
        assertDoesNotThrow(() -> future.get(10, TimeUnit.SECONDS));
        assertTrue(future.isDone());
    }
}
Code language: JavaScript (javascript)
package com.bianeck.factory;

import com.bianeck.model.Message;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;

import static org.junit.jupiter.api.Assertions.*;

class NotificationFactoryTest {
    
    private Message testMessage;
    
    @BeforeEach
    void setUp() {
        testMessage = new Message(
            "test@email.com",
            "Teste",
            "Mensagem de teste",
            Message.Priority.MEDIUM
        );
    }
    
    @ParameterizedTest
    @EnumSource(NotificationFactory.NotificationType.class)
    @DisplayName("Deve criar notificação para todos os tipos")
    void shouldCreateNotificationForAllTypes(NotificationFactory.NotificationType type) {
        // Arrange
        NotificationFactory factory = NotificationFactory.getFactory(type);
        
        // Act
        Notification notification = factory.createNotification();
        
        // Assert
        assertNotNull(notification);
        assertEquals(type.name(), notification.getType());
        assertTrue(notification.getProcessingTime() > 0);
    }
    
    @Test
    @DisplayName("Deve criar factory de email")
    void shouldCreateEmailFactory() {
        // Arrange & Act
        NotificationFactory factory = NotificationFactory.getFactory(
            NotificationFactory.NotificationType.EMAIL
        );
        Notification notification = factory.createNotification();
        
        // Assert
        assertInstanceOf(EmailNotificationImpl.class, notification);
        assertEquals("EMAIL", notification.getType());
        assertEquals(100, notification.getProcessingTime());
    }
    
    @Test
    @DisplayName("Deve criar factory de SMS")
    void shouldCreateSmsFactory() {
        // Arrange & Act
        NotificationFactory factory = NotificationFactory.getFactory(
            NotificationFactory.NotificationType.SMS
        );
        Notification notification = factory.createNotification();
        
        // Assert
        assertInstanceOf(SmsNotificationImpl.class, notification);
        assertEquals("SMS", notification.getType());
        assertEquals(50, notification.getProcessingTime());
    }
    
    @Test
    @DisplayName("Deve criar factory de Push")
    void shouldCreatePushFactory() {
        // Arrange & Act
        NotificationFactory factory = NotificationFactory.getFactory(
            NotificationFactory.NotificationType.PUSH
        );
        Notification notification = factory.createNotification();
        
        // Assert
        assertInstanceOf(PushNotificationImpl.class, notification);
        assertEquals("PUSH", notification.getType());
        assertEquals(30, notification.getProcessingTime());
    }
    
    @Test
    @DisplayName("Deve enviar notificação sem erro")
    void shouldSendNotificationWithoutError() {
        // Arrange
        NotificationFactory factory = NotificationFactory.getFactory(
            NotificationFactory.NotificationType.EMAIL
        );
        Notification notification = factory.createNotification();
        
        // Act & Assert
        assertDoesNotThrow(() -> notification.send(testMessage));
    }
}
Code language: JavaScript (javascript)

Crie o arquivo src/test/java/com/bianeck/service/NotificationServiceTest.java:

Passo 11: Executando Tudo

Execute os testes:

mvn clean test

Execute a aplicação:

mvn clean compile exec:java -Dexec.mainClass="com.bianeck.NotificationApp"
Code language: JavaScript (javascript)

🎯 Vantagens Obtidas com Abstract Factory

✅ Benefícios Alcançados:

  1. Desacoplamento: O código cliente não conhece implementações específicas
  2. Flexibilidade: Fácil trocar famílias de objetos
  3. Extensibilidade: Adicionar novos tipos sem modificar código existente
  4. Testabilidade: Fácil criar mocks e testes isolados
  5. Consistência: Garante que objetos de uma família sejam usados juntos
  6. Manutenibilidade: Código mais limpo e organizado

🔧 Analogia dos Benefícios:

Agora nossa oficina mecânica tem um padrão universal:

  • Qualquer mecânico pode consertar qualquer carro
  • Você (dono) só precisa dizer “preciso de um mecânico” sem se preocupar com detalhes
  • Adicionar novos tipos de carros é fácil: só precisa treinar os mecânicas no novo padrão
  • Cada mecânico segue o mesmo protocolo, garantindo qualidade consistente

🚀 Recursos do Java 21 Utilizados

1. Records

public record Message(String recipient, String subject, String content, Priority priority) {
    // Validação compacta e métodos automáticos
}
Code language: JavaScript (javascript)

2. Sealed Interfaces

public sealed interface Notification permits EmailNotificationImpl, SmsNotificationImpl, PushNotificationImpl {
    // Controle rígido de hierarquia
}
Code language: PHP (php)

3. Pattern Matching com Switch

static NotificationFactory getFactory(NotificationType type) {
    return switch (type) {
        case EMAIL -> new EmailNotificationFactory();
        case SMS -> new SmsNotificationFactory();
        case PUSH -> new PushNotificationFactory();
    };
}
Code language: JavaScript (javascript)

4. Virtual Threads

private final ExecutorService executor = Executors.newVirtualThreadPerTaskExecutor();
Code language: PHP (php)

5. Text Blocks (para configurações futuras)

String config = """
    {
        "email": {
            "smtp": "smtp.gmail.com",
            "port": 587
        }
    }
    """;
Code language: PHP (php)

📋 Resumo Final

Abstract Factory é como ter um “catálogo de receitas” na cozinha. Cada receita (factory) sabe como preparar uma família completa de pratos (objetos relacionados). Você não precisa se preocupar com os detalhes de cada prato – apenas escolhe a receita e ela cuida de tudo!

🎯 Quando Usar Abstract Factory:

  • Famílias de objetos relacionados: Quando você tem grupos de objetos que trabalham juntos
  • Flexibilidade de implementação: Quando precisa trocar famílias inteiras de objetos
  • Isolamento de criação: Quando quer esconder a lógica de criação do cliente
  • Consistência: Quando precisa garantir que objetos compatíveis sejam usados juntos

🏆 Principais Lições:

  1. Abstraia a criação de objetos para ganhar flexibilidade
  2. Use interfaces para definir contratos claros
  3. Implemente factories para cada família de objetos
  4. Aproveite recursos modernos do Java para código mais limpo
  5. Teste tudo para garantir que o padrão não quebrou funcionalidades

Agora você tem uma base sólida para aplicar o Abstract Factory em seus projetos reais! 🚀


Próximos Passos Sugeridos:

  • Experimente adicionar um novo tipo de notificação (ex: Slack)
  • Implemente diferentes “sabores” de cada notificação (ex: Email HTML vs Text)
  • Adicione configurações específicas para cada factory
  • Explore outros padrões criacionais (Builder, Singleton, etc.)

Bons estudos! 👨‍💻✨

Rolar para cima