que-es-el-override-de-metodos-en-programacion

Qué es y cómo usar el override de métodos

Ya hemos visto que en la programación orientada a objetos (POO), uno de los conceptos más importantes y fundamentales es la capacidad de Herencia y Polimorfismo entre otros.

Ambos conceptos basan gran parte de su funcionamiento en la sobrescritura de métodos (o override en inglés) de métodos y variables en las clases derivadas.

La sobrescritura de métodos es una característica que permite a una clase derivada proporcionar una implementación diferente de un método que ya está definido en su clase base.

La sobrescritura se utiliza para modificar el comportamiento de métodos heredados, permitiendo que las clases las subclases personalicen o reemplacen el comportamiento de los métodos definidos en las clases base.

Caso práctico

Veamos un ejemplo para entender cómo se sobrescriben los métodos. Se viene todo un clásico para explicar herencia en POO, un ejemplo con Perritos y Gatitos.

Supongamos que tenemos una clase base Animal. Esta clase tiene un método de instancia llamado HacerSonido() que muestra en consola “El animal hace un sonido”.

Ahora cogemos una clase derivada Perro, que hereda de Animal, y proporcionamos una versión alternativa del método HacerSonido().

class Animal
{
    // Método en la clase base
    HacerSonido()
    {
        Console.Log("El animal hace un sonido");
    }
}

class Perro extends Animal
{
    // Sobrescritura del método en la clase hija
    override HacerSonido()
    {
        Console.Log("El perro ladra");
    }
}

Algunos lenguajes requerirán marcar el método sobreescrito con una palabra reservada (como override o new) y otros no (eso ahora me da igual, lo veremos al final con ejemplos en lenguajes concretos).

Ahora lo importante es entender como actúa la Herencia y el Polimorfismo con este método que hemos sobreescrito.

Llamando a Métodos Sobrescritos

Veamos los dos casos más sencillos:

  • Instancia de Animal guardada en variable Animal
  • Instancia de Perro guardada en variable Perro
Animal miAnimal = new Animal();
miAnimal.HacerSonido(); // Salida: El animal hace un sonido

Perro miPerro = new Perro();
miPerro.HacerSonido(); // Salida: El perro ladra

Como vemos, al invocar HacerSonido(), cada instancia llama su propia definición de HacerSonido(). Lo lógico y que era de esperar.

Aquí vemos que, realmente, la clase derivada Perro ha sobreescrito el método de su clase base Animal, y cuando la llamamos invoca a su método (no al de su clase base).

Ahora, el caso interesante es que pasa cuando guardamos una instancia de Perro en una variable de tipo Animal. Es decir, el caso interesante es este 👇

Animal animalPerro = new Perro();
animalPerro.HacerSonido(); // Salida: El perro ladra

En general, en la mayoría de lenguajes (cada uno con su detalle) lo normal es que se invoque el método de la clase derivada. Es decir, da igual el tipo de variable, lo que importa es la instancia que guardamos en ella.

Como la instancia tiene el método HacerSonido() sobreescrito, el método que se va a llamar es el que existe en la clase Perro. Aquí tenemos en funcionamiento el override de métodos y el Polimorfismo.

Algunos lenguajes pueden tener sus particularidades, como requerir marcar el método sobreescrito con una palabra reservada (como override o new). Pero, en principio, el funcionamiento “normal” del override de métodos en POO es el que acabamos de ver.

Palabra clave base

En algunos casos, puede ser útil invocar el método de la clase base desde la clase derivada utilizando la palabra clave base.

Perro extends Animal
{
    HacerSonido()
    {
        // Llamada al método de la clase base
        base.HacerSonido();
        Console.Log("El perro ladra");
        // Salida: El animal hace un sonido 
        //         El perro ladra
    }
}

En el ejemplo anterior, HacerSonido en la clase Perro primero llama a la implementación del método en la clase base Animal antes de agregar su propio comportamiento adicional.

En este caso, mostraría en pantalla ambos mensajes

  • “El animal hace un sonido”, al invocar el método base.HacerSonido()
  • “El perro ladra”, al invocar su propio código

Override de variables

El override de variables, también conocido como ocultamiento de variables, permite a una clase derivada declarar una variable con el mismo nombre que una variable en su clase base (por eso decimos que “oculta” la variable de la clase padre en la clase hija).

Esto significa que la variable de la clase hija oculta la variable de la clase padre y puede tener un valor diferente.

class Padre
{
    string Mensaje = "Hola desde la clase Padre";
}

class Hijo extends Padre
{
    new string Mensaje = "Hola desde la clase Hijo";
}

En este ejemplo,

  • La clase Hijo declara una variable Mensaje
  • Esta oculta la variable Mensaje de la clase Padre
  • Cuando se accede a la variable Mensaje desde una instancia de la clase Hijo
  • Por tanto, se muestra el valor Hola desde la clase Hijo

No es tan frecuente como el override de métodos. De hecho, no es especialmente útil. Pero si encontráis un lenguaje que lo permite, pues ahí esta.

Ejemplos en distintos lenguajes

Ahora veamos un ejemplo de cómo se realiza el override de métodos en distintos lenguajes de programación.

En C#, el override de funciones se realiza utilizando la palabra clave override en la clase derivada para sobrescribir el comportamiento de la función definida en la clase base.

public class Animal
{
    public virtual void HacerSonido()
    {
        Console.WriteLine("El animal hace un sonido");
    }
}

public class Perro : Animal
{
    public override void HacerSonido()
    {
        Console.WriteLine("Guau");
    }
}

public class Gato : Animal
{
    public override void HacerSido()
    {on
        Console.WriteLine("Miau");
    }
}

// Uso
Animal perro = new Perro();
Animal gato = new Gato();

perro.HacerSonido(); // Salida: Guau
gato.HacerSonido(); // Salida: Miau

En C++, el override de funciones se realiza utilizando la palabra clave override en la clase derivada para indicar que se está sobrescribiendo una función virtual de la clase base.

#include <iostream>

class Animal {
public:
    virtual void HacerSonido() const {
        std::cout << "El animal hace un sonido" << std::endl;
    }
};

class Perro : public Animal {
public:
    void HacerSonido() const override {
        std::cout << "Guau" << std::endl;
    }
};

class Gato : public Animal {
public:
    void HacerSonido() const override {
        std::cout << "Miau" << std::endl;
    }
};

// Uso
int main() {
    Animal* perro = new Perro();
    Animal* gato = new Gato();

    perro->HacerSonido(); // Salida: Guau
    gato->HacerSonido(); // Salida: Miau

    delete perro;
    delete gato;

    return 0;
}

En JavaScript podemos realizar la redefinición de métodos de la clase base a través de la declaración del mismo método en la clase derivada.

class Animal {
    hacerSonido() {
        console.log("El animal hace un sonido");
    }
}

class Perro extends Animal {
    hacerSonido() {
        console.log("Guau");
    }
}

class Gato extends Animal {
    hacerSonido() {
        console.log("Miau");
    }
}

// Uso
let animal1 = new Perro();
let animal2 = new Gato();

animal1.hacerSonido(); // Salida: Guau
animal2.hacerSonido(); // Salida: Miau

En TypeScript, el concepto de clases y herencia es similar al de JavaScript pero con la ventaja de la tipificación estática.

class Animal {
    hacerSonido(): void {
        console.log("El animal hace un sonido");
    }
}

class Perro extends Animal {
    hacerSonido(): void {
        console.log("Guau");
    }
}

class Gato extends Animal {
    hacerSonido(): void {
        console.log("Miau");
    }
}

// Uso
const perro: Animal = new Perro();
const gato: Animal = new Gato();

perro.hacerSonido(); // Salida: Guau
gato.hacerSonido(); // Salida: Miau

En Python, el override de funciones se realiza simplemente volviendo a definir el método en la clase derivada.

class Animal:
    def hacer_sonido(self):
        print("El animal hace un sonido")

class Perro(Animal):
    def hacer_sonido(self):
        print("Guau")

class Gato(Animal):
    def hacer_sonido(self):
        print("Miau")

# Uso
perro = Perro()
gato = Gato()

perro.hacer_sonido() # Salida: Guau
gato.hacer_sonido() # Salida: Miau