DEV Community

Jhonathan dos reis
Jhonathan dos reis

Posted on

Separation of Concerns em Java: O Jeito Simples

É difícil aceitar a verdade. Que sua aplicação Java com mais de 2000 linhas está toda escrita em uma única classe... mas você já sabe disso, ou pelo menos espero que saiba. Eu sei que você já tentou torná-la mais compacta, mas as coisas simplesmente não estão fazendo sentido.

É aqui que entra um princípio que transcende tempo e espaço, que nos ajuda em todos os aspectos da nossa vida: Separation of Concerns (Separação de Responsabilidades) ou SoC.

Eu sei que pode parecer intimidante, mas não se preocupe - o Tio T está aqui e vou tornar tudo o mais simples possível para o seu prazer.

Então hoje vamos passar por estes pontos:

  • O que é Separation of Concerns?
  • Coesão e Acoplamento
  • Por que funciona?
  • Exemplos práticos em Java
  • Conclusão

Antes de começarmos, fique confortável, pegue sua bebida favorita e coloque seus óculos inteligentes. Vamos começar!

O que é Separation of Concerns?

Como todo bom post técnico precisa de uma citação, aqui está algo que copiei da Wikipedia:

"Em ciência da computação, separation of concerns é um princípio de design para separar um programa de computador em seções distintas, de modo que cada seção aborde uma preocupação separada."

Ok, agora que as formalidades acabaram, deixe-me explicar de forma simples.

Separation of Concerns é um princípio universal que praticamente todas as pessoas aplicam. Não estou falando apenas de desenvolvimento de software - ele se aplica a muitos outros campos também.

Um Exemplo do Mundo Real

Imagine sua cozinha. Você não guarda talheres no mesmo lugar que produtos de limpeza, certo? Você tem gavetas separadas para:

  • Talheres
  • Panelas
  • Temperos
  • Produtos de limpeza

Por quê? Porque cada categoria tem uma responsabilidade diferente, e misturá-las tornaria sua vida um caos.

Como SoC se Aplica ao Java?

Em Java, aplicamos SoC em vários níveis:

1. Nível de Arquitetura - Padrão MVC

// Model - Responsável pelos dados
public class Usuario {
    private Long id;
    private String nome;
    private String email;

    // getters e setters
}

// View - Responsável pela apresentação (JSP exemplo)
<%@ page contentType="text/html;charset=UTF-8" %>
<html>
<body>
    <h1>Bem-vindo, ${usuario.nome}!</h1>
</body>
</html>

// Controller - Responsável pela lógica de controle
@RestController
@RequestMapping("/usuarios")
public class UsuarioController {
    @Autowired
    private UsuarioService service;

    @GetMapping("/{id}")
    public Usuario buscarUsuario(@PathVariable Long id) {
        return service.buscarPorId(id);
    }
}
Enter fullscreen mode Exit fullscreen mode

2. Nível de Camadas - Arquitetura em Camadas

// Camada de Apresentação
@RestController
public class PedidoController {
    @Autowired
    private PedidoService pedidoService;

    @PostMapping("/pedidos")
    public ResponseEntity<Pedido> criarPedido(@RequestBody PedidoDTO dto) {
        Pedido pedido = pedidoService.processar(dto);
        return ResponseEntity.ok(pedido);
    }
}

// Camada de Negócio
@Service
public class PedidoService {
    @Autowired
    private PedidoRepository repository;
    @Autowired
    private EmailService emailService;

    public Pedido processar(PedidoDTO dto) {
        // Lógica de negócio
        Pedido pedido = new Pedido(dto);
        pedido.calcularTotal();
        pedido = repository.save(pedido);
        emailService.enviarConfirmacao(pedido);
        return pedido;
    }
}

// Camada de Dados
@Repository
public interface PedidoRepository extends JpaRepository<Pedido, Long> {
    List<Pedido> findByClienteId(Long clienteId);
}
Enter fullscreen mode Exit fullscreen mode

Coesão e Acoplamento

Dois conceitos fundamentais em SoC:

Alta Coesão (BOM ✅)

Elementos relacionados ficam juntos.

// ALTA COESÃO - Todos os métodos são sobre gerenciamento de conta
public class ContaBancaria {
    private BigDecimal saldo;
    private String numero;

    public void depositar(BigDecimal valor) {
        this.saldo = this.saldo.add(valor);
    }

    public void sacar(BigDecimal valor) {
        if (saldo.compareTo(valor) >= 0) {
            this.saldo = this.saldo.subtract(valor);
        }
    }

    public BigDecimal consultarSaldo() {
        return this.saldo;
    }
}
Enter fullscreen mode Exit fullscreen mode

Baixo Acoplamento (BOM ✅)

Módulos dependem minimamente uns dos outros.

// BAIXO ACOPLAMENTO - Usando interfaces
public interface NotificadorService {
    void enviar(String mensagem, String destinatario);
}

@Service
public class EmailNotificador implements NotificadorService {
    public void enviar(String mensagem, String destinatario) {
        // Implementação de email
    }
}

@Service
public class SMSNotificador implements NotificadorService {
    public void enviar(String mensagem, String destinatario) {
        // Implementação de SMS
    }
}

// A classe não está acoplada a uma implementação específica
@Service
public class PedidoService {
    @Autowired
    private NotificadorService notificador; // Interface, não implementação!

    public void finalizarPedido(Pedido pedido) {
        // processar pedido...
        notificador.enviar("Pedido confirmado!", pedido.getCliente().getEmail());
    }
}
Enter fullscreen mode Exit fullscreen mode

Por Que SoC Funciona?

1. Manutenibilidade 🔧

Quando você precisa fazer uma mudança, você sabe exatamente onde ir:

// Antes de SoC - Tudo misturado 😱
public class SistemaPedido {
    public void processarPedido(String item, int quantidade, String email) {
        // Validação
        if (item == null || quantidade <= 0) {
            throw new IllegalArgumentException("Dados inválidos");
        }

        // Cálculo de preço
        double preco = quantidade * 10.0;
        if (quantidade > 100) {
            preco = preco * 0.9; // desconto
        }

        // Salvar no banco
        Connection conn = DriverManager.getConnection("jdbc:...");
        PreparedStatement stmt = conn.prepareStatement("INSERT INTO pedidos...");
        // ...

        // Enviar email
        Properties props = new Properties();
        Session session = Session.getInstance(props);
        MimeMessage message = new MimeMessage(session);
        // ...
    }
}

// Depois de SoC - Tudo organizado 😊
@Service
public class PedidoService {
    @Autowired private ValidadorPedido validador;
    @Autowired private CalculadoraPreco calculadora;
    @Autowired private PedidoRepository repository;
    @Autowired private EmailService emailService;

    public void processarPedido(PedidoDTO dto) {
        validador.validar(dto);
        BigDecimal preco = calculadora.calcular(dto);
        Pedido pedido = new Pedido(dto, preco);
        repository.save(pedido);
        emailService.enviarConfirmacao(pedido);
    }
}
Enter fullscreen mode Exit fullscreen mode

2. Testabilidade 🧪

Com SoC, testar fica muito mais fácil:

@Test
public void testCalculadoraPreco() {
    CalculadoraPreco calc = new CalculadoraPreco();
    PedidoDTO dto = new PedidoDTO("Item", 150);

    BigDecimal preco = calc.calcular(dto);

    // Testando apenas a lógica de cálculo, isoladamente!
    assertEquals(new BigDecimal("1350.00"), preco); // 150 * 10 * 0.9
}

@Test
public void testPedidoService() {
    // Usando mocks para isolar o teste
    ValidadorPedido validador = mock(ValidadorPedido.class);
    CalculadoraPreco calculadora = mock(CalculadoraPreco.class);
    PedidoRepository repository = mock(PedidoRepository.class);
    EmailService emailService = mock(EmailService.class);

    when(calculadora.calcular(any())).thenReturn(new BigDecimal("100"));

    PedidoService service = new PedidoService(validador, calculadora, repository, emailService);
    service.processarPedido(new PedidoDTO());

    verify(repository).save(any());
    verify(emailService).enviarConfirmacao(any());
}
Enter fullscreen mode Exit fullscreen mode

3. Reutilização ♻️

Componentes bem separados podem ser reutilizados:

// Este serviço de email pode ser usado em qualquer lugar!
@Service
public class EmailService {
    public void enviar(Email email) {
        // implementação
    }
}

// Usado no PedidoService
@Service
public class PedidoService {
    @Autowired EmailService emailService;
    // ...
}

// Usado no UsuarioService também!
@Service 
public class UsuarioService {
    @Autowired EmailService emailService;
    // ...
}
Enter fullscreen mode Exit fullscreen mode

Exemplos Práticos do Mundo Java

Exemplo 1: Sistema de E-commerce

// ❌ RUIM - Violando SoC
public class Produto {
    private String nome;
    private BigDecimal preco;

    // Métodos de negócio - OK
    public BigDecimal calcularPrecoComDesconto(BigDecimal percentual) {
        return preco.multiply(BigDecimal.ONE.subtract(percentual));
    }

    // Método de persistência - NÃO DEVERIA ESTAR AQUI!
    public void salvarNoBanco() {
        Connection conn = DriverManager.getConnection("...");
        // código SQL...
    }

    // Método de apresentação - NÃO DEVERIA ESTAR AQUI!
    public String toHTML() {
        return "<div>" + nome + " - R$ " + preco + "</div>";
    }
}

// ✅ BOM - Respeitando SoC
public class Produto {
    private String nome;
    private BigDecimal preco;

    // Apenas lógica de negócio
    public BigDecimal calcularPrecoComDesconto(BigDecimal percentual) {
        return preco.multiply(BigDecimal.ONE.subtract(percentual));
    }
}

@Repository
public class ProdutoRepository {
    public void salvar(Produto produto) {
        // código de persistência
    }
}

@Component
public class ProdutoView {
    public String renderizar(Produto produto) {
        return "<div>" + produto.getNome() + " - R$ " + produto.getPreco() + "</div>";
    }
}
Enter fullscreen mode Exit fullscreen mode

Exemplo 2: Processamento de Pagamento

// Separando responsabilidades em um sistema de pagamento

// 1. Validação
@Component
public class ValidadorPagamento {
    public void validar(DadosPagamento dados) {
        if (dados.getValor().compareTo(BigDecimal.ZERO) <= 0) {
            throw new ValorInvalidoException();
        }
        if (!isCartaoValido(dados.getNumeroCartao())) {
            throw new CartaoInvalidoException();
        }
    }

    private boolean isCartaoValido(String numero) {
        // Algoritmo de Luhn
        return true; // simplificado
    }
}

// 2. Processamento
@Component
public class ProcessadorPagamento {
    @Autowired
    private GatewayPagamento gateway;

    public RespostaPagamento processar(DadosPagamento dados) {
        return gateway.cobrar(dados);
    }
}

// 3. Notificação
@Component
public class NotificadorPagamento {
    @Autowired
    private EmailService emailService;
    @Autowired
    private SMSService smsService;

    public void notificarSucesso(Pagamento pagamento) {
        emailService.enviar(criarEmailSucesso(pagamento));
        if (pagamento.getCliente().temCelular()) {
            smsService.enviar(criarSMSSucesso(pagamento));
        }
    }
}

// 4. Orquestração
@Service
public class PagamentoService {
    @Autowired private ValidadorPagamento validador;
    @Autowired private ProcessadorPagamento processador;
    @Autowired private NotificadorPagamento notificador;
    @Autowired private PagamentoRepository repository;

    @Transactional
    public Pagamento realizarPagamento(DadosPagamento dados) {
        // Cada componente faz sua parte
        validador.validar(dados);
        RespostaPagamento resposta = processador.processar(dados);
        Pagamento pagamento = new Pagamento(dados, resposta);
        repository.save(pagamento);
        notificador.notificarSucesso(pagamento);
        return pagamento;
    }
}
Enter fullscreen mode Exit fullscreen mode

SoC em Diferentes Níveis

Nível de Método

// ❌ RUIM - Método fazendo muitas coisas
public void processarRelatorio(List<Venda> vendas) {
    // Filtrar vendas
    List<Venda> vendasValidas = new ArrayList<>();
    for (Venda v : vendas) {
        if (v.getValor() > 0 && v.getData() != null) {
            vendasValidas.add(v);
        }
    }

    // Calcular total
    BigDecimal total = BigDecimal.ZERO;
    for (Venda v : vendasValidas) {
        total = total.add(v.getValor());
    }

    // Formatar relatório
    StringBuilder sb = new StringBuilder();
    sb.append("Relatório de Vendas\n");
    sb.append("Total: ").append(total).append("\n");

    // Salvar arquivo
    Files.write(Paths.get("relatorio.txt"), sb.toString().getBytes());

    // Enviar email
    emailService.enviar("relatorio@empresa.com", sb.toString());
}

// ✅ BOM - Cada método com uma responsabilidade
public void processarRelatorio(List<Venda> vendas) {
    List<Venda> vendasValidas = filtrarVendasValidas(vendas);
    BigDecimal total = calcularTotal(vendasValidas);
    String relatorio = formatarRelatorio(vendasValidas, total);
    salvarRelatorio(relatorio);
    enviarRelatorioPorEmail(relatorio);
}

private List<Venda> filtrarVendasValidas(List<Venda> vendas) {
    return vendas.stream()
        .filter(v -> v.getValor().compareTo(BigDecimal.ZERO) > 0)
        .filter(v -> v.getData() != null)
        .collect(Collectors.toList());
}

private BigDecimal calcularTotal(List<Venda> vendas) {
    return vendas.stream()
        .map(Venda::getValor)
        .reduce(BigDecimal.ZERO, BigDecimal::add);
}

private String formatarRelatorio(List<Venda> vendas, BigDecimal total) {
    return String.format("Relatório de Vendas\nTotal: %s\nQuantidade: %d", 
                        total, vendas.size());
}
Enter fullscreen mode Exit fullscreen mode

Nível de Pacotes

com.empresa.app/
├── controller/      # Responsável por receber requisições
│   ├── UsuarioController.java
│   └── ProdutoController.java
├── service/         # Lógica de negócio
│   ├── UsuarioService.java
│   └── ProdutoService.java
├── repository/      # Acesso a dados
│   ├── UsuarioRepository.java
│   └── ProdutoRepository.java
├── model/          # Entidades do domínio
│   ├── Usuario.java
│   └── Produto.java
├── dto/            # Objetos de transferência
│   ├── UsuarioDTO.java
│   └── ProdutoDTO.java
└── util/           # Utilitários
    ├── EmailUtil.java
    └── ValidadorUtil.java
Enter fullscreen mode Exit fullscreen mode

Aplicando SoC com Spring Boot

Spring Boot naturalmente encoraja SoC através de suas anotações:

@Configuration  // Configuração separada
public class AppConfig {
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

@Component      // Componente reutilizável
public class CacheManager {
    private Map<String, Object> cache = new HashMap<>();
    // ...
}

@Service        // Lógica de negócio
public class ClienteService {
    // ...
}

@Repository     // Acesso a dados
public interface ClienteRepository extends JpaRepository<Cliente, Long> {
    // ...
}

@RestController // Controlador REST
public class ClienteController {
    // ...
}

@Aspect         // Aspectos transversais (logging, segurança)
@Component
public class LoggingAspect {
    @Around("@annotation(Loggable)")
    public Object logExecutionTime(ProceedingJoinPoint joinPoint) throws Throwable {
        long start = System.currentTimeMillis();
        Object proceed = joinPoint.proceed();
        long executionTime = System.currentTimeMillis() - start;
        logger.info("{} executed in {} ms", joinPoint.getSignature(), executionTime);
        return proceed;
    }
}
Enter fullscreen mode Exit fullscreen mode

Padrões de Design que Implementam SoC

1. Strategy Pattern

// Separando algoritmos de cálculo de frete
public interface CalculadoraFrete {
    BigDecimal calcular(Pedido pedido);
}

@Component
public class FretePAC implements CalculadoraFrete {
    public BigDecimal calcular(Pedido pedido) {
        return pedido.getPeso().multiply(new BigDecimal("5.00"));
    }
}

@Component
public class FreteSedex implements CalculadoraFrete {
    public BigDecimal calcular(Pedido pedido) {
        return pedido.getPeso().multiply(new BigDecimal("15.00"));
    }
}

@Service
public class FreteService {
    private Map<TipoFrete, CalculadoraFrete> calculadoras;

    public BigDecimal calcularFrete(Pedido pedido, TipoFrete tipo) {
        return calculadoras.get(tipo).calcular(pedido);
    }
}
Enter fullscreen mode Exit fullscreen mode

2. Observer Pattern

// Separando notificações de eventos
public interface EventListener {
    void onPedidoCriado(Pedido pedido);
}

@Component
public class EmailListener implements EventListener {
    public void onPedidoCriado(Pedido pedido) {
        // Enviar email
    }
}

@Component
public class EstoqueListener implements EventListener {
    public void onPedidoCriado(Pedido pedido) {
        // Atualizar estoque
    }
}

@Component
public class NotaFiscalListener implements EventListener {
    public void onPedidoCriado(Pedido pedido) {
        // Gerar nota fiscal
    }
}

@Service
public class PedidoService {
    @Autowired
    private List<EventListener> listeners;

    public void criarPedido(PedidoDTO dto) {
        Pedido pedido = new Pedido(dto);
        // salvar pedido...

        // Notificar todos os interessados
        listeners.forEach(l -> l.onPedidoCriado(pedido));
    }
}
Enter fullscreen mode Exit fullscreen mode

Microserviços: SoC em Escala

// Serviço de Usuários
@SpringBootApplication
public class UserServiceApplication {
    // Responsável apenas por usuários
}

// Serviço de Pedidos
@SpringBootApplication
public class OrderServiceApplication {
    // Responsável apenas por pedidos
}

// Serviço de Notificações
@SpringBootApplication
public class NotificationServiceApplication {
    // Responsável apenas por notificações
}

// Comunicação via REST ou mensageria
@FeignClient(name = "user-service")
public interface UserServiceClient {
    @GetMapping("/users/{id}")
    User getUser(@PathVariable Long id);
}
Enter fullscreen mode Exit fullscreen mode

Armadilhas Comuns e Como Evitá-las

1. God Class (Classe Deus)

// ❌ RUIM - Classe fazendo tudo
public class SistemaCompleto {
    // 50 atributos
    // 100 métodos
    // Validação, cálculo, persistência, apresentação...
}

// ✅ BOM - Responsabilidades distribuídas
public class Usuario { /* modelo */ }
public class UsuarioService { /* lógica */ }
public class UsuarioRepository { /* persistência */ }
public class UsuarioController { /* API */ }
Enter fullscreen mode Exit fullscreen mode

2. Anemic Domain Model

// ⚠️ CUIDADO - Modelo anêmico (sem comportamento)
public class Conta {
    private BigDecimal saldo;
    // apenas getters e setters
}

public class ContaService {
    public void sacar(Conta conta, BigDecimal valor) {
        // toda lógica aqui
    }
}

// ✅ MELHOR - Modelo rico
public class Conta {
    private BigDecimal saldo;

    public void sacar(BigDecimal valor) {
        if (valor.compareTo(saldo) > 0) {
            throw new SaldoInsuficienteException();
        }
        this.saldo = saldo.subtract(valor);
    }

    public void depositar(BigDecimal valor) {
        this.saldo = saldo.add(valor);
    }
}
Enter fullscreen mode Exit fullscreen mode

Conclusão

Separation of Concerns não é apenas um princípio teórico - é uma ferramenta prática que torna nosso código:

  • Mais fácil de entender 🧠
  • Mais fácil de manter 🔧
  • Mais fácil de testar 🧪
  • Mais fácil de escalar 📈
  • Mais fácil de reutilizar ♻️

Lembre-se:

  1. Cada classe deve ter uma razão para mudar
  2. Cada método deve fazer uma coisa bem feita
  3. Cada camada deve ter uma responsabilidade clara
  4. Prefira composição sobre herança
  5. Use interfaces para reduzir acoplamento

Próximos Passos

  • Estude os princípios SOLID (SoC é parte do S - Single Responsibility)
  • Explore padrões de design como MVC, MVP, MVVM
  • Pratique Domain-Driven Design (DDD)
  • Aprenda sobre Arquitetura Hexagonal
  • Experimente com Microserviços

Call to Action

Agora olhe para seu código Java. Encontre aquela classe com 1000 linhas. Você sabe qual é.

Comece pequeno:

  1. Extraia um método
  2. Depois extraia uma classe
  3. Depois separe em camadas
  4. Continue refatorando

Roma não foi construída em um dia, e seu código limpo também não será. Mas cada passo na direção certa conta!


💬 Tem alguma dúvida ou quer compartilhar como aplica SoC em seus projetos Java? Deixe um comentário abaixo!

⭐ Se este artigo foi útil, não esqueça de dar uma estrela e compartilhar com seus colegas desenvolvedores Jav

Top comments (0)