class Autor:
def __init__(self, nombre: str):
self.__nombre = nombre
self.__libros = []
def escribir_libro(self, titulo: str):
= Libro(titulo, self)
libro self.__libros.append(libro)
return libro
class Libro:
def __init__(self, titulo: str, autor: Autor):
self.__titulo = titulo
self.__autor = autor
Diseño Orientado a Objetos - Ejercicios
Ejercicio. Empleados y proyectos
Un equipo de desarrollo ha diseñado un sistema para gestionar empleados y proyectos en una empresa. Sin embargo, el diagrama UML contiene varios errores que afectan su correcta implementación.
- La relación del diagrama es una herencia y es incorrecta, debería ser una asociación.
- La multiplicidad de la relación entre Empleado y Proyecto debería ser n a n.
- La relación entre Empleado y Proyecto debería ser bidireccional.
- Los atributos deberían ser privados (asumimos que se harán accesibles mediante properties).
- El método
calcular_bonus()
debería estar en la clase Empleado.
Ejercicio. Autor y Libro
¿A partir del código anterior, qué tipo de relación hay entre Autor y Libro?
La opción correcta es C) asociación porque (según el código) un autor escribe muchos libros, y un libro pertenece a un autor.
En la opción A) el autor escribe muchos libros, pero el libro “no sabe” qué autor lo ha escrito. Sin embargo en el código sí existe esa relación.
En la opción B) las multiplicidades están invertidas, un autor escribiría un libro y un libro tendría muchos autores.
Ejercicio. Vehículo y Coche
class Vehiculo:
def __init__(self, marca: str):
self.marca = marca
def arrancar(self):
return "El vehículo está arrancando"
class Coche(Vehiculo):
def __init__(self, marca: str, num_puertas: int):
super().__init__(marca)
self.num_puertas = num_puertas
def arrancar(self):
return "El coche está arrancando"
¿A partir del código anterior, qué tipo de relación hay entre Vehículo y Coche?
La opción correcta es A) herencia porque Coche hereda de Vehículo. B) es una implementación de interfaces, pero la clase Vehículo no es un interfaz (no es puramente abstracta). C) es una asociación dirigida.
Ejercicio. Empresa, empleado y proyecto
Agrega la relación entre Empleado y Proyecto, teniendo en cuenta que:
- Un empleado puede trabajar en varios proyectos.
- Un proyecto puede tener varios empleados asignados.
Agrega la relación entre Empresa y Empleado, asegurando que:
- Cada empresa tiene varios empleados.
- Un empleado trabaja en una única empresa.
Ejercicio. Biblioteca y préstamo de libros
Agrega la relación entre Usuario y Libro, considerando que:
- Un usuario puede tomar prestados varios libros.
- Un libro solo puede estar prestado a un usuario a la vez.
- Cada vez que se presta un libro se registra la fecha de inicio y fin del préstamo.
Agrega la relación entre Biblioteca y Libro, asegurando que:
- Una biblioteca tiene varios libros en su colección.
- Un libro pertenece a una única biblioteca.
- ¿Cómo guardamos la información del préstamo?
Ejercicio. Gestor de modelos (I)
Una empresa tecnológica está desarrollando un sistema de explotación de modelos de inteligencia artificial para ofrecer predicciones a clientes. De momento quieren desarrollar un sistema que gestione los distintos modelos disponibles.
Dibuja un diagrama de clases para este sistema.
Requisitos:
- El sistema gestiona múltiples modelos, cada uno con un nombre, una versión y un tipo de tarea (ejemplo: clasificación, regresión, procesamiento de texto, etc.).
- Un gestor de modelos se encarga de mantener una copia en memoria de los modelos para poder acceder rápidamente, dentro del gestor los modelos se identifican con su nombre y versión. Debe tener los siguientes métodos:
- load: carga un modelo y lo guarda en memoria
- delete: elimina el modelo almacenado en memoria
- list_models: devuelve una lista con los identificadores de los modelos
- get_model: devuelve un modelo
- ¿Cuántas clases hay?
- ¿Cuál modelamos primero?
- ¿Qué atributos tiene y cuáles son sus tipos?
- ¿Qué métodos tiene y cuáles son sus parámetros y valores de retorno?
- ¿Qué tipo de dato usamos para identificar los modelos en el gestor?
- ¿Qué piensas de mantener los modelos en memoria?
- Usaremos **kwargs para tener flexibilidad en los parámetros, y object para devolver cualquier cosa (de momento).
- Si los modelos van a estar distribuidos habría que buscar otra forma de identificarlos y comunicarnos con ellos (recuerda Sistemas Operativos Distribuidos), seguiremos produndizando en esto.
Ejercicio. Gestor de modelos (II)
La empresa quiere ofrecer los modelos a clientes mediante una API para obtener predicciones en tiempo real, manteniendo un histórico de todas las predicciones proporcionadas a cada cliente para que puedan volver a consultarlas.
Requisitos del sistema:
- Los clientes tienen un identificador único, un nombre y una clave API para autenticarse en el sistema.
- Los clientes pueden enviar solicitudes de predicción a un modelo específico, enviando los datos de entrada requeridos por cada modelo.
- Cada solicitud de predicción genera una respuesta que incluye la predicción del modelo, la fecha de ejecución y el tiempo de procesamiento.
- Se debe registrar un historial de predicciones, permitiendo a los clientes consultar sus solicitudes pasadas.
- En la solución anterior, la predicción era un object, ¿qué hacemos para mejorarlo y cumplir con los requisitos?
- La relación entre User y Model y el método get_save_prediction no son realistas, esto lo mejoraremos en el tema de arquitectura.
:::{.callout-tip collapse=“true”“} ## Solución
:::
Ejercicio. Productos, pedidos y facturas (I)
¿Qué problema de diseño tiene este código?
class Producto:
def __init__(self, nombre: str, precio: float):
self.nombre = nombre
self.precio = precio
class Pedido:
def __init__(self):
self.productos = []
def agregar_producto(self, nombre: str, precio: float):
self.productos.append(Producto(nombre, precio))
def calcular_total(self):
return sum(producto.precio for producto in self.productos)
def imprimir_factura(self):
print("Factura:")
for producto in self.productos:
print(f"{producto.nombre} - ${producto.precio}")
print(f"Total: ${self.calcular_total()}")
- Incumple un principio SOLID, ¿cuál?
- Pedido no debe encargarse de la impresión (Single Responsibility Principle)
- Añadimos una clase Factura (ver siguiente ejercicio)
Ejercicio. Productos, pedidos y facturas (II)
Hemos añadido la clase Factura. ¿Qué problemas de diseño tiene este código?
class Producto: # Igual que antes
class Pedido:
def __init__(self):
self.productos = []
# Sin cambios
def agregar_producto(self, nombre: str, precio: float):
def calcular_total(self):
# Eliminamos imprimir_factura
class Factura(Pedido):
def __init__(self, numero: int):
super().__init__()
self.numero = numero
def imprimir(self):
print(f"Factura #{self.numero}")
for producto in self.productos:
print(f"{producto.nombre} - ${producto.precio}")
print(f"Total: ${self.calcular_total()}")
- Factura no necesita heredar de Pedido para poder acceder a sus productos (Factura no es un Pedido).
- Los atributos son públicos, no hay encapsulación. Deberían hacerse properties y devolver copias de los objetos protegidos para evitar efectos colaterales.
class TodoList:
def __init__(self):
self._tasks = []
@property
def tasks(self):
return self._tasks # Devuelve una referencia a la lista, no una copia
def add(self, task):
self._tasks.append(task)
def __str__(self):
return str(self._tasks)
= TodoList()
todolist "Poner el lavavajillas")
todolist.add("Doblar la ropa")
todolist.add(print(todolist)
= todolist.tasks # tasks es el mismo objeto que todolist._tasks
tasks # Se elimina la tarea también en todolist._tasks
tasks.pop() print(tasks)
print(todolist)
['Poner el lavavajillas', 'Doblar la ropa']
['Poner el lavavajillas']
['Poner el lavavajillas']
Ejercicio. Servicios de notificación (I)
Usa el patrón Adapter para que el Banco pueda notificar a los clientes que están en descubierto.
class ServicioWhatsApp:
def enviar_mensaje(self, numero: str, contenido: str):
print(f"WhatsApp: Enviando '{contenido}' a {numero}")
class ServicioEmail:
def enviar_email(self, destinatario: str, asunto: str, cuerpo: str):
print(f"Email: Enviando '{asunto}' a {destinatario}")
class Notificador:
def enviar_notificacion(self, destinatario: str, mensaje: str):
raise NotImplementedError
class Banco:
def notificar_descubiertos(self):
"""Envía un mensaje a los clientes con saldo negativo."""
pass
class AdaptadorWhatsApp(Notificador):
"""Adaptador para integrar WhatsApp en el sistema de notificaciones"""
def __init__(self, servicio_whatsapp: ServicioWhatsApp):
self.servicio_whatsapp = servicio_whatsapp
def enviar_notificacion(self, destinatario: str, mensaje: str):
self.servicio_whatsapp.enviar_mensaje(destinatario, mensaje)
class AdaptadorEmail(Notificador):
"""Adaptador para integrar Email en el sistema de notificaciones"""
def __init__(self, servicio_email: ServicioEmail):
self.servicio_email = servicio_email
def enviar_notificacion(self, destinatario: str, mensaje: str):
self.servicio_email.enviar_email(destinatario, "Notificación Bancaria", mensaje)
Ejercicio. Servicios de notificación (II)
Mejora este diseño usando una Factoría.
class Banco:
def notificar_descubiertos(self, clientes):
"""Envía un mensaje a los clientes con saldo negativo."""
for id, saldo, tipo, contacto in clientes:
if saldo < 0:
= f"Su cuenta tiene un saldo negativo de ${-saldo}. Por favor, regularice su situación."
mensaje
= None
notificador if tipo == "whatsapp":
= AdaptadorWhatsApp(ServicioWhatsApp())
notificador elif tipo == "email":
= AdaptadorEmail(ServicioEmail())
notificador
if notificador is not None:
notificador.enviar_notificacion(contacto, mensaje)
= [
clientes 1, -50.0, "whatsapp", "555123456"),
(2, -200.0, "email", "johndoe@mail.com")
(
]
= Banco()
banco banco.notificar_descubiertos(clientes)
WhatsApp: Enviando 'Su cuenta tiene un saldo negativo de $50.0. Por favor, regularice su situación.' a 555123456
Email: Enviando 'Notificación Bancaria' a johndoe@mail.com
class NotificadorFactory:
@classmethod
def get(cls, tipo: str) -> Notificador:
if tipo == "whatsapp":
return AdaptadorWhatsApp(ServicioWhatsApp())
elif tipo == "email":
return AdaptadorEmail(ServicioEmail())
else:
raise ValueError(f"No existe el servicio de notificación {tipo}")
class Banco:
def notificar_descubiertos(self, clientes):
"""Envía un mensaje a los clientes con saldo negativo."""
for id, saldo, tipo, contacto in clientes:
if saldo < 0:
= f"Su cuenta tiene un saldo negativo de ${-saldo}. Por favor, regularice su situación."
mensaje
= NotificadorFactory.get(tipo)
notificador
notificador.enviar_notificacion(contacto, mensaje)
= [
clientes 1, -50.0, "whatsapp", "555123456"),
(2, -200.0, "email", "johndoe@mail.com")
(
]
= Banco()
banco banco.notificar_descubiertos(clientes)
WhatsApp: Enviando 'Su cuenta tiene un saldo negativo de $50.0. Por favor, regularice su situación.' a 555123456
Email: Enviando 'Notificación Bancaria' a johndoe@mail.com