El ecosistema de desarrollo corporativo vive bajo el hechizo de Enterprise JavaBeans (EJB 2.1) y la ortodoxia de la Programación Orientada a Objetos llevada al paroxismo. Para escribir un registro en una base de datos o validar un formulario de facturación, la doctrina imperante exige declarar una interfaz remota (EJBObject), una interfaz local (EJBLocalHome), una clase de implementación abstracta (EntityBean), un descriptor XML de doscientas líneas y un despliegue pesado en un servidor de aplicaciones como WebLogic o WebSphere1 Rod Johnson expuso esta crisis de productividad en su obra seminal Expert One-on-One J2EE Design and Development (2002), sentando las bases de lo que se convertiría en el framework Spring y la reivindicación de los POJOs (Plain Old Java Objects)..
Lo que prometía modularidad y reutilización de código se ha transformado en un laberinto de abstracciones huecas donde la lógica de negocio queda enterrada bajo capas infinitas de fontanería arquitectónica.
La patología de la clase base frágil
El pecado fundacional de esta corriente radica en la veneración de la herencia como principal mecanismo de reutilización. Los manuales enseñaron a modelar los sistemas construyendo árboles taxonómicos del mundo real: Vehiculo deriva en Coche, que a su vez deriva en CocheElectrico.
En la práctica de ingeniería de software, los árboles de herencia profunda sufren del problema de la clase base frágil (Fragile Base Class problem)2 Barbara Liskov formuló el principio de sustitución (LSP) en 1987 precisamente para acotar este riesgo: si una función espera una instancia de tipo $T$, debe poder operar con cualquier subtipo $S$ sin conocer la diferencia ni alterar la corrección formal del programa.. Una subclase no solo hereda la firma de los métodos públicos de su ancestro, sino que queda indisolublemente acoplada a sus detalles de implementación interna.
// Ejemplo canónico de fragilidad por herencia
public class InstrumentedHashSet<E> extends HashSet<E> {
private int addCount = 0;
@Override
public boolean add(E e) {
addCount++;
return super.add(e);
}
@Override
public boolean addAll(Collection<? extends E> c) {
addCount += c.size();
return super.addAll(c); // ¡Peligro! super.addAll() invoca internamente a add()
}
}
Si el autor de la clase base HashSet decide implementar addAll() iterando e invocando internamente a add(), el contador addCount duplicará el cómputo de cada elemento. Para corregir este comportamiento, el autor de la subclase debe inspeccionar el código fuente del ancestro —violando la encapsulación— o asumir que cualquier actualización de la biblioteca base puede romper silenciosamente la lógica del sistema en producción.
La inflación de patrones y la pérdida de foco
La comunidad industrial reaccionó al fracaso de la herencia no simplificando, sino añadiendo más capas. El catálogo del Gang of Four (GoF) dejó de ser una recopilación pragmática de soluciones de diseño en C++ y Smalltalk para convertirse en un catecismo burocrático.
Surgieron constructos grotescos donde un simple cálculo financiero requería un AbstractTransactionProcessorFactory, gestionado por un TransactionPolicyProxyDecorator, orquestado mediante un CommandExecutionChainManager. La semántica de la computación quedó desplazada por la arquitectura de las interfaces:
Entrada de datos ──> [Proxy] ──> [Interceptor] ──> [Adapter] ──> [Facade] ──> [Worker]
▲
¿Dónde demonios está la regla de negocio?
Cuando un ingeniero necesita rastrear qué ocurre con una transferencia bancaria y se ve obligado a saltar por doce interfaces intermedias que solo delegan la llamada a la siguiente clase, el diseño no está resolviendo complejidad esencial; está fabricando complejidad accidental para justificar su propia existencia3 Fred Brooks diferenció en No Silver Bullet (1986) la complejidad esencial (inherente al problema a resolver) de la complejidad accidental (introducida por las propias herramientas y decisiones de ingeniería elegidas por el desarrollador)..
Composición sobre herencia y el retorno al dato
La salida a esta ciénaga técnica no exige inventar paradigmas esotéricos, sino aplicar el principio más olvidado de los propios autores del GoF: favorecer la composición de objetos sobre la herencia de clases.
En lugar de heredar comportamiento a través de la jerarquía de tipos, el software desacoplado ensambla piezas independientes mediante referencias explícitas. Si una clase necesita registrar métricas de almacenamiento, no hereda de HashSet: contiene una instancia interna de Set y delega exclusivamente las operaciones que le interesan mediante el patrón de envoltura (wrapper):
// Composición limpia mediante delegación explícita
public class InstrumentedSet<E> implements Set<E> {
private final Set<E> internalSet;
private int addCount = 0;
public InstrumentedSet(Set<E> internalSet) {
this.internalSet = internalSet;
}
public boolean add(E e) {
addCount++;
return internalSet.add(e);
}
public boolean addAll(Collection<? extends E> c) {
addCount += c.size();
return internalSet.addAll(c);
}
// ... resto de métodos delegados limpiamente ...
}
La composición transforma dependencias rígidas en contratos desacoplados. La clase no asume nada sobre la mecánica interna del objeto delegado; solo respeta su interfaz pública.
La lección que deja el colapso de los monolitos EJB y el delirio taxonómico de principios de los 2000 es contundente: el software no existe para crear catedrales de clases abstractas que intenten clasificar el universo. Existe para transformar datos de entrada en datos de salida con la menor fricción mental posible. Cada capa de abstracción que no resuelva un problema concreto del dominio es deuda técnica introducida deliberadamente.