Herencia con extends: Reutilización de Código en Clases
La herencia en JavaScript permite que una clase herede propiedades y métodos de otra clase, facilitando la reutilización de código y la creación de jerarquías lógicas.
TL;DR - Resumen rápido
- La palabra clave extends permite que una clase herede de otra clase
- super() llama al constructor de la clase padre y debe ser lo primero en el constructor hijo
- super.metodo() permite llamar métodos de la clase padre
- Puedes sobrescribir métodos del padre para cambiar su comportamiento
- instanceof verifica si un objeto es instancia de una clase o sus padres
- La herencia permite reutilizar código y crear jerarquías lógicas
Introducción a Herencia
La herencia es un pilar fundamental de la programación orientada a objetos que permite que una clase (clase hija o subclase) herede propiedades y métodos de otra clase (clase padre o superclase). En JavaScript moderno, la palabra clave extends facilita la creación de estas relaciones de herencia de forma clara y expresiva.
La herencia te permite reutilizar código existente, crear jerarquías lógicas de clases que reflejan relaciones del mundo real, y extender funcionalidad sin modificar el código original. Por ejemplo, puedes tener una clase Animal con métodos básicos, y luego crear clases Perro y Gato que hereden de Animal y agreguen comportamiento específico.
Reutilización de código
La herencia permite escribir código común una sola vez en la clase padre y reutilizarlo en todas las clases hijas. Esto reduce duplicación, facilita el mantenimiento y hace tu código más organizado y escalable.
Sintaxis Básica de extends
Para crear una clase que herede de otra, usas la palabra clave extends seguida del nombre de la clase padre. La clase hija automáticamente tiene acceso a todas las propiedades y métodos públicos de la clase padre, además de poder definir sus propios métodos y propiedades específicas.
Este ejemplo muestra la sintaxis básica de herencia. La clase Perro extiende Animal usando extends, lo que significa que hereda todos los métodos y propiedades de Animal. Las instancias de Perro pueden usar tanto sus propios métodos (ladrar) como los métodos heredados de Animal (hacerSonido, dormir). La propiedad nombre también se hereda y está disponible en las instancias de Perro.
Herencia automática
Con extends, la clase hija hereda automáticamente todos los métodos y propiedades públicos de la clase padre. No necesitas copiar código ni hacer nada especial: simplemente usas extends y todo está disponible.
super() en el Constructor
Cuando una clase hija define su propio constructor, debe llamar a super() antes de usar this. La función super() invoca al constructor de la clase padre, pasándole los parámetros necesarios. Esta llamada es obligatoria y debe ser la primera línea en el constructor de la clase hija.
Este ejemplo muestra cómo usar super() correctamente. La clase Perro tiene un constructor que acepta tres parámetros: nombre, edad y raza. Primero llama a super(nombre, edad) para inicializar las propiedades del padre, y luego inicializa su propia propiedad raza. Si intentas usar this antes de llamar super(), JavaScript lanza un ReferenceError.
Reglas de super()
Las reglas son estrictas: super() debe ser la primera declaración en el constructor de la clase hija, debe llamarse exactamente una vez, y no puedes acceder a this antes de llamar super(). Si tu clase hija no define constructor, JavaScript automáticamente crea uno que llama a super() con todos los argumentos recibidos.
super() es obligatorio
Si defines un constructor en una clase hija, DEBES llamar super() antes de usar this. Olvidar super() o intentar usar this antes de llamarlo resulta en un ReferenceError que detiene la ejecución del código.
super para Métodos del Padre
Además de super() para constructores, puedes usar super.metodo() para llamar métodos de la clase padre desde la clase hija. Esto es útil cuando quieres sobrescribir un método pero mantener parte de la funcionalidad original, extendiendo el comportamiento en lugar de reemplazarlo completamente.
Este ejemplo demuestra cómo extender métodos del padre. La clase Auto sobrescribe obtenerInfo() y arrancar(), pero en lugar de reemplazar completamente la funcionalidad, llama a super.obtenerInfo() y super.arrancar() para obtener el resultado del método del padre, y luego lo extiende con información adicional. Esto permite reutilizar la lógica existente mientras agregas comportamiento específico.
Extender vs reemplazar
Usar super.metodo() te permite extender la funcionalidad del padre en lugar de reemplazarla completamente. Esto es útil cuando el método del padre hace algo útil que quieres conservar mientras agregas comportamiento adicional.
Sobrescribir Métodos
Sobrescribir métodos (method overriding) significa definir un método en la clase hija con el mismo nombre que un método de la clase padre. Cuando llamas a ese método en una instancia de la clase hija, se ejecuta la versión de la clase hija, no la del padre. Esto permite personalizar el comportamiento para cada subclase.
Este ejemplo muestra sobrescritura completa de métodos. La clase Forma define calcularArea() con una implementación genérica. Las clases Rectangulo y Circulo sobrescriben completamente este método con sus propias implementaciones específicas. Cada clase calcula el área de forma diferente según su geometría. No usan super porque reemplazan completamente la funcionalidad, no la extienden.
- <strong>Mismo nombre</strong>: El método en la clase hija debe tener exactamente el mismo nombre que en el padre
- <strong>Reemplaza completamente</strong>: Sin super, el método del padre no se ejecuta en absoluto
- <strong>Polimorfismo</strong>: Diferentes clases pueden tener implementaciones diferentes del mismo método
- <strong>Decisión en runtime</strong>: JavaScript decide qué versión ejecutar basándose en el tipo real del objeto
- <strong>Extiende con super</strong>: Puedes llamar super.metodo() si quieres ejecutar el del padre también
Polimorfismo
La sobrescritura de métodos es la base del polimorfismo: la capacidad de tratar objetos de diferentes clases de manera uniforme mientras cada uno responde de forma específica. Puedes tener un array de Formas y llamar calcularArea() en cada una, obteniendo el cálculo correcto automáticamente.
Cadena de Herencia Multinivel
JavaScript permite crear cadenas de herencia donde una clase hereda de otra, que a su vez hereda de otra. Esto crea jerarquías multinivel que reflejan relaciones complejas del mundo real. Una clase puede acceder a propiedades y métodos de toda su cadena de ancestros, no solo del padre directo.
Este ejemplo demuestra una cadena de herencia de cuatro niveles: SerVivo → Animal → Mamifero → Perro. La clase Perro hereda métodos de todas las clases en la cadena. Cuando creas una instancia de Perro, tiene acceso a ladrar() (propio), amamantar() (de Mamifero), moverse() (de Animal), y respirar() (de SerVivo). JavaScript recorre la cadena de prototipos hacia arriba hasta encontrar el método solicitado.
Profundidad de herencia
Aunque JavaScript permite cadenas de herencia profundas, es recomendable mantenerlas relativamente planas. Jerarquías muy profundas se vuelven difíciles de entender y mantener. En general, 2-3 niveles de profundidad son suficientes para la mayoría de casos. Si necesitas más, considera si composition (composición) podría ser mejor que inheritance (herencia).
Evita jerarquías profundas
Cadenas de herencia muy profundas (más de 3-4 niveles) dificultan el mantenimiento y razonamiento sobre el código. Si te encuentras creando jerarquías muy profundas, considera usar composición en lugar de herencia.
Verificar Herencia con instanceof
El operador instanceof te permite verificar si un objeto es una instancia de una clase específica o de cualquiera de sus clases padre. Devuelve true si el objeto fue creado con esa clase o hereda de ella, y false en caso contrario. Es útil para verificar tipos antes de ejecutar operaciones específicas.
Este ejemplo muestra cómo usar instanceof para verificar tipos. Un objeto de tipo Perro devuelve true para instanceof Perro (es directamente de esa clase) y también para instanceof Animal (hereda de Animal). Sin embargo, devuelve false para instanceof Gato porque no hay relación de herencia. Esto permite escribir código que se comporta diferente según el tipo de objeto, como la función hacerSonido que verifica el tipo antes de llamar métodos específicos.
Verificación de tipo segura
instanceof es útil para verificar tipos antes de usar métodos específicos de una clase. Esto previene errores al intentar llamar métodos que no existen y permite implementar lógica condicional basada en el tipo de objeto.
Clases Hijas sin Constructor
Si una clase hija no define su propio constructor, JavaScript automáticamente proporciona uno que simplemente llama a super() pasando todos los argumentos recibidos. Esto es conveniente cuando la clase hija no necesita inicializar propiedades adicionales y solo quiere agregar métodos.
Este ejemplo compara dos enfoques. ProductoDigital no define constructor, por lo que JavaScript automáticamente crea uno equivalente a constructor(...args) super(...args) que pasa todos los argumentos al constructor del padre. ProductoFisico define su propio constructor porque necesita inicializar la propiedad peso adicional. Usa el constructor cuando necesites propiedades adicionales; omítelo cuando solo agregues métodos.
Constructor automático
Si tu clase hija solo agrega métodos y no necesita propiedades adicionales, puedes omitir el constructor completamente. JavaScript automáticamente creará uno que llama a super() con todos los argumentos recibidos.
Ejemplo Práctico
Este ejemplo práctico demuestra un sistema de empleados que usa herencia para modelar diferentes tipos de empleados. Muestra cómo combinar extends, super, sobrescritura de métodos y propiedades adicionales en un caso de uso realista.
Este ejemplo muestra una jerarquía completa: Persona → Empleado → Gerente. Cada nivel agrega funcionalidad específica. Persona proporciona datos básicos, Empleado agrega información laboral y cálculos de salario, y Gerente agrega gestión de equipo. Cada clase sobrescribe presentarse() usando super para extender el mensaje del padre. Este patrón permite código reutilizable y organizado que modela relaciones del mundo real de forma natural.
Jerarquías del mundo real
La herencia es natural para modelar jerarquías del mundo real como personas-empleados-gerentes, productos-productos digitales-productos físicos, o formas geométricas-rectángulos-cuadrados. Cada nivel agrega o especializa funcionalidad del nivel anterior.
Resumen: Herencia con extends
Conceptos principales:
- •extends permite que una clase herede de otra clase padre
- •super() llama al constructor del padre, debe ser primero en constructor hijo
- •super.metodo() permite llamar métodos del padre y extenderlos
- •Puedes sobrescribir métodos definiendo uno con el mismo nombre
- •instanceof verifica si un objeto es instancia de una clase
- •Puedes crear cadenas de herencia multinivel
Mejores prácticas:
- •Usa herencia para relaciones "es un" (un Perro es un Animal)
- •Mantén las jerarquías relativamente planas (2-3 niveles máximo)
- •Llama super() antes de usar this en constructores
- •Usa super.metodo() para extender funcionalidad en vez de duplicar
- •Documenta claramente la jerarquía de clases y sus responsabilidades
- •Considera composición si la jerarquía se vuelve muy compleja