Técnicas em Java para Decompor Lógica de Negócio com Clareza

No desenvolvimento de aplicações Java, a lógica de negócio complexa frequentemente resulta em blocos de if-else aninhados, comprometendo a legibiliddade e manutenção do código. Este artigo explora abordagens para simplificar essa lógica, evitando estruturas condicionais excessivas e promovendo código mais modular e elegante.

Minimização de Unidades Lógicas

A estratégia fundamental é dividir a lógica em unidades menores e reutilizáveis. Cada unidade encapsula uma responsabilidade específica, permitindo composição flexível e reduzindo a complexidade.

Encapsulamento com Enums

Enums podem abrigar comportamentos diretamente, eliminando a necessidade de condicionais baseadas em valores. Considere um cenário de aplicação de descontos em um sistema de e-commerce:

public enum DescontoTipo {
    SAZONAL("sazonal") {
        @Override
        public double aplicarDesconto(double valorBase) {
            return valorBase * 0.85;
        }
    },
    LIQUIDACAO("liquidacao") {
        @Override
        public double aplicarDesconto(double valorBase) {
            return valorBase * 0.6;
        }
    },
    NENHUM("nenhum") {
        @Override
        public double aplicarDesconto(double valorBase) {
            return valorBase;
        }
    };

    public abstract double aplicarDesconto(double valorBase);

    private final String identificador;

    DescontoTipo(String identificador) {
        this.identificador = identificador;
    }

    public String getIdentificador() {
        return identificador;
    }
}

// Exemplo de uso
double valorComDesconto = DescontoTipo.SAZONAL.aplicarDesconto(200.0);
// ou dinamicamente
DescontoTipo tipoDesconto = DescontoTipo.valueOf("LIQUIDACAO");
double valorFinal = tipoDesconto.aplicarDesconto(200.0);

Otimização com Interfaces Funcionais

Em aplicações de processamento de pedidos, diferentes regras de cálculo de frete podem ser aplicadas com base na origem do pedido. Em vez de múltiplos ifs, utilize interfaces funcionais:

@RestController
@RequestMapping("/pedidos")
public class PedidoController {
    @Autowired
    private FreteService freteService;

    @PostMapping("/calcular-frete")
    public ResponseEntity<Double> calcularFrete(String pedidoId, String regiao) {
        double resultado = freteService.calcular(pedidoId, regiao);
        return ResponseEntity.ok(resultado);
    }
}

@Service
public class FreteService {
    private static final Logger log = LoggerFactory.getLogger(FreteService.class);
    private final Map<String, BiFunction<String, String, Double>> regrasFrete = new HashMap<>();

    @PostConstruct
    private void inicializarRegras() {
        regrasFrete.put("norte", (idPedido, regiao) -> this.calcularFreteNorte(idPedido));
        regrasFrete.put("sul", (idPedido, regiao) -> this.calcularFreteSul(idPedido));
    }

    public double calcular(String pedidoId, String regiao) {
        BiFunction<String, String, Double> funcaoRegra = regrasFrete.get(regiao);
        if (funcaoRegra != null) {
            return funcaoRegra.apply(pedidoId, regiao);
        }
        throw new IllegalArgumentException("Região não suportada: " + regiao);
    }

    private double calcularFreteNorte(String pedidoId) {
        log.info("Calculando frete para pedido {} na região Norte", pedidoId);
        return 15.0;
    }

    private double calcularFreteSul(String pedidoId) {
        log.info("Calculando frete para pedido {} na região Sul", pedidoId);
        return 10.0;
    }
}

Padrão Fábrica com Classes Abstratas

Para processamento de pagamentos, diferenets métodos (cartão, boleto, PIX) podem ser tratados com fábricas:

public abstract class ProcessadorPagamento {
    public abstract boolean processar(String transacaoId, double valor);
}

public class ProcessadorCartao extends ProcessadorPagamento {
    @Override
    public boolean processar(String transacaoId, double valor) {
        // Lógica para processar pagamento com cartão
        return true;
    }
}

public class ProcessadorBoleto extends ProcessadorPagamento {
    @Override
    public boolean processar(String transacaoId, double valor) {
        // Lógica para gerar boleto
        return true;
    }
}

public class FabricaPagamento {
    public static ProcessadorPagamento obterProcessador(String metodo) {
        switch (metodo) {
            case "cartao":
                return new ProcessadorCartao();
            case "boleto":
                return new ProcessadorBoleto();
            default:
                return null;
        }
    }
}

// Uso no controlador
ProcessadorPagamento processador = FabricaPagamento.obterProcessador("cartao");
if (processador != null) {
    processador.processar("TRX123", 100.0);
}

Padrão Estratégia

Para validação de usuários em diferentes canais (web, mobile, API), o padrão estratégia oferece flexibilidade:

public interface EstrategiaValidacao {
    boolean validar(String usuarioId, String canal);
}

@Service("web")
public class ValidacaoWeb implements EstrategiaValidacao {
    @Override
    public boolean validar(String usuarioId, String canal) {
        // Validação específica para web
        return true;
    }
}

@Service("mobile")
public class ValidacaoMobile implements EstrategiaValidacao {
    @Override
    public boolean validar(String usuarioId, String canal) {
        // Validação específica para mobile
        return true;
    }
}

// No controlador
@Autowired
private ApplicationContext contexto;

@GetMapping("validar")
public ResponseEntity<Boolean> validarUsuario(@RequestParam String usuarioId, @RequestParam String canal) {
    EstrategiaValidacao estrategia = contexto.getBean(canal, EstrategiaValidacao.class);
    return ResponseEntity.ok(estrategia.validar(usuarioId, canal));
}

Combinação de Padrões para Fluxos Complexos

Em cenários como emissão de notas fiscais com etapas obrigatórias (validação fiscal, cálculo de impostos), combine estratégia, template method e fábrica:

public interface EstrategiaEmissao {
    Map<String, Object> emitirNota(String pedidoId);
    String obterTipoServico();
}

public abstract class ModeloEmissaoBase {
    public final Map<String, Object> executarFluxo(String pedidoId) {
        this.validarDados(pedidoId);
        this.calcularImpostos(pedidoId);
        return this.gerarDocumento(pedidoId);
    }

    protected abstract void validarDados(String pedidoId);
    protected abstract void calcularImpostos(String pedidoId);
    protected abstract Map<String, Object> gerarDocumento(String pedidoId);
}

@Service
public class EmissaoNfce extends ModeloEmissaoBase implements EstrategiaEmissao {
    @Override
    public Map<String, Object> emitirNota(String pedidoId) {
        return executarFluxo(pedidoId);
    }

    @Override
    public String obterTipoServico() {
        return "nfce";
    }

    @Override
    protected void validarDados(String pedidoId) {
        // Validação para NFC-e
    }

    @Override
    protected void calcularImpostos(String pedidoId) {
        // Cálculo de impostos para NFC-e
    }

    @Override
    protected Map<String, Object> gerarDocumento(String pedidoId) {
        // Geração do documento
        return new HashMap<>();
    }
}

@Component
public class FabricaEmissao {
    private static final Map<String, EstrategiaEmissao> estrategias = new HashMap<>();

    @Autowired
    public void inicializarEstrategias(List<EstrategiaEmissao> lista) {
        lista.forEach(e -> estrategias.put(e.obterTipoServico(), e));
    }

    public Map<String, Object> emitir(String pedidoId, String tipoServico) {
        EstrategiaEmissao estrategia = estrategias.get(tipoServico);
        if (estrategia == null) {
            throw new IllegalArgumentException("Tipo de serviço não suportado");
        }
        return estrategia.emitirNota(pedidoId);
    }
}

// Controlador de exemplo
@RestController
@RequestMapping("/notas")
public class NotaController {
    @Autowired
    private FabricaEmissao fabricaEmissao;

    @PostMapping("/emitir")
    public ResponseEntity<Map<String, Object>> emitirNota(@RequestParam String pedidoId, @RequestParam String tipoServico) {
        Map<String, Object> resultado = fabricaEmissao.emitir(pedidoId, tipoServico);
        return ResponseEntity.ok(resultado);
    }
}

Tags: java Padrões de Projeto Interfaces Funcionais Enums Java Strategy Pattern

Publicado em 6-17 16:27