Herencia: clases base y derivadas
Introducción
La herencia es uno de los conceptos fundamentales de la programación orientada a objetos que permite crear nuevas clases basadas en clases existentes. Este mecanismo facilita la reutilización de código, promueve la organización jerárquica y permite modelar relaciones del tipo "es un" entre diferentes entidades. En Python, la herencia se implementa de forma sencilla y potente, permitiendo incluso herencia múltiple. A través de este artículo, exploraremos cómo crear y utilizar clases derivadas, aprovechando y extendiendo la funcionalidad de sus clases base.
Concepto básico de herencia
La herencia permite que una clase (llamada subclase o clase derivada) adquiera automáticamente todas las propiedades y comportamientos de otra clase (llamada superclase o clase base). La subclase puede luego:
- Usar los métodos y atributos de la clase base tal como están
- Sobrescribir (redefinir) algunos de ellos
- Añadir nuevos métodos y atributos propios
Podemos visualizar la herencia como una relación "es un": un perro "es un" animal, un coche deportivo "es un" coche.
Sintaxis para crear una clase derivada
La sintaxis en Python para definir una clase que hereda de otra es muy sencilla:
class ClaseBase:
# Atributos y métodos de la clase base
pass
class ClaseDerivada(ClaseBase):
# Atributos y métodos de la clase derivada
pass
La clase entre paréntesis es la clase base de la que se hereda.
Ejemplo práctico: Empleados de una empresa
Veamos un ejemplo práctico donde modelamos diferentes tipos de empleados en una empresa:
class Empleado:
"""Clase base para todos los empleados"""
def __init__(self, nombre, dni, salario_base):
self.nombre = nombre
self.dni = dni
self.salario_base = salario_base
def calcular_salario(self):
return self.salario_base
def mostrar_detalles(self):
return f"Empleado: {self.nombre}, DNI: {self.dni}, Salario: {self.calcular_salario()}€"
class Programador(Empleado):
"""Clase derivada para programadores"""
def __init__(self, nombre, dni, salario_base, lenguaje, horas_extra=0):
# Llamamos al constructor de la clase base
super().__init__(nombre, dni, salario_base)
# Añadimos atributos propios
self.lenguaje = lenguaje
self.horas_extra = horas_extra
def calcular_salario(self):
# Sobrescribimos el método para incluir pago por horas extra
return self.salario_base + (self.horas_extra * 20)
def programar(self):
return f"{self.nombre} está programando en {self.lenguaje}"
class Gerente(Empleado):
"""Clase derivada para gerentes"""
def __init__(self, nombre, dni, salario_base, departamento, bonus=0):
super().__init__(nombre, dni, salario_base)
self.departamento = departamento
self.bonus = bonus
def calcular_salario(self):
return self.salario_base + self.bonus
def gestionar_equipo(self):
return f"{self.nombre} está gestionando el departamento de {self.departamento}"
La función super()
La función super()
es un componente clave en la herencia. Nos permite llamar a métodos de la clase base desde la clase derivada:
super().__init__(nombre, dni, salario_base)
Es especialmente útil para:
- Inicializar la parte de los atributos que maneja la clase base
- Extender la funcionalidad de un método en lugar de reemplazarlo por completo
Uso de las clases
Ahora veamos cómo podemos usar estas clases:
# Creamos instancias de las diferentes clases
empleado_general = Empleado("Laura Pérez", "12345678A", 1500)
programador = Programador("Carlos Gómez", "87654321B", 1800, "Python", 10)
gerente = Gerente("Ana Martínez", "11223344C", 2200, "Desarrollo", 500)
# Accedemos a métodos comunes
print(empleado_general.mostrar_detalles()) # Empleado: Laura Pérez, DNI: 12345678A, Salario: 1500€
print(programador.mostrar_detalles()) # Empleado: Carlos Gómez, DNI: 87654321B, Salario: 2000€
print(gerente.mostrar_detalles()) # Empleado: Ana Martínez, DNI: 11223344C, Salario: 2700€
# Usamos métodos específicos de cada clase derivada
print(programador.programar()) # Carlos Gómez está programando en Python
print(gerente.gestionar_equipo()) # Ana Martínez está gestionando el departamento de Desarrollo
Verificación de herencia
Python proporciona funciones para comprobar las relaciones de herencia:
# isinstance() comprueba si un objeto es instancia de una clase
print(isinstance(programador, Programador)) # True
print(isinstance(programador, Empleado)) # True
print(isinstance(programador, Gerente)) # False
# issubclass() comprueba si una clase es subclase de otra
print(issubclass(Programador, Empleado)) # True
print(issubclass(Gerente, Empleado)) # True
print(issubclass(Programador, Gerente)) # False
Herencia múltiple
Python permite que una clase herede de múltiples clases base, una característica que no todos los lenguajes orientados a objetos ofrecen:
class DispositivoElectronico:
def __init__(self, marca, modelo):
self.marca = marca
self.modelo = modelo
self.encendido = False
def encender(self):
self.encendido = True
return f"{self.marca} {self.modelo} está encendido"
def apagar(self):
self.encendido = False
return f"{self.marca} {self.modelo} está apagado"
class DispositivoConectado:
def __init__(self):
self.conectado = False
def conectar(self):
self.conectado = True
return "Dispositivo conectado a internet"
def desconectar(self):
self.conectado = False
return "Dispositivo desconectado de internet"
class Portatil(DispositivoElectronico, DispositivoConectado):
def __init__(self, marca, modelo, ram):
DispositivoElectronico.__init__(self, marca, modelo)
DispositivoConectado.__init__(self)
self.ram = ram
def describir(self):
estado_conexion = "conectado" if self.conectado else "no conectado"
estado_encendido = "encendido" if self.encendido else "apagado"
return f"Portátil {self.marca} {self.modelo} con {self.ram}GB de RAM, {estado_encendido} y {estado_conexion}"
En este caso, Portatil
hereda de dos clases base. Observa cómo debemos llamar explícitamente a los constructores de ambas clases.
El problema del diamante y el MRO (Method Resolution Order)
La herencia múltiple puede llevar a ambigüedades, especialmente en el "problema del diamante", donde una clase hereda de dos clases que tienen un ancestro común:
A
/ \
B C
\ /
D
Si B
y C
sobrescriben un método de A
, ¿qué implementación debe usar D
? Python resuelve esto mediante el Orden de Resolución de Métodos (MRO), que se puede consultar a través del atributo __mro__
:
print(Portatil.__mro__)
# Muestra el orden en que Python busca los métodos
El MRO utiliza un algoritmo llamado C3 que garantiza un orden predecible y que respeta la herencia simple y múltiple.
Resumen
La herencia en Python es un mecanismo potente que facilita la reutilización de código y la estructuración de clases en relaciones jerárquicas naturales. A través de la sintaxis sencilla para crear clases derivadas y el uso de super()
, podemos extender y modificar el comportamiento de las clases base. Python proporciona además herramientas para verificar relaciones de herencia (isinstance()
, issubclass()
) y soporta herencia múltiple, con un sistema sólido para resolver ambigüedades. Dominar este concepto abrirá las puertas al siguiente nivel de programación orientada a objetos: el polimorfismo, que veremos en el próximo artículo del tutorial.