Especialização em Sistemas com Java 21 LTS • Capítulo 08


🎯 Visão Geral dos Exemplos Práticos

Neste laboratório prático, implementaremos exemplos corporativos completos explorando:

  1. Composição Forte e Agregação UML: modelagem de um ecossistema de checkout (Pedido, Cliente, ItemPedido e Produto).
  2. Máquina de Estados de Ciclo de Vida com Enum: enums avançados com métodos abstratos para validar transições de status legais.
  3. Padrão Strategy Embutido em Enum: cálculo de frete logístico especializado sem encadeamentos de if/else.

💻 Exemplo 01 — Sistema Completo de Checkout e Máquina de Estados

package com.empresa.composicao;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

/**
 * Sistema corporativo de pedidos com composição, agregacão e enums comportamentais.
 */
public class SistemaCheckoutComposicao {

    public enum ModalidadeFrete {
        RETIRADA_LOJA {
            @Override
            public BigDecimal calcular(BigDecimal pesoTotalKg) { return BigDecimal.ZERO; }
        },
        PAC_CORREIOS {
            @Override
            public BigDecimal calcular(BigDecimal pesoTotalKg) {
                return new BigDecimal("22.50").add(pesoTotalKg.multiply(new BigDecimal("2.10")));
            }
        },
        SEDEX_EXPRESSO {
            @Override
            public BigDecimal calcular(BigDecimal pesoTotalKg) {
                return new BigDecimal("45.00").add(pesoTotalKg.multiply(new BigDecimal("4.80")));
            }
        };

        public abstract BigDecimal calcular(BigDecimal pesoTotalKg);
    }

    public enum StatusPedido {
        CRIADO {
            @Override
            public boolean podeAvancar(StatusPedido proximo) { return proximo == PAGO || proximo == CANCELADO; }
        },
        PAGO {
            @Override
            public boolean podeAvancar(StatusPedido proximo) { return proximo == EM_TRANSPORTE || proximo == CANCELADO; }
        },
        EM_TRANSPORTE {
            @Override
            public boolean podeAvancar(StatusPedido proximo) { return proximo == ENTREGUE; }
        },
        ENTREGUE {
            @Override
            public boolean podeAvancar(StatusPedido proximo) { return false; }
        },
        CANCELADO {
            @Override
            public boolean podeAvancar(StatusPedido proximo) { return false; }
        };

        public abstract boolean podeAvancar(StatusPedido proximo);
    }

    public record Cliente(String id, String nome, String email) {}
    public record Produto(String sku, String nome, BigDecimal precoUnitario, BigDecimal pesoKg) {}

    public static class ItemPedido {
        private final Produto produto;
        private final int quantidade;
        private final BigDecimal precoPraticado;

        public ItemPedido(Produto produto, int quantidade) {
            this.produto = produto;
            this.quantidade = quantidade;
            this.precoPraticado = produto.precoUnitario();
        }

        public BigDecimal getSubtotal() {
            return precoPraticado.multiply(new BigDecimal(quantidade));
        }

        public BigDecimal getPesoTotal() {
            return produto.pesoKg().multiply(new BigDecimal(quantidade));
        }

        public Produto getProduto() { return produto; }
        public int getQuantidade() { return quantidade; }
    }

    public static class Pedido {
        private final String numeroProtocolo;
        private final LocalDateTime dataCriacao;
        private final Cliente cliente; // Agregação (Cliente existe de forma independente)
        private final List<ItemPedido> itens = new ArrayList<>(); // Composição Forte (Itens pertencem a este pedido)
        private ModalidadeFrete freteEscolhido = ModalidadeFrete.RETIRADA_LOJA;
        private StatusPedido status = StatusPedido.CRIADO;

        public Pedido(String numeroProtocolo, Cliente cliente) {
            this.numeroProtocolo = numeroProtocolo;
            this.cliente = cliente;
            this.dataCriacao = LocalDateTime.now();
        }

        public void adicionarItem(Produto produto, int quantidade) {
            if (this.status != StatusPedido.CRIADO) {
                throw new IllegalStateException("Pedido bloqueado para adição de itens (Status: " + status + ")");
            }
            itens.add(new ItemPedido(produto, quantidade));
        }

        public void definirFrete(ModalidadeFrete frete) {
            this.freteEscolhido = frete;
        }

        public void mudarStatus(StatusPedido novoStatus) {
            if (!this.status.podeAvancar(novoStatus)) {
                throw new IllegalStateException(String.format("Transição ilegal de [%s] para [%s]", status, novoStatus));
            }
            System.out.printf("   [MUDANÇA DE ESTADO] Pedido %s: %s ➡️ %s%n", numeroProtocolo, status, novoStatus);
            this.status = novoStatus;
        }

        public BigDecimal calcularSubtotalItens() {
            return itens.stream().map(ItemPedido::getSubtotal).reduce(BigDecimal.ZERO, BigDecimal::add);
        }

        public BigDecimal calcularPesoTotal() {
            return itens.stream().map(ItemPedido::getPesoTotal).reduce(BigDecimal.ZERO, BigDecimal::add);
        }

        public BigDecimal calcularValorTotal() {
            BigDecimal subtotal = calcularSubtotalItens();
            BigDecimal custoFrete = freteEscolhido.calcular(calcularPesoTotal());
            return subtotal.add(custoFrete).setScale(2, RoundingMode.HALF_UP);
        }

        public void imprimirSumario() {
            DateTimeFormatter fmt = DateTimeFormatter.ofPattern("dd/MM/yyyy HH:mm");
            System.out.println("================================================================================");
            System.out.printf("                    PEDIDO DE VENDA: %s%n", numeroProtocolo);
            System.out.println("================================================================================");
            System.out.printf("Data: %s | Status: [%s]%n", dataCriacao.format(fmt), status);
            System.out.printf("Cliente: %s (%s)%n", cliente.nome(), cliente.email());
            System.out.println("--------------------------------------------------------------------------------");
            System.out.printf("%-12s | %-30s | %4s | %-10s | %-10s%n", "SKU", "PRODUTO", "QTD", "UNITÁRIO", "SUBTOTAL");
            System.out.println("-------------+--------------------------------+------+------------+-------------");

            for (ItemPedido item : itens) {
                System.out.printf("%-12s | %-30s | %4d | R$ %8.2f | R$ %8.2f%n",
                        item.getProduto().sku(), item.getProduto().nome(),
                        item.getQuantidade(), item.getProduto().precoUnitario(), item.getSubtotal());
            }

            BigDecimal custoFrete = freteEscolhido.calcular(calcularPesoTotal());
            System.out.println("--------------------------------------------------------------------------------");
            System.out.printf("Subtotal dos Itens  : R$ %10.2f%n", calcularSubtotalItens());
            System.out.printf("Frete (%-14s): R$ %10.2f (Peso: %.2f kg)%n", freteEscolhido, custoFrete, calcularPesoTotal());
            System.out.printf("TOTAL DO PEDIDO     : R$ %10.2f%n", calcularValorTotal());
            System.out.println("================================================================================");
        }

        public List<ItemPedido> getItens() { return Collections.unmodifiableList(itens); }
    }

    public static void main(String[] args) {
        Cliente cli = new Cliente("CLI-890", "Fernando Silveira", "fernando@empresa.com.br");
        Pedido pedido = new Pedido("PED-2026-9041", cli);

        pedido.adicionarItem(new Produto("MON-DELL-27", "Monitor Dell 27 4K", new BigDecimal("2499.00"), new BigDecimal("6.50")), 2);
        pedido.adicionarItem(new Produto("KB-MECH-RGB", "Teclado Mecânico Keychron", new BigDecimal("650.00"), new BigDecimal("1.20")), 1);

        pedido.definirFrete(ModalidadeFrete.SEDEX_EXPRESSO);
        pedido.imprimirSumario();

        // Transições de Estado
        System.out.println("\n--- Fluxo do Ciclo de Vida ---");
        pedido.mudarStatus(StatusPedido.PAGO);
        pedido.mudarStatus(StatusPedido.EM_TRANSPORTE);
        pedido.mudarStatus(StatusPedido.ENTREGUE);

        // Teste de Transição Inválida
        try {
            System.out.println("\n--- Teste de Violação: Tentar Cancelar Pedido Já Entregue ---");
            pedido.mudarStatus(StatusPedido.CANCELADO);
        } catch (IllegalStateException e) {
            System.out.println("🛡️ Interceptação de Regra de Negócio: " + e.getMessage());
        }
    }
}

📋 Resultado Esperado no Console

================================================================================
                    PEDIDO DE VENDA: PED-2026-9041
================================================================================
Data: 28/09/2026 15:30 | Status: [CRIADO]
Cliente: Fernando Silveira (fernando@empresa.com.br)
--------------------------------------------------------------------------------
SKU          | PRODUTO                        |  QTD | UNITÁRIO   | SUBTOTAL  
-------------+--------------------------------+------+------------+-------------
MON-DELL-27  | Monitor Dell 27 4K             |    2 | R$  2499.00 | R$  4998.00
KB-MECH-RGB  | Teclado Mecânico Keychron      |    1 | R$   650.00 | R$   650.00
--------------------------------------------------------------------------------
Subtotal dos Itens  : R$    5648.00
Frete (SEDEX_EXPRESSO): R$     113.16 (Peso: 14.20 kg)
TOTAL DO PEDIDO     : R$    5761.16
================================================================================

--- Fluxo do Ciclo de Vida ---
   [MUDANÇA DE ESTADO] Pedido PED-2026-9041: CRIADO ➡️ PAGO
   [MUDANÇA DE ESTADO] Pedido PED-2026-9041: PAGO ➡️ EM_TRANSPORTE
   [MUDANÇA DE ESTADO] Pedido PED-2026-9041: EM_TRANSPORTE ➡️ ENTREGUE

--- Teste de Violação: Tentar Cancelar Pedido Já Entregue ---
🛡️ Interceptação de Regra de Negócio: Transição ilegal de [ENTREGUE] para [CANCELADO]