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);
}
}