Programación Orientada a Objetos 8 - Herencia

En este capítulo hablaremos de la herencia, pilar fundamental de la programación orientada a objetos que facilita la reutilización de código y la creación de jerarquías de clases.

Veremos cómo la herencia permite extender y modificar el comportamiento de una clases, promoviendo un diseño más eficiente y organizado.

herencia

¿Qué es la Herencia?

La herencia parte de la relación “es un“, una clasificación. En Matemática podríamos decir que los enteros y los reales son una subclase de número, en Zoología podemos decir que orca es un mamífero.

Acá podemos ver algunos casos:

herencia

Puntos a notar:

  • Las clasificaciones no tienen por qué ser complejas.
  • La ubicación en la jerarquía se basa en la relación “es un“.
  • Cada clase hereda propiedades (atributos) y comportamiento (métodos) de sus ancestros
  • Algunas clases pueden ser abstractas (no van a tener instancias).
  • En UML se modela con una flecha apuntando del heredero al ancestro.

Este mecanismo permite que una clase herede atributos y métodos de otra clase (superclase o clase base).

Esto significa que se pueden crear nuevas clases sobre las existentes, extendiendo, jerarquizando, y especializando su comportamiento, sin tener que escribir todo de cero.

Clases Base y Derivadas

Clase Base (Superclase, Ancestro): Es la clase de la cual se hereda, también llamada clase Padre. Define atributos y métodos comunes que serán compartidos por sus subclases.
Clase Derivada (Subclase, Heredero): Es la clase que hereda de la superclase, o clase Hija. Puede añadir sus propios atributos y métodos, además de los heredados, y redefinir comportamientos (esto lo veremos en el capítulo de polimorfismo).

Uso de extends

En Java, utilizamos la palabra clave extends para establecer una relación de herencia entre dos clases. La subclase extiende a la superclase.

Sobrescribir Métodos

Las subclases pueden sobrescribir los métodos heredados de la superclase, para proporcionar una implementación específica.

Esto se hace simplemente definiendo un método en la subclase con la misma firma que el método en la superclase.

El método super()

Vinculado a la sobre escritura de métodos, el método super() se utiliza para invocar, desde la subclase, al método sobrescrito de la superclase.

Es habitual su uso en los constructores, cuando la superclase tiene un constructor con argumentos para inicializar, pero se puede usar desde cualquier método.

Herencia Simple vs Herencia Múltiple

En lenguajes como C++ se permite la herencia múltiple, quiere decir que una clase puede heredar de más de una clase.

En Java y en la mayoría de los lenguajes no se permite la herencia múltiple, quiere decir que una clase solo puede tener un “Padre”, esto facilita la codificación.

Ejemplo Práctico: Jerarquía de Clases para Vehículos

Vamos a crear una jerarquía de clases para representar diferentes tipos de vehículos: un vehículo genérico, un auto y una motocicleta.

Base: Vehículo


    public class Vehiculo {
      protected String marca;
      protected String modelo;
  
      public Vehiculo(String marca, String modelo) {
          this.marca = marca;
          this.modelo = modelo;
      }
  
      public void mostrarDetalles() {
          System.out.println("Marca: " + marca + ", Modelo: " + modelo);
      }
    }                
                

Derivada: Auto


    public class Auto extends Vehiculo {
      private int numeroDePuertas;
  
      public Auto(String marca, String modelo, int numeroDePuertas) {
          super(marca, modelo); // Llamada al constructor de la superclase
          this.numeroDePuertas = numeroDePuertas;
      }
  
      // Sobreescritura del método mostrarDetalles, pisa el del padre
      public void mostrarDetalles() {
          super.mostrarDetalles(); // Llamada al método de la superclase (padre)
          System.out.println("Número de Puertas: " + numeroDePuertas);
      }
    }                
                

Derivada: Motocicleta


    public class Motocicleta extends Vehiculo {
      private boolean tieneSidecar;
  
      public Motocicleta(String marca, String modelo, boolean tieneSidecar) {
          super(marca, modelo); // Llamada al constructor de la superclase
          this.tieneSidecar = tieneSidecar;
      }
  
      // Sobreescritura del método mostrarDetalles, pisa el del padre
      public void mostrarDetalles() {
          super.mostrarDetalles(); // Llamada al método de la superclase
          System.out.println("¿Tiene Sidecar?: " + (tieneSidecar ? "Sí" : "No"));
      }
    }                
                

Uso de las Clases


    Vehiculo miAuto = new Auto("Toyota", "Corolla", 4);
    miAuto.mostrarDetalles();

    Vehiculo miMotocicleta = new Motocicleta("Harley-Davidson", "Street 750", false);
    miMotocicleta.mostrarDetalles();                
                

Conclusiones

La herencia es una herramienta poderosa en la programación orientada a objetos que facilita la extensión y la reutilización de código.

Permite a los desarrolladores construir sistemas complejos y jerárquicos de una manera más intuitiva y modular, asegurando que el código sea fácil de entender y mantener.

Mediante la herencia podemos reducir la redundancia, promover la consistencia y adaptar el comportamiento de las clases para satisfacer las necesidades específicas de nuestra aplicación.

A medida que avances, verás cómo la herencia, junto con el polimorfismo, se convierte en un componente esencial en el diseño de tus proyectos de software.