IF0100 - Programación OO II

Unidad 1: Programación Orientada a Objetos

Clase 5: Herencia y Polimorfismo

Martes, 24 de febrero de 2026 Semana 4 - Martes (120 minutos) 60 min Teoría 60 min Práctica

E1: Examen POO - Jueves 26/02/2026 (2 días)

Objetivos de Aprendizaje

Al finalizar esta clase, serás capaz de:

  • Crear jerarquías de clases usando herencia
  • Usar super() para llamar métodos de la clase padre
  • Sobreescribir métodos para especializar comportamiento
  • Comprender el polimorfismo (mismo método, distinto comportamiento)
  • Diseñar Usuario, Admin y Cliente para TaskFlow
Video Recomendado

Herencia y polimorfismo explicados:

Ver "Herencia y polimorfismo en Python" 12:15 min

Herencia (20 min)

La herencia permite crear nuevas clases basadas en clases existentes. La clase hija hereda atributos y métodos del padre.

Analogía: Un Vehículo es la clase base. Un Coche y una Moto heredan de Vehículo (tienen ruedas, motor) pero cada uno tiene características específicas.

Sintaxis

# Clase Padre (Base)
class Animal:
    def __init__(self, nombre, edad):
        self.nombre = nombre
        self.edad = edad
    
    def hacer_sonido(self):
        return "Sonido genérico"
    
    def presentarse(self):
        return f"Soy {self.nombre}, tengo {self.edad} años"

# Clase Hija (hereda de Animal)
class Perro(Animal):
    def __init__(self, nombre, edad, raza):
        # Llamar al constructor del padre
        super().__init__(nombre, edad)
        self.raza = raza
    
    def hacer_sonido(self):
        return "¡Guau!"
    
    def buscar_pelota(self):
        return f"{self.nombre} está buscando la pelota"

# Otra clase hija
class Gato(Animal):
    def hacer_sonido(self):
        return "¡Miau!"

# Uso
mi_perro = Perro("Fido", 3, "Labrador")
mi_gato = Gato("Michi", 2)

print(mi_perro.presentarse())    # Heredado de Animal
print(mi_perro.hacer_sonido())   # "¡Guau!" (especializado)
print(mi_gato.hacer_sonido())    # "¡Miau!" (especializado)

Jerarquía en TaskFlow

class Usuario:
    """Clase base para todos los usuarios."""
    
    def __init__(self, username, email):
        self.username = username
        self.email = email
        self.activo = True
    
    def obtener_rol(self):
        return "Usuario"
    
    def puede_crear_proyectos(self):
        return True

class Administrador(Usuario):
    """Admin hereda de Usuario."""
    
    def obtener_rol(self):
        return "Administrador"
    
    def puede_eliminar_usuarios(self):
        return True
    
    def puede_crear_proyectos(self):
        return True  # También puede crear proyectos

class Cliente(Usuario):
    """Cliente hereda de Usuario."""
    
    def __init__(self, username, email, empresa):
        super().__init__(username, email)
        self.empresa = empresa
    
    def obtener_rol(self):
        return "Cliente"
    
    def puede_crear_proyectos(self):
        return False  # Los clientes solo ven proyectos

# Uso
admin = Administrador("admin", "admin@empresa.com")
cliente = Cliente("cliente1", "cli@empresa.com", "ACME Corp")

print(f"{admin.username} es {admin.obtener_rol()}")
print(f"{cliente.username} es {cliente.obtener_rol()} de {cliente.empresa}")
print(f"Admin puede eliminar usuarios: {admin.puede_eliminar_usuarios()}")
print(f"Cliente puede crear proyectos: {cliente.puede_crear_proyectos()}")

super() (10 min)

super() permite llamar métodos de la clase padre. Es útil en el constructor y para extender funcionalidad.

class Persona:
    def __init__(self, nombre, edad):
        print(f"Inicializando Persona: {nombre}")
        self.nombre = nombre
        self.edad = edad
    
    def presentarse(self):
        return f"Hola, soy {self.nombre}"

class Empleado(Persona):
    def __init__(self, nombre, edad, salario):
        # Llamar al __init__ del padre
        super().__init__(nombre, edad)
        print(f"Inicializando Empleado con salario: {salario}")
        self.salario = salario
    
    def presentarse(self):
        # Extender el método del padre
        base = super().presentarse()
        return f"{base} y gano ${self.salario}"

# Uso
emp = Empleado("Juan", 30, 50000)
print(emp.presentarse())
# Salida:
# Inicializando Persona: Juan
# Inicializando Empleado con salario: 50000
# Hola, soy Juan y gano $50000
Importante: Siempre usa super().__init__() en el constructor de la clase hija para asegurar que todos los atributos del padre se inicialicen.

Sobreescritura de Métodos (10 min)

La sobreescritura (override) es redefinir un método del padre en la clase hija para cambiar su comportamiento.

class UsuarioBase:
    def __init__(self, username):
        self.username = username
    
    def obtener_permisos(self):
        return ["ver_tareas"]
    
    def __str__(self):
        return f"Usuario({self.username})"

class UsuarioPro(UsuarioBase):
    def obtener_permisos(self):
        # Extender: agregar permisos del padre + nuevos
        permisos_base = super().obtener_permisos()
        return permisos_base + ["crear_tareas", "editar_tareas"]
    
    def __str__(self):
        # Sobreescribir completamente
        return f"UsuarioPro: @{self.username}"

class UsuarioEnterprise(UsuarioBase):
    def obtener_permisos(self):
        return ["ver_tareas", "crear_tareas", "editar_tareas", 
                "eliminar_tareas", "gestionar_usuarios"]

# Uso
usuario = UsuarioBase("basico")
pro = UsuarioPro("profesional")
enterprise = UsuarioEnterprise("empresa")

for u in [usuario, pro, enterprise]:
    print(f"{u}: {u.obtener_permisos()}")
    
# Salida:
# Usuario(basico): ['ver_tareas']
# UsuarioPro: @profesional: ['ver_tareas', 'crear_tareas', 'editar_tareas']
# Usuario(empresa): ['ver_tareas', 'crear_tareas', ...]

Polimorfismo (10 min)

El polimorfismo significa "múltiples formas". El mismo método se comporta diferente según el objeto que lo ejecuta.

class Notificacion:
    def enviar(self, mensaje):
        raise NotImplementedError("Subclase debe implementar esto")

class NotificacionEmail(Notificacion):
    def __init__(self, email):
        self.email = email
    
    def enviar(self, mensaje):
        return f"📧 Enviando email a {self.email}: {mensaje}"

class NotificacionSMS(Notificacion):
    def __init__(self, telefono):
        self.telefono = telefono
    
    def enviar(self, mensaje):
        return f"📱 Enviando SMS a {self.telefono}: {mensaje}"

class NotificacionPush(Notificacion):
    def __init__(self, device_id):
        self.device_id = device_id
    
    def enviar(self, mensaje):
        return f"🔔 Enviando push a dispositivo {self.device_id}: {mensaje}"

# Función polimórfica
# Funciona con cualquier tipo de notificación
def notificar_usuario(notificacion, mensaje):
    print(notificacion.enviar(mensaje))

# Uso
email = NotificacionEmail("usuario@email.com")
sms = NotificacionSMS("+123456789")
push = NotificacionPush("device123")

notificar_usuario(email, "Tarea completada!")
notificar_usuario(sms, "Recordatorio: reunión en 5 min")
notificar_usuario(push, "Nuevo mensaje recibido")

# También podemos iterar sobre diferentes tipos
notificaciones = [email, sms, push]
for n in notificaciones:
    print(n.enviar("Mensaje de prueba"))
Ventaja: El código que usa los objetos no necesita saber de qué tipo específico son. Solo necesita saber que tienen el método enviar().

Ejercicio: Jerarquía de Usuarios (20 min)

Crea una jerarquía completa para TaskFlow.

# jerarquia_usuarios.py

class Usuario:
    """Clase base para todos los usuarios."""
    
    def __init__(self, username, email):
        self._username = username
        self._email = email
        self._activo = True
    
    @property
    def username(self):
        return self._username
    
    @property
    def email(self):
        return self._email
    
    @email.setter
    def email(self, valor):
        if "@" not in valor:
            raise ValueError("Email inválido")
        self._email = valor
    
    def obtener_rol(self):
        return "Usuario"
    
    def obtener_permisos(self):
        return ["ver_tareas", "comentar"]
    
    def puede_eliminar_proyectos(self):
        return False
    
    def __str__(self):
        return f"[{self.obtener_rol()}] @{self._username}"


class MiembroEquipo(Usuario):
    """Usuario que puede crear y editar tareas."""
    
    def __init__(self, username, email, departamento):
        super().__init__(username, email)
        self.departamento = departamento
    
    def obtener_rol(self):
        return f"Miembro ({self.departamento})"
    
    def obtener_permisos(self):
        base = super().obtener_permisos()
        return base + ["crear_tareas", "editar_tareas", "asignar_tareas"]


class Administrador(Usuario):
    """Admin con todos los permisos."""
    
    def obtener_rol(self):
        return "Administrador"
    
    def obtener_permisos(self):
        return ["ver_tareas", "comentar", "crear_tareas", "editar_tareas",
                "eliminar_tareas", "gestionar_usuarios", "ver_reportes"]
    
    def puede_eliminar_proyectos(self):
        return True


class Cliente(Usuario):
    """Cliente externo, solo puede ver."""
    
    def __init__(self, username, email, empresa):
        super().__init__(username, email)
        self.empresa = empresa
    
    def obtener_rol(self):
        return f"Cliente - {self.empresa}"
    
    def obtener_permisos(self):
        # Clientes solo ven, no comentan ni crean
        return ["ver_tareas"]


# === PRUEBAS ===
if __name__ == "__main__":
    # Crear diferentes tipos de usuarios
    usuarios = [
        Usuario("visitante", "visit@email.com"),
        MiembroEquipo("desarrollador", "dev@email.com", "Tecnología"),
        Administrador("admin", "admin@email.com"),
        Cliente("cliente1", "cli1@empresa.com", "ACME Corp")
    ]
    
    print("=== Jerarquía de Usuarios TaskFlow ===\n")
    
    # Polimorfismo: mismo método, distinto comportamiento
    for u in usuarios:
        print(f"{u}")
        print(f"   Permisos: {', '.join(u.obtener_permisos())}")
        print(f"   Puede eliminar proyectos: {u.puede_eliminar_proyectos()}")
        print()
    
    # Función polimórfica
    def mostrar_info_usuario(usuario):
        """Funciona con cualquier tipo de Usuario."""
        print(f"Usuario: {usuario}")
        print(f"Rol: {usuario.obtener_rol()}")
        print(f"Total permisos: {len(usuario.obtener_permisos())}")
    
    print("=== Info Detallada ===")
    for u in usuarios:
        mostrar_info_usuario(u)
        print()
Conexión con TaskFlow: Esta jerarquía permite controlar qué puede hacer cada tipo de usuario en el sistema. Los permisos se verifican antes de permitir acciones.

Retos Adicionales

  1. Agrega clase Gerente que herede de MiembroEquipo y pueda aprobar tareas
  2. Crea método tiene_permiso(permiso) que verifique si un usuario tiene un permiso específico
  3. Agrega atributo proyectos_asignados a MiembroEquipo

Resumen

  • Herencia: Clase Hija(Padre) - hereda atributos y métodos
  • super(): Llama métodos del padre, especialmente __init__
  • Sobreescritura: Redefinir método del padre en la hija
  • Polimorfismo: Mismo método, distinto comportamiento según la clase
  • Jerarquía TaskFlow: Usuario → MiembroEquipo/Administrador/Cliente
Para el examen E1: Practica crear jerarquías con super() y sobreescritura. Diseña las clases antes de escribir código.
← Anterior: Encapsulamiento
Clase 5 de 25
Siguiente: Clases Abstractas →