Skip to the content.

📚 Padrões de Projeto, Arquiteturas e Boas Práticas

🛠️ Padrões de Projeto

Padrões de projeto são soluções reutilizáveis para problemas comuns no desenvolvimento de software. Eles ajudam a criar um código mais estruturado, de fácil manutenção e escalável.

1. Factory Method

Define uma interface para a criação de objetos, mas permite que as subclasses decidam qual classe instanciar. É útil para desacoplar o código cliente das classes concretas.

// Interface do Produto
interface Produto {
    void criar();
}

// Implementação Concreta do Produto
class ProdutoA implements Produto {
    public void criar() {
        System.out.println("Produto A criado");
    }
}

// Fábrica que cria o produto
class FabricaDeProdutos {
    public static Produto criarProduto(String tipo) {
        if (tipo.equals("A")) {
            return new ProdutoA();
        }
        throw new IllegalArgumentException("Tipo de produto desconhecido");
    }
}

classDiagram
    class "Produto" <<interface>> {
        +criar()
    }
    class "ProdutoA" {
        +criar()
    }
    class "FabricaDeProdutos" {
        +criarProduto("tipo")
    }

    "ProdutoA" ..|> "Produto"
    "FabricaDeProdutos" ..> "Produto" : cria
    

2. Singleton

Garante que uma classe tenha apenas uma instância e fornece um ponto de acesso global a ela. Ideal para gerenciar recursos compartilhados, como configurações ou conexões de banco de dados.

public class Configuracao {
    private static Configuracao instancia;

    private Configuracao() {
        // Construtor privado para evitar instanciação externa
    }
    
    public static Configuracao getInstancia() {
        if (instancia == null) {
            instancia = new Configuracao();
        }
        return instancia;
    }
}
classDiagram
    Configuracao : -Configuracao()
    Configuracao : +getInstancia()$
    Configuracao o-- "instancia" Configuracao

3. Observer

Define uma dependência um-para-muitos entre objetos. Quando o estado de um objeto (o “sujeito”) muda, todos os seus dependentes (os “observadores”) são notificados e atualizados automaticamente.

// Interface do Observador
interface Observador {
    void atualizar(String mensagem);
}

// Observador Concreto
class Usuario implements Observador {
    private String nome;
    public Usuario(String nome) { this.nome = nome; }
    public void atualizar(String mensagem) {
        System.out.println(nome + " recebeu a notificação: " + mensagem);
    }
}

// Sujeito (ou Notificador)
class Notificador {
    private List<Observador> observadores = new ArrayList<>();
    
    public void adicionar(Observador o) { 
        observadores.add(o); 
    }
    
    public void notificarTodos(String mensagem) {
        for (Observador o : observadores) {
            o.atualizar(mensagem);
        }
    }
}
classDiagram
    <<interface>> Observador
    Observador : +atualizar(String mensagem)
    Usuario ..|> Observador
    Notificador o-- "observadores" Observador
    Notificador : +adicionar(Observador o)
    Notificador : +notificarTodos(String mensagem)

4. Decorator

Permite adicionar novas funcionalidades a um objeto dinamicamente, envolvendo-o em um objeto “decorador”. É uma alternativa flexível à herança.

// Componente
interface Cafe {
    String getDescricao();
}

// Componente Concreto
class CafeSimples implements Cafe {
    public String getDescricao() { 
        return "Café Simples"; 
    }
}

// Decorator
class LeiteDecorator implements Cafe {
    private Cafe cafe;
    public LeiteDecorator(Cafe cafe) { 
        this.cafe = cafe; 
    }
    public String getDescricao() { 
        return cafe.getDescricao() + ", com leite"; 
    }
}
classDiagram
    <<interface>> Cafe
    Cafe : +getDescricao()
    CafeSimples ..|> Cafe
    LeiteDecorator ..|> Cafe
    LeiteDecorator o-- Cafe

5. Adapter

Converte a interface de uma classe em outra que o cliente espera, permitindo que classes com interfaces incompatíveis trabalhem juntas.

// Classe existente com interface incompatível
class TomadaAntiga {
    public void ligarNaTomadaDeDoisPinos() {
        System.out.println("Ligado na tomada de dois pinos.");
    }
}

// Adaptador
class AdaptadorTomada {
    private TomadaAntiga tomada;
    public AdaptadorTomada(TomadaAntiga tomada) { 
        this.tomada = tomada; 
    }
    public void ligarNaTomadaDeTresPinos() {
        tomada.ligarNaTomadaDeDoisPinos(); // Adapta a chamada
    }
}
classDiagram
    TomadaAntiga : +ligarNaTomadaDeDoisPinos()
    AdaptadorTomada : +ligarNaTomadaDeTresPinos()
    AdaptadorTomada o-- TomadaAntiga

🏗️ Arquiteturas

A arquitetura define a estrutura de um sistema, organizando seus componentes de forma escalável e manutenível.

1. Monolito vs. Microservices

2. DDD (Domain-Driven Design)

Abordagem que foca em modelar o software para corresponder ao domínio de negócio. Utiliza uma linguagem ubíqua e conceitos como Entidades, Agregados e Repositórios para criar um modelo rico e expressivo.

3. Arquitetura Hexagonal (Portas e Adaptadores)

Separa a lógica de negócios (o “hexágono”) da infraestrutura (banco de dados, UI, APIs externas). A comunicação ocorre por meio de Portas (interfaces) e Adaptadores (implementações), o que torna o sistema desacoplado e mais fácil de testar.


✔️ Testes e Qualidade

Testes automatizados são fundamentais para garantir a confiabilidade e a robustez do código.

1. JUnit

Principal framework para testes unitários em Java. Permite verificar se cada unidade de código (método ou classe) funciona como esperado.

import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;

class CalculadoraTest {
    @Test
    void testSoma() {
        assertEquals(4, Calculadora.somar(2, 2));
    }
}

2. Mockito

Framework para criar “mocks” (objetos simulados) que imitam o comportamento de dependências reais, permitindo testar uma unidade de forma isolada.

import static org.mockito.Mockito.when;

// Exemplo de uso
when(repositorio.buscarPorId(1L)).thenReturn(new Cliente("João"));

3. Testes de Integração

Verificam a interação entre diferentes componentes do sistema, como a integração da lógica de negócios com o banco de dados. No Spring, @SpringBootTest é usado para carregar o contexto da aplicação.


🗄️ Banco de Dados

Sistemas de gerenciamento de banco de dados (SGBDs) são usados para armazenar e recuperar os dados da aplicação.


✨ Boas Práticas

Princípios que guiam o desenvolvimento de um código limpo, eficiente e de fácil manutenção.


🏁 Conclusão

Este guia apresentou conceitos essenciais de padrões de projeto, arquiteturas, testes e boas práticas. Dominar esses tópicos é fundamental para construir software de alta qualidade, robusto e escalável.


🚀 ricardotecpro.github.io