CodeGym /Cursos /JAVA 25 SELF /Problemas y limitaciones de la herencia

Problemas y limitaciones de la herencia

JAVA 25 SELF
Nivel 17 , Lección 4
Disponible

1. Limitaciones de la herencia en Java

Solo herencia simple de clases. En Java, una clase solo puede heredar de otra clase. A esto se le llama herencia simple. Por ejemplo, esto sí se puede:

class Animal { }
class Dog extends Animal { }

Pero esto no se permite:

class Animal { }
class Robot { }
// ¡ERROR! Java no admite la herencia múltiple de clases
class RoboDog extends Animal, Robot { }

Si intentas declarar una clase así, el compilador dirá: "class RoboDog cannot extend multiple classes". ¿Por qué? Porque la herencia múltiple conduce a ambigüedades: si ambos padres tienen un método con la misma firma, ¿cuál se utiliza? Es el famoso «problema del diamante» (diamond problem).

Interfaces en Java se pueden implementar tantas como se quiera, pero aún no las hemos estudiado. Hablaremos de ellas más adelante.

Los constructores no se heredan. Aunque tengas una clase base con un constructor conveniente, ese constructor no aparecerá automáticamente en la subclase. Hay que invocar explícitamente el constructor del padre mediante super(...) en el constructor de la subclase.

Los miembros privados no se heredan. Todos los (private)-miembros (campos y métodos) del padre no están disponibles en la subclase. Existen «dentro» del objeto, pero no se puede acceder a ellos directamente.

2. Problemas de una jerarquía frágil

Acoplamiento fuerte entre clases. Cuando creas una jerarquía de clases, las subclases quedan estrechamente acopladas a la clase base. Si cambias la clase base, esto puede afectar (o incluso romper) todas sus subclases. Imagina que tienes la clase Animal, de la que heredan Dog, Cat, Bird y otra decena más. Si cambias la estructura de Animal (por ejemplo, añades un nuevo parámetro obligatorio en el constructor), tendrás que revisar todas las subclases y actualizar su código. Esto es especialmente doloroso en proyectos grandes.

El problema de la herencia «que se rompe». A veces una subclase puede cambiar accidentalmente el comportamiento del que depende la clase base. Por ejemplo, la clase padre llama a su propio método dentro de otro método, y la subclase sobreescribe ese método y cambia su lógica. Como resultado, la clase base deja de funcionar como se esperaba.

class Animal {
    void makeSound() {
        System.out.println("Some sound");
    }
    void sleep() {
        System.out.println("Animal is going to sleep...");
        makeSound(); // El padre llama a su propio método
    }
}

class Dog extends Animal {
    @Override
    void makeSound() {
        System.out.println("Woof!");
    }
}

public class Main {
    public static void main(String[] args) {
        Animal a = new Dog();
        a.sleep();
    }
}

¿Qué imprimirá el programa?

Animal is going to sleep...
Woof!

La clase base contaba con que makeSound() era su propia implementación, ¡pero en realidad se invocará la versión de la subclase! Esto puede llevar a errores inesperados si la subclase sobreescribe el método con otra lógica.

3. Problema de la clase base frágil (fragile base class problem)

Es un problema real en proyectos grandes. Si cambias la clase base (por ejemplo, añades un campo o modificas la implementación de un método), corres el riesgo de romper el comportamiento de todas las subclases. A veces no se manifiesta de inmediato, y encontrar ese error puede llevar horas o incluso días.

Ilustración: supongamos que tienes la clase Shape con el método draw(). Decides añadir a Shape un nuevo método drawShadow(), que llama a draw(). Pero una de las subclases (Circle) sobreescribe draw(), y ahora, al invocar drawShadow() sobre Circle, el comportamiento puede resultar inesperado.

4. Acoplamiento fuerte y dificultades de refactorización

Cuando las clases están unidas mediante herencia, el cambio de una clase puede afectar a toda una cadena de dependencias. Esto hace que el código sea menos flexible, dificulta el refactorizado y la ampliación. A veces hay que reescribir jerarquías enteras para añadir nueva funcionalidad.

Ejemplo real

class Vehicle { /* ... */ }
class Car extends Vehicle { /* ... */ }
class Bicycle extends Vehicle { /* ... */ }
class Bus extends Vehicle { /* ... */ }

De repente llega un requisito: «Añadamos un patinete eléctrico». Pero el patinete eléctrico es tanto transporte como gadget. ¿Qué hacer? Si empiezas a estirar la jerarquía para encajar todas las nuevas entidades, se volverá incontrolable rápidamente.

5. Problema de reutilización de código sin relación lógica

Muy a menudo, los programadores novatos (y no solo ellos) usan la herencia para reutilizar código aunque no haya relación «es un» (is-a) entre las clases. Esto conduce a una arquitectura incorrecta.

Ejemplo de herencia incorrecta

class DatabaseUtils {
    void connect() { /* ... */ }
    void disconnect() { /* ... */ }
}

class User extends DatabaseUtils { // ¡Un usuario no "es" una utilidad de base de datos!
    String name;
}

Es más correcto usar composición: convertir DatabaseUtils en una clase separada y llamar a sus métodos desde donde corresponda, en lugar de heredar de ella.

6. Alternativas a la herencia

Composición (has-a)

Si un objeto «contiene» otro objeto, usa composición. Por ejemplo, una clase Car puede tener un campo Engine:

class Engine { /* ... */ }

class Car {
    private Engine engine;
    // ...
}

Delegación

En lugar de extender una clase, delega la realización de la tarea en otro objeto. Así mantienes la flexibilidad y reduces el acoplamiento de los componentes.

Interfaces

En Java, una clase puede implementar tantas interfaces como quiera. Esto permite combinar comportamientos de forma flexible sin una jerarquía rígida. Volveremos sobre las interfaces más adelante.

¿Cuándo conviene usar herencia?

Usa herencia solo cuando entre las clases exista una relación clara «es un» (is-a):

  • Un gato es un animal (Cat extends Animal)
  • Un círculo es una figura (Circle extends Shape)
  • Un administrador es un usuario (Admin extends User)

No uses la herencia solo para reutilizar código — para eso están la composición y la delegación.

7. Varios ejemplos prácticos

Ejemplo: jerarquía sobrecomplicada

class Animal { }
class Mammal extends Animal { }
class Cat extends Mammal { }
class PersianCat extends Cat { }
class SuperPersianCat extends PersianCat { }

Si tu jerarquía supera tres niveles, plantéate si no ha llegado el momento de parar. Las jerarquías demasiado profundas dificultan la comprensión y el mantenimiento del código.

Ejemplo: jerarquía plana

class Animal { }
class Cat extends Animal { }
class Dog extends Animal { }
class Bird extends Animal { }
class Fish extends Animal { }
class Spider extends Animal { }
class Platypus extends Animal { }
class Dragon extends Animal { }

Si tienes decenas de subclases y cada una se diferencia solo por un método, quizá convenga usar interfaces o composición.

8. Errores típicos al usar herencia

Error n.º 1: herencia sin relación «is-a».
Si la subclase en realidad no es una variedad de la clase base, la arquitectura se vuelve antinatural y pronto se descontrola. Por ejemplo, la clase User no debería heredar de DatabaseUtils, aunque parezca «cómodo».

Error n.º 2: sobrescribir métodos cambiando el contrato.
Si sobreescribes un método y cambias su lógica de manera que ya no cumple las expectativas de la clase base, provocarás errores inesperados. Por ejemplo, si la clase base cuenta con que el método draw() dibuja una figura y, en la subclase, de repente empieza a realizar efectos secundarios peligrosos — es una catástrofe.

Error n.º 3: jerarquías demasiado profundas o demasiado planas.
Una jerarquía demasiado profunda dificulta entender el código; una demasiado plana conduce a duplicaciones.

Error n.º 4: intentar sortear las limitaciones del lenguaje.
Se intenta implementar herencia múltiple con «apaños» (copiar y pegar, superclases «utilitarias»), lo que conduce al caos.

Error n.º 5: uso ciego de la herencia para reutilizar código.
A menudo lleva a relaciones inesperadas entre clases, complica las pruebas y el mantenimiento. Usa composición y delegación.

1
Cuestionario/control
Herencia e jerarquía, nivel 17, lección 4
No disponible
Herencia e jerarquía
Herencia e jerarquía
Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION