Fundamentos
Tutorial Fundamentos: Herencia
Python: Aprende el concepto de herencia y cómo reutilizar y extender clases para promover un código estructurado y eficiente.
Aprende Fundamentos GRATIS y certifícateConcepto de herencia: reutilización y extensión de clases
La herencia es un principio fundamental en la programación orientada a objetos que permite crear nuevas clases basadas en clases existentes. A través de ella, una clase derivada reutiliza atributos y métodos de una clase base, posibilitando la extensión y especialización del comportamiento sin necesidad de reinventar el código.
En Python, la herencia se implementa indicando la clase base entre paréntesis al definir la clase derivada. Esto facilita la creación de jerarquías de clases donde las subclases heredan propiedades y comportamientos de sus superclases. Por ejemplo:
class Vehiculo:
def __init__(self, marca):
self.marca = marca
def avanzar(self):
print(f"El vehículo {self.marca} avanza.")
class Coche(Vehiculo):
def tocar_claxon(self):
print(f"El coche {self.marca} toca el claxon.")
En este ejemplo, Coche
extiende a Vehiculo
, heredando sus atributos y métodos. La clase Coche
puede usar el método avanzar
y también añadir funcionalidades propias como tocar_claxon
. Esto demuestra cómo la herencia promueve la extensibilidad de las clases.
Además, las clases derivadas pueden sobrescribir métodos de la clase base para modificar o mejorar su comportamiento. Utilizando la función super()
, es posible llamar al método original y luego ampliar su funcionalidad:
class Coche(Vehiculo):
def avanzar(self):
super().avanzar()
print(f"El coche {self.marca} avanza más rápido.")
Aquí, Coche
redefine el método avanzar
, pero utiliza super().avanzar()
para mantener el comportamiento original antes de agregar modificaciones. Este enfoque es esencial para aprovechar la reutilización del código y mantener una arquitectura coherente.
La herencia también facilita la creación de relaciones jerárquicas entre clases, reflejando conceptos del mundo real. Por ejemplo, si tenemos una clase Animal
y subclases como Perro
y Gato
, podemos modelar comportamientos y características comunes en la superclase y especificar detalles en las subclases.
Es importante usar la herencia de manera adecuada para evitar una dependencia excesiva entre clases y promover el polimorfismo, permitiendo que objetos de diferentes clases puedan ser tratados de forma uniforme. La herencia bien aplicada mejora la modularidad y la legibilidad del código, facilitando su mantenimiento y evolución.
Sintaxis y uso de herencia en diferentes lenguajes
La herencia es un concepto central en la programación orientada a objetos, presente en diversos lenguajes, aunque cada uno implementa su sintaxis de manera particular. A pesar de las diferencias sintácticas, el objetivo común es permitir que una clase derivada herede atributos y métodos de una clase base, fomentando la reutilización y extensión del código.
En lenguajes como Java, la herencia se especifica utilizando la palabra clave extends
. Por ejemplo, public class Coche extends Vehiculo
indica que la clase Coche
hereda de Vehiculo
. En C++, se utiliza el símbolo :
seguido del especificador de acceso y el nombre de la clase base: class Coche : public Vehiculo
. A diferencia de estos, Python ofrece una sintaxis más sencilla y flexible.
En Python, la herencia se indica colocando el nombre de la clase base entre paréntesis al definir la clase derivada:
class Vehiculo:
def desplazarse(self):
print("El vehículo se mueve.")
class Coche(Vehiculo):
def desplazarse(self):
print("El coche avanza en la carretera.")
En este ejemplo, Coche
hereda de Vehiculo
y redefine el método desplazarse
. La simplicidad de la sintaxis en Python facilita la creación de jerarquías de clases y la sobrescritura de métodos para personalizar el comportamiento de las subclases.
Además, Python soporta la herencia múltiple, permitiendo que una clase derive de más de una clase base. Esto se logra listando las clases base separadas por comas:
class Electricidad:
def encender(self):
print("El dispositivo se enciende eléctricamente.")
class Bateria:
def cargar(self):
print("La batería se está cargando.")
class CocheElectrico(Electricidad, Bateria):
pass
auto = CocheElectrico()
auto.encender()
auto.cargar()
Aquí, CocheElectrico
hereda de Electricidad
y Bateria
, obteniendo acceso a los métodos de ambas clases. La herencia múltiple en Python proporciona una gran flexibilidad, aunque requiere manejar con cuidado posibles conflictos en los nombres de métodos.
En otros lenguajes, la herencia múltiple puede estar restringida o implementada de manera diferente. Por ejemplo, Java no permite herencia múltiple de clases, pero sí permite implementar múltiples interfaces. En C#, se utiliza un enfoque similar al de Java, donde una clase puede heredar de una clase base y además implementar interfaces.
La manera de invocar al constructor de la clase base también varía entre lenguajes. En Python, se utiliza la función super()
dentro del constructor de la subclase:
class Vehiculo:
def __init__(self, marca):
self.marca = marca
class Coche(Vehiculo):
def __init__(self, marca, modelo):
super().__init__(marca)
self.modelo = modelo
La llamada a super().__init__(marca)
permite inicializar los atributos heredados de Vehiculo
. En C++, se usan inicializadores de lista en el constructor de la subclase, y en Java, se utiliza la palabra clave super
seguida de los parámetros correspondientes.
La sobrescritura de métodos es una práctica común en la herencia para modificar o ampliar el comportamiento de los métodos heredados. En Python, simplemente se redefine el método en la subclase con el mismo nombre:
class Vehiculo:
def frenar(self):
print("El vehículo está frenando.")
class Bicicleta(Vehiculo):
def frenar(self):
print("La bicicleta reduce su velocidad.")
Cuando se invoca frenar
en una instancia de Bicicleta
, se ejecuta la versión redefinida en la subclase. En lenguajes como Java, se utiliza la anotación @Override
para indicar explícitamente la sobrescritura.
Es relevante mencionar que en algunos lenguajes se requieren especificadores de acceso para controlar la visibilidad de los métodos y atributos heredados. En C++, por ejemplo, se utiliza public
, protected
o private
al heredar: class Coche : public Vehiculo
. En Python, los atributos y métodos son públicos por defecto, aunque se pueden usar convenciones de nomenclatura para indicar que un atributo es privado, precediendo su nombre con doble guión bajo __
.
La herencia en Python también permite utilizar funciones integradas como isinstance()
y issubclass()
para verificar relaciones entre objetos y clases:
print(isinstance(auto, CocheElectrico)) # Devuelve True
print(issubclass(CocheElectrico, Vehiculo)) # Devuelve False
Estas funciones son útiles para comprender la jerarquía de clases y para implementar comportamientos específicos basados en el tipo de objeto.
Herencia simple vs. herencia múltiple: diferencias y retos
Herencia es una característica fundamental en la programación orientada a objetos que permite a las clases derivadas heredar atributos y métodos de las clases base. En la herencia simple, una clase hereda de una única clase base, lo que facilita la reutilización del código y mantiene una jerarquía más sencilla y comprensible. Por otro lado, la herencia múltiple permite que una clase herede de varias clases base, ofreciendo mayor flexibilidad pero introduciendo complejidades adicionales y potenciales conflictos.
En Python, la herencia múltiple se implementa al listar múltiples clases base separadas por comas en la definición de una clase:
class A:
def metodo(self):
print("Método de A")
class B:
def metodo(self):
print("Método de B")
class C(A, B):
pass
objeto = C()
objeto.metodo()
En este ejemplo, la clase C
hereda de A
y B
. Al llamar a objeto.metodo()
, se ejecuta el método de A
debido al Orden de Resolución de Métodos (MRO, Method Resolution Order), que determina la precedencia de las clases base. El MRO sigue un orden linealizado que prioriza las clases definidas primero en la lista de herencia.
La herencia múltiple puede ocasionar problemas como el diamante de herencia, donde una clase hereda de dos clases que a su vez heredan de una clase común:
class Vehiculo:
def descripcion(self):
print("Vehículo genérico")
class Terrestre(Vehiculo):
def descripcion(self):
print("Vehículo terrestre")
class Acuatico(Vehiculo):
def descripcion(self):
print("Vehículo acuático")
class Anfibio(Terrestre, Acuatico):
pass
vehiculo_anfibio = Anfibio()
vehiculo_anfibio.descripcion()
En este caso, Anfibio
hereda de Terrestre
y Acuatico
, que ambos heredan de Vehiculo
. Al invocar vehiculo_anfibio.descripcion()
, se ejecuta el método de Terrestre
según el MRO. Esto puede generar ambigüedad si no se gestiona correctamente, ya que podría no quedar claro qué método se debería ejecutar.
Para manejar estas situaciones, Python utiliza el MRO y se puede emplear la función super()
para acceder a métodos de las clases base de forma ordenada:
class Terrestre(Vehiculo):
def descripcion(self):
super().descripcion()
print("Características terrestres")
class Acuatico(Vehiculo):
def descripcion(self):
super().descripcion()
print("Características acuáticas")
class Anfibio(Terrestre, Acuatico):
def descripcion(self):
super().descripcion()
print("Características anfibias")
vehiculo_anfibio = Anfibio()
vehiculo_anfibio.descripcion()
Aquí, cada clase llama al método descripcion
de su clase base con super()
, lo que permite encadenar las llamadas según el MRO y combinar las descripciones. Sin embargo, esta técnica requiere una comprensión profunda del orden de resolución y puede complicarse en jerarquías extensas.
La herencia simple evita estos problemas al tener una única clase base. Esto simplifica el diseño y hace que el comportamiento de las clases sea más predecible. Muchos lenguajes, como Java, optan por limitar la herencia a una sola clase base y permiten la implementación de múltiples interfaces para alcanzar cierta flexibilidad sin incurrir en los retos de la herencia múltiple.
Los retos de la herencia múltiple incluyen:
- Ambigüedad en la resolución de métodos y atributos: Cuando las clases base tienen métodos o atributos con el mismo nombre, puede ser difícil determinar cuál utilizar.
- Complejidad en el mantenimiento: El seguimiento de la jerarquía de clases y el MRO puede hacer que el código sea más difícil de entender y mantener.
- Posibles conflictos en el diseño: La necesidad de coordinar el comportamiento de múltiples clases base puede llevar a contradicciones y errores si no se planifica cuidadosamente.
Debido a estos desafíos, es común preferir la composición sobre la herencia múltiple. La composición consiste en construir clases complejas mediante la inclusión de objetos de otras clases, delegando responsabilidades en lugar de heredar directamente. Esto promueve un diseño más modular y reduce las dependencias entre clases.
Además, aunque Python permite la herencia múltiple, es recomendable utilizarla con precaución y únicamente cuando aporta un claro beneficio. Es fundamental comprender el MRO y diseñar las clases de manera que minimicen los conflictos y mantengan la coherencia en el comportamiento.
Ejemplos prácticos: diseño de jerarquías de clases
Para ilustrar cómo aplicar la herencia en el diseño de jerarquías de clases, consideremos un ejemplo práctico de un sistema de vehículos. Queremos modelar diferentes tipos de vehículos compartiendo características comunes, pero también con comportamientos específicos.
Comenzamos definiendo una clase base Vehiculo
que representará las propiedades y métodos generales de cualquier vehículo.
class Vehiculo:
def __init__(self, marca, modelo):
self.marca = marca
self.modelo = modelo
def arrancar(self):
print(f"{self.marca} {self.modelo} está arrancando.")
def detener(self):
print(f"{self.marca} {self.modelo} se ha detenido.")
La clase Vehiculo
tiene atributos como marca
y modelo
, y métodos comunes arrancar
y detener
. Ahora, crearemos subclases que heredan de Vehiculo
y añaden funcionalidades específicas.
Clase Coche
como subclase de Vehiculo
:
class Coche(Vehiculo):
def __init__(self, marca, modelo, num_puertas):
super().__init__(marca, modelo)
self.num_puertas = num_puertas
def abrir_maletero(self):
print(f"Abriendo el maletero de {self.marca} {self.modelo}.")
La clase Coche
añade el atributo num_puertas
y el método abrir_maletero
, manteniendo los atributos y métodos de Vehiculo
gracias a la función super()
.
Clase Motocicleta
como subclase de Vehiculo
:
class Motocicleta(Vehiculo):
def __init__(self, marca, modelo, tipo):
super().__init__(marca, modelo)
self.tipo = tipo
def hacer_caballito(self):
print(f"{self.marca} {self.modelo} está haciendo un caballito.")
La clase Motocicleta
introduce el atributo tipo
y el método hacer_caballito
, extendiendo la funcionalidad de Vehiculo
.
Clase Camion
como subclase de Vehiculo
:
class Camion(Vehiculo):
def __init__(self, marca, modelo, capacidad_carga):
super().__init__(marca, modelo)
self.capacidad_carga = capacidad_carga
def cargar(self, peso):
if peso <= self.capacidad_carga:
print(f"Cargando {peso} toneladas en {self.marca} {self.modelo}.")
else:
print(f"El peso excede la capacidad de carga de {self.capacidad_carga} toneladas.")
La clase Camion
incorpora el atributo capacidad_carga
y el método cargar
, permitiendo manejar operaciones específicas de un camión.
Creación y uso de objetos de las clases:
coche = Coche("Toyota", "Corolla", 4)
moto = Motocicleta("Yamaha", "MT-07", "Deportiva")
camion = Camion("Volvo", "FH16", 25)
coche.arrancar()
coche.abrir_maletero()
coche.detener()
print("---")
moto.arrancar()
moto.hacer_caballito()
moto.detener()
print("---")
camion.arrancar()
camion.cargar(20)
camion.detener()
Resultado esperado:
Toyota Corolla está arrancando.
Abriendo el maletero de Toyota Corolla.
Toyota Corolla se ha detenido.
---
Yamaha MT-07 está arrancando.
Yamaha MT-07 está haciendo un caballito.
Yamaha MT-07 se ha detenido.
---
Volvo FH16 está arrancando.
Cargando 20 toneladas en Volvo FH16.
Volvo FH16 se ha detenido.
Jerarquía de clases con herencia múltiple:
Ahora, imaginemos un vehículo anfibio que puede comportarse como un coche y como una embarcación. Creamos una clase Embarcacion
:
class Embarcacion:
def navegar(self):
print("La embarcación está navegando.")
def anclar(self):
print("La embarcación está anclada.")
La clase Amfibio
hereda de Vehiculo
y Embarcacion
, combinando funcionalidades terrestres y acuáticas:
class Anfibio(Vehiculo, Embarcacion):
def __init__(self, marca, modelo):
Vehiculo.__init__(self, marca, modelo)
def transformar(self, modo):
if modo == "tierra":
print(f"{self.marca} {self.modelo} se transforma para moverse en tierra.")
elif modo == "agua":
print(f"{self.marca} {self.modelo} se transforma para navegar en agua.")
else:
print("Modo desconocido.")
Uso de la clase Anfibio
:
anfibio = Anfibio("Gibbs", "Biski")
anfibio.arrancar()
anfibio.transformar("agua")
anfibio.navegar()
anfibio.transformar("tierra")
anfibio.detener()
Resultado esperado:
Gibbs Biski está arrancando.
Gibbs Biski se transforma para navegar en agua.
La embarcación está navegando.
Gibbs Biski se transforma para moverse en tierra.
Gibbs Biski se ha detenido.
En este ejemplo, Anfibio
utiliza herencia múltiple para incorporar métodos de Vehiculo
y Embarcacion
, ofreciendo una funcionalidad híbrida.
Diseño de una jerarquía con clases abstractas:
Para definir comportamientos comunes y obligar a las subclases a implementar ciertos métodos, podemos usar clases abstractas con el módulo abc
de Python.
from abc import ABC, abstractmethod
class Animal(ABC):
@abstractmethod
def hacer_sonido(self):
pass
def dormir(self):
print("El animal está durmiendo.")
La clase Animal
es una clase abstracta con un método abstracto hacer_sonido
. Las subclases deben implementar este método.
Subclases que heredan de Animal
:
class Perro(Animal):
def hacer_sonido(self):
print("El perro ladra: ¡Guau guau!")
class Gato(Animal):
def hacer_sonido(self):
print("El gato maúlla: ¡Miau!")
Uso de las clases animales:
animales = [Perro(), Gato()]
for animal in animales:
animal.hacer_sonido()
animal.dormir()
Resultado esperado:
El perro ladra: ¡Guau guau!
El animal está durmiendo.
El gato maúlla: ¡Miau!
El animal está durmiendo.
Este diseño asegura que todas las subclases de Animal
implementen el método hacer_sonido
, garantizando coherencia en la jerarquía.
Polimorfismo y herencia en la jerarquía de clases:
El polimorfismo permite tratar objetos de diferentes clases derivadas como instancias de su clase base, facilitando operaciones sobre colecciones heterogéneas.
Continuando con el ejemplo de figuras geométricas:
class Figura:
def area(self):
pass
class Triangulo(Figura):
def __init__(self, base, altura):
self.base = base
self.altura = altura
def area(self):
return (self.base * self.altura) / 2
class Circulo(Figura):
def __init__(self, radio):
self.radio = radio
def area(self):
return math.pi * self.radio ** 2
Cálculo del área de distintas figuras:
figuras = [Triangulo(10, 5), Circulo(7)]
for figura in figuras:
print(f"El área es: {figura.area():.2f}")
Resultado esperado:
El área es: 25.00
El área es: 153.94
Aquí, utilizamos el polimorfismo para calcular el área de diferentes figuras sin preocuparse por el tipo específico de cada objeto.
Beneficios del uso de herencia en jerarquías de clases:
- Organización lógica: Agrupa clases relacionadas, reflejando relaciones del mundo real.
- Mantenibilidad: Facilita actualizaciones y modificaciones al código.
- Reutilización: Aprovecha código existente, reduciendo redundancias.
- Extensibilidad: Permite ampliar funcionalidades sin afectar clases base.
Consideraciones al diseñar jerarquías de clases:
- Simplicidad frente a complejidad: Evitar jerarquías demasiado profundas o anchas que compliquen el mantenimiento.
- Herencia múltiple con precaución: Puede generar conflictos de nombres y dificultades en la resolución de métodos.
- Uso adecuado de clases abstractas e interfaces: Define contratos claros para las subclases.
Estos ejemplos prácticos demuestran cómo la herencia es una herramienta esencial para el diseño eficiente y estructurado de sistemas en Python, promoviendo buenas prácticas de programación orientada a objetos.
Todas las lecciones de Fundamentos
Accede a todas las lecciones de Fundamentos y aprende con ejemplos prácticos de código y ejercicios de programación con IDE web sin instalar nada.
¿Qué Es La Programación?
Introducción Y Entorno
Lenguajes De Programación
Introducción Y Entorno
Ciclo De Vida Del Desarrollo De Software
Introducción Y Entorno
Herramientas Y Entornos De Desarrollo
Introducción Y Entorno
Instalar Y Configurar Pseint Y Python
Introducción Y Entorno
Estructura De Un Programa Pseint
Introducción Y Entorno
Pensamiento Algorítmico
Lógica
Tipos De Datos Y Variables
Lógica
Operadores
Lógica
Estructuras De Control Condicional
Lógica
Estructuras De Control De Repetición
Lógica
Diagramas De Flujo
Lógica
Depuración De Programas
Lógica
Arrays
Estructuras De Datos
Matrices
Estructuras De Datos
Cadenas De Caracteres
Estructuras De Datos
Algoritmos De Ordenamiento
Ordenamiento Y Búsqueda
Algoritmos De Búsqueda
Ordenamiento Y Búsqueda
Complejidad Temporal Y Espacial
Ordenamiento Y Búsqueda
Definición Y Utilidad De Las Funciones
Funciones
Paso De Parámetros
Funciones
Recursividad
Funciones
Funciones Anónimas
Funciones
Concepto De Clases Y Objetos
Programación Orientada A Objetos
Método Constructor
Programación Orientada A Objetos
Encapsulación
Programación Orientada A Objetos
Herencia
Programación Orientada A Objetos
Polimorfismo
Programación Orientada A Objetos
Composición
Programación Orientada A Objetos
En esta lección
Objetivos de aprendizaje de esta lección
- Comprender el concepto de herencia en programación orientada a objetos.
- Aprender a implementar herencia en Python, Java y C++.
- Conocer la diferencia entre herencia simple y múltiple.
- Dominar la sintaxis para redefinir métodos y usar
super()
. - Desarrollar jerarquías de clases coherentes y evitar ambigüedades.
- Utilizar composición cuando sea más apropiado que la herencia múltiple.
- Aplicar polimorfismo y buenas prácticas de diseño.