javascript-herencia-y-polimorfismo

Herencia y polimorfismo en JavaScript

La herencia es una característica que permite crear nuevas clases basadas en clases existentes.

Con la herencia, una clase puede disponer de las propiedades y métodos de otra, a la vez que añade o sobreescribe, con sus propios métodos.

Esta relación entre clases se conoce como relación padre-hijo o superclase-subclase.

En JavaScript, la herencia se implementa principalmente a través de clases, que fueron introducidas en ECMAScript 6 (ES6).

Antes de ES6, JavaScript utilizaba funciones constructoras y el objeto prototype para implementar la herencia. Con la introducción de la sintaxis de clases, la herencia se volvió más sencilla y clara.

Sintaxis básica de la herencia

En JavaScript para crear una subclase que herede de la clase base, usamos la palabra clave extends.

En primer lugar necesitamos definir una clase base, y a continuación crearíamos la clase derivada. Por ejemplo así,

class Animal {
  constructor(nombre) {
    this.nombre = nombre; // Propiedad compartida
  }

  hablar() {
    console.log(`${this.nombre} hace un sonido`);
  }
}

class Perro extends Animal {
  constructor(nombre, raza) {
    super(nombre); // Llama al constructor de la clase padre
    this.raza = raza; // Propiedad adicional
  }

  hablar() {
    console.log(`${this.nombre} dice ¡Guau!`);
  }
}

const miPerro = new Perro('Rex', 'Labrador');
miPerro.hablar(); // "Rex dice ¡Guau!"

En este ejemplo:

  • Animal es la clase base que tiene un constructor que asigna un valor a nombre y un método llamado hablar().
  • Perro es una subclase que hereda de Animal. Usa el método super() para llamar al constructor de la clase base y asignar el valor a nombre.
  • Además, sobrescribe el método hablar() para personalizar el comportamiento específico de los perros.

Palabra clave super

La palabra clave super() nos permite obtener una referencia a la clase base, desde la clase derivada. Por ejemplo, sirve para,

  1. Llamar al constructor de la superclase: Cuando necesitamos inicializar las propiedades heredadas desde la clase base.
  2. Acceder a los métodos de la superclase: Para hacer uso de métodos de la superclase dentro de la subclase.
class Perro extends Animal {
  constructor(nombre, raza) {
    super(nombre); // Llama al constructor de la clase padre
    this.raza = raza; // Propiedad adicional
  }
}

En el ejemplo anterior,

  • super(nombre) llama al constructor de Animal y pasa el argumento nombre para que la propiedad nombre sea inicializada.

Propiedades y métodos heredados

Cuando una subclase hereda de una superclase, hereda sus propiedades y métodos. Esto significa que podemos acceder a las propiedades y métodos de la superclase como si estuvieran definidos directamente en la subclase.

class Animal {
  constructor(nombre) {
    this.nombre = nombre;
  }

  hablar() {
    console.log(`${this.nombre} hace un sonido`);
  }
}

class Gato extends Animal {
  // No es necesario redefinir 'nombre', ya que se hereda de Animal
}

const miGato = new Gato('Whiskers');
miGato.hablar(); // "Whiskers hace un sonido"

Aquí, Gato hereda el método hablar() de Animal y lo puede utilizar sin tener que volver a definirlo.

Sobrescribir métodos en la subclase

Una característica de la herencia es que podemos sobrescribir los métodos de la clase base en la subclase. Esto nos permite personalizar el comportamiento.

Esto es útil cuando queremos que las subclases tengan comportamientos específicos, mientras conservamos la base de la clase original.

class Gato extends Animal {
  hablar() {
    console.log(`${this.nombre} dice ¡Miau!`);
  }
}

const miGato = new Gato('Rufus el gato');
miGato.hablar(); // "Rufus el gato dice ¡Miau!"

En este caso, Gato redefine el método hablar() para que emita un sonido diferente al del Animal.

Propiedades estáticas y herencia

Las subclases también heredan las propiedades estáticas. La clase base y la clase derivada comparten las variables y métodos estáticos (la clase derivada no tiene sus propias versiones)

class Animal {
  static especie = 'Animal'; // Propiedad estática

  //... más cosas
}

class Perro extends Animal {}

console.log(Perro.especie); // "Animal"

Perro.especie = "Perraco";
console.log(Animal.especie); // "Perraco"

Aquí,

  • La propiedad estática especie es heredada por la subclase Perro.
  • Si accedemos a Perro.especie obtenemos Animal.
  • Cuando modificamos Perro.especie, también modificamos Animal.especie.
  • Es decir, la propiedad especie es la misma para ambas clases.

¿Qué es el polimorfismo?

En otras palabras, el polimorfismo permite que los objetos de diferentes clases tengan diferentes comportamientos para el mismo método.

En JavaScript, el polimorfismo se puede lograr utilizando la herencia y la sobrescritura de métodos. Vamos a verlo con un ejemplo:

let animal = new Animal('Animal');
let cat = new Cat('Gato');
let dog = new Dog('Perro');

animal.makeSound(); // Haciendo sonido...
cat.makeSound(); // Miau!
dog.makeSound(); // Guau!
class Animal {
  constructor(name) {
    this.name = name;
  }

  makeSound() {
    console.log('Haciendo sonido...');
  }
}
class Cat extends Animal {
  constructor(name) {
    super(name);
  }

  makeSound() {
    console.log('Miau!');
  }
}
class Dog extends Animal {
  constructor(name) {
    super(name);
  }

  makeSound() {
    console.log('Guau!');
  }
}

En este ejemplo,

  • Tenemos una clase Animal con un método makeSound().
  • La clase Cat y la clase Dog heredan de la clase Animal y sobrescriben el método makeSound() con sus propias implementaciones.
  • Cuando se llama al método makeSound() en cada objeto, el comportamiento es diferente según la clase de objeto.