Cómo ensamblar objetos bien — las decisiones que más impactan en mantenibilidad
// OrderService hereda logging "gratis" class OrderService extends BaseLoggingService { void placeOrder(Order order) { log("Placing: " + order.getId()); // heredado /* lógica de negocio */ } } // OrderService NO ES-UN BaseLoggingService // Acoplado a la jerarquía para siempre // Cambiar el logger → modificar herencia // No puedes tener dos loggers distintos
class OrderService { // no extiende nada private final Logger logger; // TIENE-UN private final OrderRepository repo; OrderService(Logger logger, OrderRepository repo) { this.logger = logger; this.repo = repo; } void placeOrder(Order order) { logger.info("Placing: " + order.getId()); /* lógica de negocio */ } // Logger es intercambiable en tests y prod }
// UserManager hace demasiado class UserManager { void createUser(User u) { /* DB */ } void sendWelcomeEmail(User u) { /* SMTP */ } void generatePDFReport() { /* PDF */ } void connectToDatabase() { /* JDBC */ } void validateTaxId(String rut) { /* regex */ } } // Cambia el PDF → toca UserManager // Cambia el SMTP → toca UserManager // 5 razones de cambio → baja cohesión
class UserRepository { void save(User u) { /* solo DB */ } } class WelcomeEmailSender { void send(User u) { /* solo SMTP */ } } class UserReportGenerator { void generate() { /* solo PDF */ } } // Cada clase cambia por una sola razón // Fácil de testear de forma aislada // Fácil de reutilizar en otros contextos
class OrderService { // instancia directa = acoplamiento máximo private MySQLOrderRepository repo = new MySQLOrderRepository(); void place(Order o) { repo.save(o); } } // Cambiar a PostgreSQL → modificar aquí // Imposible de testear con doble de prueba // Viola DIP: depende de un detalle volátil
class OrderService { private final OrderRepository repo; // interfaz // dependencia inyectada desde fuera OrderService(OrderRepository repo) { this.repo = repo; } void place(Order o) { repo.save(o); } } // MySQL, PostgreSQL, InMemory → mismo código // Tests: inyectar FakeOrderRepository
// Le pregunto el estado y decido yo if (account.getBalance() > amount) { account.setBalance( account.getBalance() - amount); } // Lógica de negocio fuera del objeto
// Le digo qué hacer, él decide cómo account.withdraw(amount); // BankAccount.withdraw() contiene // la regla de negocio — donde pertenece
// Conoce Order, Customer, Address, City double tax = order .getCustomer() .getAddress() .getCity() .getTaxRate(); // Cambiar Address → rompe aquí
// Order sabe cómo obtener su tasa double tax = order.getTaxRate(); // Cambios internos en Address // no afectan a este código // Menos acoplamiento → más mantenible