7 maniobras concretas organizadas por objetivo: componer, mover y simplificar
// validar pedido o // calcular total. Ese comentario es el nombre del método que aún no existe. Cuando necesitas scroll para leer una función. Cuando el mismo bloque aparece dos veces.void processOrder(Order order) { // validar if (order.getItems().isEmpty()) throw new IllegalStateException("No items"); if (order.getCustomer() == null) throw new IllegalStateException("No customer"); // calcular total double total = 0; for (Item i : order.getItems()) total += i.getPrice() * i.getQty(); if (order.getCustomer().isPremium()) total *= 0.9; order.setTotal(total); // notificar emailService.send( order.getCustomer().getEmail(), "Confirmado: $" + total); }
void processOrder(Order order) { validateOrder(order); order.setTotal(calculateTotal(order)); notifyCustomer(order); } private void validateOrder(Order o) { if (o.getItems().isEmpty()) throw new IllegalStateException("No items"); if (o.getCustomer() == null) throw new IllegalStateException("No customer"); } private double calculateTotal(Order o) { double t = o.getItems().stream() .mapToDouble(i -> i.getPrice() * i.getQty()) .sum(); return o.getCustomer().isPremium() ? t * 0.9 : t; }
boolean isEligibleForDiscount(Customer c) { return isPremiumLongTerm(c); } private boolean isPremiumLongTerm(Customer c) { return c.getMonthsSinceJoin() > 12 && c.getTotalOrders() > 10; } // el nombre isEligibleForDiscount ya // lo dice todo → inline isPremiumLongTerm
boolean isEligibleForDiscount(Customer c) { return c.getMonthsSinceJoin() > 12 && c.getTotalOrders() > 10; } // Una sola capa, criterio legible, // fácil de modificar sin perder contexto. // Si la lógica crece → Extract de nuevo.
double getFinalPrice() { double basePrice = quantity * itemPrice; if (basePrice > 1000) return basePrice * 0.95; return basePrice; // basePrice solo existe aquí // si otra función la necesita: duplicado }
double getFinalPrice() { return basePrice() > 1000 ? basePrice() * 0.95 : basePrice(); } private double basePrice() { return quantity * itemPrice; // ahora cualquier método puede // llamar a basePrice() sin duplicar }
class OrderLine { private Product product; private int qty; double getDiscountedPrice() { // usa 3 datos de Product, 0 de OrderLine double base = product.getBasePrice(); double tax = product.getTaxRate(); if (product.isOnSale()) return (base * 0.9) * (1 + tax); return base * (1 + tax); } }
class Product { private double basePrice; private double taxRate; private boolean onSale; double getDiscountedPrice() { // ahora usa sus propios datos double base = onSale ? basePrice * 0.9 : basePrice; return base * (1 + taxRate); } } class OrderLine { double lineTotal() { return product.getDiscountedPrice() * qty; } }
class Customer { String name; String street; // ← datos de dirección String city; String zipCode; String country; String formatAddress() { /* ... */ } boolean isAddressValid() { /* ... */ } // Customer y Address mezclados → SRP roto }
class Address { // Value Object private final String street; private final String city; private final String zipCode; private final String country; String format() { /* ... */ } boolean isValid() { /* ... */ } } class Customer { String name; Address address; // cohesión }
if requiere más de una lectura para entenderse. Hay operadores lógicos compuestos (&&, ||, !) que combinan más de 2 conceptos. El cuerpo del then o else supera 3-4 líneas.if (date.before(SUMMER_START) || date.after(SUMMER_END)) { charge = qty * winterRate + winterServiceCharge; } else { charge = qty * summerRate; } // ¿qué son SUMMER_START y END aquí? // ¿qué significa winterServiceCharge? // el lector debe inferir el modelo mental
charge = isWinter(date) ? winterCharge(qty) : summerCharge(qty); private boolean isWinter(Date d) { return d.before(SUMMER_START) || d.after(SUMMER_END); } private double winterCharge(int qty) { return qty * winterRate + winterServiceCharge; } private double summerCharge(int qty) { return qty * summerRate; }
switch que chequea el tipo de un objeto para ejecutar lógica diferente, usa subclases e interfaces. Es la aplicación directa de OCP: agregar un tipo nuevo es agregar una clase, no modificar el switch.switch sobre el mismo campo aparece en dos o más métodos. Cuando aparece un nuevo tipo, hay que buscar y actualizar todos los switch. Esta es la conexión directa con GoF Strategy y el principio OCP de SOLID.double calculateShipping(Order order) { switch (order.getShippingType()) { case EXPRESS: return order.getTotal() * 0.15; case STANDARD: return order.getTotal() * 0.05; case ECONOMY: return 2.99; default: throw new IllegalArgumentException(); } // nuevo tipo → modificar aquí siempre }
interface ShippingStrategy { double calculate(Order order); } class ExpressShipping implements ShippingStrategy { public double calculate(Order o) { return o.getTotal() * 0.15; } } class EconomyShipping implements ShippingStrategy { public double calculate(Order o) { return 2.99; } } // nuevo tipo = nueva clase, sin tocar nada