Introducción
Contenido de la lección.
Objetivos de aprendizaje
- Objetivo 1
- Objetivo 2
- Objetivo 3
Objetivos de Aprendizaje
Al finalizar este tema, serás capaz de:
- Comprender los conceptos de herencia y polimorfismo en Python
- Implementar clases padre e hija con relaciones jerárquicas
- Utilizar la función super() para acceder a métodos de la clase padre
- Aplicar polimorfismo mediante sobrescritura de métodos
- Desarrollar sistemas orientados a objetos escalables y mantenibles
📚 Conceptos Fundamentales de Herencia
La herencia es un mecanismo que permite crear una nueva clase basada en una clase existente. La clase base se denomina clase padre o superclase, mientras que la nueva clase se llama clase hija o subclase. La clase hija hereda todos los atributos y métodos de la clase padre, pudiendo además agregar nuevos atributos y métodos o modificar los existentes. En Python, la herencia se especifica colocando el nombre de la clase padre entre paréntesis después del nombre de la clase hija. Este concepto fundamental permite la reutilización de código y establece relaciones is-a entre objetos, donde la clase hija es una especialización de la clase padre.
class Animal:
def __init__(self, nombre, especie):
self.nombre = nombre
self.especie = especie
def hacer_sonido(self):
return 'El animal hace un sonido'
def dormir(self):
return f'{self.nombre} está durmiendo'
class Perro(Animal):
def __init__(self, nombre, raza):
super().__init__(nombre, 'Canino')
self.raza = raza
def hacer_sonido(self):
return 'Guau guau'
# Crear instancias
mi_perro = Perro('Max', 'Labrador')
print(f'Nombre: {mi_perro.nombre}')
print(f'Especie: {mi_perro.especie}')
print(f'Raza: {mi_perro.raza}')
print(mi_perro.hacer_sonido())
print(mi_perro.dormir())
Nombre: Max Especie: Canino Raza: Labrador Guau guau Max está durmiendo
🎯 La Función super() y su Importancia
La función super() es fundamental para acceder a métodos y atributos de la clase padre desde la clase hija. Permite llamar al constructor de la clase padre y reutilizar su funcionalidad sin duplicar código. super() retorna un objeto proxy que delega llamadas de método a la clase padre, facilitando la extensión de funcionalidad existente. Es especialmente útil en constructores donde necesitamos inicializar tanto los atributos de la clase padre como los específicos de la clase hija. El uso de super() también hace el código más mantenible, ya que cambios en la clase padre se propagan automáticamente a las clases hijas sin necesidad de modificaciones adicionales.
class Vehiculo:
def __init__(self, marca, modelo, año):
self.marca = marca
self.modelo = modelo
self.año = año
self.encendido = False
def encender(self):
self.encendido = True
return f'{self.marca} {self.modelo} encendido'
def info(self):
return f'{self.marca} {self.modelo} ({self.año})'
class Auto(Vehiculo):
def __init__(self, marca, modelo, año, puertas):
super().__init__(marca, modelo, año)
self.puertas = puertas
def encender(self):
resultado = super().encender()
return f'{resultado} - Revisando cinturones de seguridad'
def info(self):
info_base = super().info()
return f'{info_base} - {self.puertas} puertas'
# Uso del ejemplo
mi_auto = Auto('Toyota', 'Corolla', 2023, 4)
print(mi_auto.info())
print(mi_auto.encender())
print(f'Estado: {"Encendido" if mi_auto.encendido else "Apagado"}')
Toyota Corolla (2023) - 4 puertas Toyota Corolla encendido - Revisando cinturones de seguridad Estado: Encendido
💡 Polimorfismo y Métodos Virtuales
El polimorfismo permite que objetos de diferentes clases respondan de manera distinta al mismo método, proporcionando flexibilidad en el diseño de software. En Python, el polimorfismo se logra principalmente através de la sobrescritura de métodos (method overriding), donde las clases hijas redefinen métodos heredados para proporcionar implementaciones específicas. Los métodos virtuales son aquellos diseñados para ser sobrescritos en las clases derivadas. Python no tiene palabras clave específicas como virtual, pero todos los métodos pueden ser sobrescritos. Esta característica permite escribir código genérico que funciona con múltiples tipos de objetos, siguiendo el principio de sustitución de Liskov.
import math
class Forma:
def area(self):
raise NotImplementedError('Subclase debe implementar area()')
def perimetro(self):
raise NotImplementedError('Subclase debe implementar perimetro()')
def describir(self):
return f'Área: {self.area():.2f}, Perímetro: {self.perimetro():.2f}'
class Rectangulo(Forma):
def __init__(self, ancho, alto):
self.ancho = ancho
self.alto = alto
def area(self):
return self.ancho * self.alto
def perimetro(self):
return 2 * (self.ancho + self.alto)
class Circulo(Forma):
def __init__(self, radio):
self.radio = radio
def area(self):
return math.pi * self.radio ** 2
def perimetro(self):
return 2 * math.pi * self.radio
# Demostración de polimorfismo
formas = [Rectangulo(5, 3), Circulo(4), Rectangulo(2, 8)]
for i, forma in enumerate(formas, 1):
print(f'Forma {i}: {forma.__class__.__name__}')
print(f' {forma.describir()}')
Forma 1: Rectangulo Área: 15.00, Perímetro: 16.00 Forma 2: Circulo Área: 50.27, Perímetro: 25.13 Forma 3: Rectangulo Área: 16.00, Perímetro: 20.00
🔧 Herencia Múltiple y Method Resolution Order (MRO)
Python soporta herencia múltiple, permitiendo que una clase herede de múltiples clases padre. Esto proporciona gran flexibilidad pero puede crear ambigüedad sobre qué método se ejecuta cuando hay nombres duplicados. Python resuelve esto usando el Method Resolution Order (MRO), que determina el orden en que se buscan métodos en la jerarquía de clases. El MRO sigue el algoritmo C3 linearization, garantizando un orden consistente y predecible. Es crucial entender el MRO para evitar comportamientos inesperados y diseñar jerarquías de clases efectivas. El método __mro__ y la función help() pueden mostrar el orden de resolución de una clase.
class A:
def metodo(self):
return 'Método de A'
def saludar(self):
return 'Hola desde A'
class B(A):
def metodo(self):
return 'Método de B'
def despedir(self):
return 'Adiós desde B'
class C(A):
def metodo(self):
return 'Método de C'
def presentar(self):
return 'Me presento desde C'
class D(B, C):
def metodo_combinado(self):
return f'Combinando: {super().metodo()}'
# Análisis del MRO
objeto = D()
print('Method Resolution Order:')
for i, clase in enumerate(D.__mro__):
print(f' {i+1}. {clase.__name__}')
print(f'\nMétodo llamado: {objeto.metodo()}')
print(f'Método combinado: {objeto.metodo_combinado()}')
print(f'Heredado de A: {objeto.saludar()}')
print(f'Heredado de B: {objeto.despedir()}')
print(f'Heredado de C: {objeto.presentar()}')
Method Resolution Order: 1. D 2. B 3. C 4. A 5. object Método llamado: Método de B Método combinado: Combinando: Método de B Heredado de A: Hola desde A Heredado de B: Adiós desde B Heredado de C: Me presento desde C
⚡ Casos de Uso Avanzados: Sistema de Empleados
Un ejemplo práctico común es un sistema de gestión de empleados que demuestra herencia, polimorfismo y el uso de super() en un contexto profesional. Este sistema incluye una clase base Empleado con atributos comunes y métodos generales, y clases derivadas como Desarrollador, Gerente y Diseñador, cada una con comportamientos específicos. El polimorfismo permite calcular salarios de forma diferente para cada tipo de empleado, mientras que la herencia evita duplicación de código común. Este patrón es fundamental en sistemas empresariales reales donde diferentes tipos de entidades comparten características base pero tienen comportamientos especializados.
class Empleado:
def __init__(self, nombre, id_empleado, salario_base):
self.nombre = nombre
self.id_empleado = id_empleado
self.salario_base = salario_base
self.horas_trabajadas = 0
def calcular_salario(self):
return self.salario_base
def registrar_horas(self, horas):
self.horas_trabajadas += horas
return f'{self.nombre} trabajó {horas} horas adicionales'
def info_empleado(self):
return f'{self.nombre} (ID: {self.id_empleado}) - Salario: ${self.calcular_salario():,.2f}'
class Desarrollador(Empleado):
def __init__(self, nombre, id_empleado, salario_base, lenguajes):
super().__init__(nombre, id_empleado, salario_base)
self.lenguajes = lenguajes
self.bonus_horas = 25
def calcular_salario(self):
salario_base = super().calcular_salario()
bonus_horas_extra = self.horas_trabajadas * self.bonus_horas
return salario_base + bonus_horas_extra
def info_empleado(self):
info_base = super().info_empleado()
return f'{info_base} - Lenguajes: {", ".join(self.lenguajes)}'
class Gerente(Empleado):
def __init__(self, nombre, id_empleado, salario_base, equipo_size):
super().__init__(nombre, id_empleado, salario_base)
self.equipo_size = equipo_size
self.bonus_gerencial = 0.15
def calcular_salario(self):
salario_base = super().calcular_salario()
bonus = salario_base * self.bonus_gerencial * (self.equipo_size / 10)
return salario_base + bonus
def info_empleado(self):
info_base = super().info_empleado()
return f'{info_base} - Equipo: {self.equipo_size} personas'
# Demostración del sistema
empleados = [
Desarrollador('Ana García', 'DEV001', 50000, ['Python', 'Java', 'JavaScript']),
Gerente('Carlos López', 'MNG001', 70000, 8),
Desarrollador('María Rodríguez', 'DEV002', 48000, ['C++', 'Python'])
]
# Registrar horas extra
empleados[0].registrar_horas(10)
empleados[2].registrar_horas(15)
print('=== INFORMACIÓN DE EMPLEADOS ===')
for empleado in empleados:
print(empleado.info_empleado())
print(f'\nNómina total: ${sum(emp.calcular_salario() for emp in empleados):,.2f}')
=== INFORMACIÓN DE EMPLEADOS === Ana García (ID: DEV001) - Salario: $50,250.00 - Lenguajes: Python, Java, JavaScript Carlos López (ID: MNG001) - Salario: $78,400.00 - Equipo: 8 personas María Rodríguez (ID: DEV002) - Salario: $48,375.00 - Lenguajes: C++, Python Nómina total: $177,025.00
Ejercicios Prácticos
Sistema de Vehículos con Herencia
BÁSICODescripción:
Crear una jerarquía de clases con Vehiculo como clase base, y Auto, Motocicleta y Camion como clases derivadas. Cada vehículo debe tener métodos para acelerar, frenar y mostrar información. Implementar polimorfismo en el método de consumo de combustible.
Jerarquía de Figuras Geométricas 3D
INTERMEDIODescripción:
Extender el ejemplo de formas geométricas para incluir figuras 3D como Cubo, Esfera y Cilindro. Implementar métodos para calcular volumen y área superficial. Crear un método que compare objetos por volumen.
Sistema de Inventario con Herencia Múltiple
AVANZADODescripción:
Diseñar un sistema de inventario donde los productos puedan ser Vendible, Almacenable y Rastreable. Usar herencia múltiple para crear clases como ProductoElectronico que combine estas características. Implementar métodos para gestión de stock y ventas.
Framework de Juego con Polimorfismo
AVANZADODescripción:
Crear un mini-framework de juego con una clase base Personaje y clases derivadas Guerrero, Mago y Arquero. Implementar sistema de combate polimórfico donde cada personaje tenga diferentes habilidades y formas de atacar y defenderse.
Resumen y Próximos Pasos
La herencia y el polimorfismo son conceptos fundamentales que permiten crear arquitecturas de software robustas y escalables. La herencia facilita la reutilización de código y establece relaciones lógicas entre clases, mientras que el polimorfismo proporciona flexibilidad permitiendo que diferentes objetos respondan de manera específica a la misma interfaz. El dominio de super() es crucial para extender funcionalidad de manera eficiente, y la comprensión del MRO es esencial para manejar herencia múltiple correctamente. Estos conceptos preparan el terreno para patrones de diseño más avanzados y arquitecturas de software complejas que se estudiarán en módulos posteriores.