Diseño de software

Ingeniería del Software para Inteligencia Artificial

Tabla de contenidos

Diseño de Software

¿Qué es el Diseño de Software?

  • El diseño de software es el proceso de definir la arquitectura, componentes, interfaces y comportamiento de un sistema antes de su implementación.
  • Su objetivo es garantizar que el software sea modular, mantenible y escalable.

Niveles de Diseño

  1. Diseño Arquitectónico: Define la estructura general del sistema y sus módulos principales.
  2. Diseño de Alto Nivel: Especifica la organización de los subsistemas y sus interacciones.
  3. Diseño de Bajo Nivel: Detalla la implementación interna de clases, métodos y estructuras de datos.

Importancia en sistemas de IA

  • Permite separar el modelo de IA de otros componentes (interfaz, almacenamiento).
  • Facilita la reutilización de modelos y procesamiento de datos en distintos entornos.
  • Mejora la escalabilidad en infraestructuras de Machine Learning y Deep Learning.

🔹 Un buen diseño evita problemas de acoplamiento excesivo, baja cohesión y dificultades de mantenimiento.

Acoplamiento

Se refiere al grado de dependencia entre módulos o clases de un sistema.

  • Bajo acoplamiento → Los módulos tienen poca dependencia entre sí, facilitando mantenimiento y reutilización.
  • Alto acoplamiento → Los módulos están fuertemente interconectados, dificultando modificaciones sin afectar otras partes del sistema.

Cohesión

Indica cómo de relacionadas están las responsabilidades dentro de un mismo módulo.

  • Alta cohesión → Un módulo realiza una única tarea bien definida.
  • Baja cohesión → Un módulo contiene múltiples responsabilidades no relacionadas, dificultando el mantenimiento.

Ejemplo

  • Un ModeloIA debe tener alta cohesión (solo maneja entrenamiento y predicción).
  • Un ProcesadorDatos no debe estar fuertemente acoplado a ModeloIA, permitiendo cambiar el modelo sin afectar el preprocesamiento.

🔹 Diseñar software con bajo acoplamiento y alta cohesión mejora la modularidad, escalabilidad y mantenibilidad.

class ModeloIA:
    def __init__(self):
        self.datos = []
        self.modelo = None

    def cargar_datos(self, ruta):
        # Mezcla lógica de lectura de datos con procesamiento
        with open(ruta, "r") as f:
            self.datos = [line.strip().split(",") for line in f]
        self.limpiar_datos()

    def limpiar_datos(self):
        # Limpieza de datos dentro del mismo módulo
        self.datos = [fila for fila in self.datos if len(fila) == 3]

    def entrenar_modelo(self):
        # Lógica de entrenamiento dentro del mismo módulo
        self.modelo = sum(len(fila) for fila in self.datos)

    def predecir(self, entrada):
        return self.modelo * len(entrada) if self.modelo else None
class ModeloIA:
    def __init__(self):
        self.modelo = None

    def entrenar(self, datos):
        # Simulación de entrenamiento
        self.modelo = sum(len(fila) for fila in datos)

    def predecir(self, entrada):
        return self.modelo * len(entrada) if self.modelo else None

# Acoplamiento fuerte: depende directamente de ModeloIA
class ProcesadorDatos:
    def __init__(self):
        self.datos = []
        self.modelo = ModeloIA()

    def cargar_datos(self, ruta):
        with open(ruta, "r") as f:
            self.datos = [line.strip().split(",") for line in f]
        self.limpiar_datos()
        self.modelo.entrenar(self.datos)  # Acoplamiento fuerte: entrenar directamente aquí

    def limpiar_datos(self):
        self.datos = [fila for fila in self.datos if len(fila) == 3]

    def predecir(self, entrada):
        return self.modelo.predecir(entrada)  # Llama directamente al modelo
class ProcesadorDatos:
    def init(self):
        self.datos = []
    
    def cargar_datos(self, ruta):
        with open(ruta, "r") as f:
            self.datos = [line.strip().split(",") for line in f]
        self.limpiar_datos()

    def limpiar_datos(self):
        self.datos = [fila for fila in self.datos if len(fila) == 3]

    def obtener_datos(self):
        return self.datos

class ModeloIA:
    def init(self):
        self.modelo = None
    
    def entrenar(self, datos):
        self.modelo = sum(len(fila) for fila in datos)

    def predecir(self, entrada):
        return self.modelo * len(entrada) if self.modelo else None

procesador = ProcesadorDatos()
procesador.cargar_datos("datos.csv")
datos = procesador.obtener_datos()
modelo = ModeloIA()
modelo.entrenar(datos)
prediccion = modelo.predecir([1, 2, 3])
print(prediccion)

RECUERDA

Reutilizar código NO es ❌ copiar y pegar

El código reutilizable debe diseñarse para que funcione bien en distintos contextos sin modificaciones.

Por ejemplo, al realizar pruebas automáticas estamos cambiando el contexto:

  • Llamando a funciones de forma aislada
  • Pasando datos falsos
  • Simulando el comportamiento de librerías externas

Principios de Diseño de Software

  • Son directrices que ayudan a crear software mantenible, reutilizable y escalable.
  • Aplicables en el diseño de sistemas orientados a objetos y arquitectura de software.
  • Algunos principios fundamentales…

DRY (Don’t Repeat Yourself)

Evitar duplicación de código

KISS (Keep It Simple, Stupid)

Diseñar soluciones simples y comprensibles

YAGNI (You Ain’t Gonna Need It)

No implementar funcionalidades innecesarias

Separation of Concerns

Dividir el software en módulos con responsabilidades claras

Para poder diseñar y comprender sistemas complejos necesitamos visualizarlos usando herramientas que nos permitan ver solamente la información más relevante.

Lenguajes visuales (diagramas)

UML

  • UML (Unified Modeling Language) es un lenguaje de modelado visual estándar para representar el diseño de sistemas de software.
  • Facilita la comunicación entre desarrolladores, analistas y otros interesados en el proyecto.
  • Permite modelar estructuras, comportamientos e interacciones dentro de un sistema.
  • Es independiente del lenguaje de programación y se usa en múltiples dominios.

Diagramas de clases

Diagramas de Clases en UML

  • Un diagrama de clases es una representación visual de la estructura de un sistema orientado a objetos.
  • Describe las clases, sus atributos y métodos, y las relaciones entre ellas.

Clases en UML

Las clases se representan en UML con un rectángulo dividido en tres secciones:

  1. Nombre de la clase (Parte superior)
  2. Atributos (Parte media)
  3. Métodos (Parte inferior)

Modificadores de visibilidad

Símbolo Tipo Descripción
+ Público Accesible desde cualquier clase
- Privado Solo accesible dentro de la propia clase
# Protegido Accesible dentro de la clase y sus subclases
~ Paquete Accesible dentro del mismo paquete o módulo

Modificadores de visibilidad

Recuerda que en el código los atributos privados usan el prefijo __ y los protegidos _

Atributos y métodos estáticos

Se indican en el diagrama con un subrayado.

class Config:
    DEFAULT_BATCH_SIZE: int = 32  # Constante estática

    @classmethod
    def get_configuracion(cls) -> dict:
        return {"batch_size": cls.DEFAULT_BATCH_SIZE, "learning_rate": 0.001}

Asociaciones

Asociaciones

Las asociaciones implican una propiedad que hace referencia al objeto asociado.

class DataSet:
    def __init__(self):
        self.__items = []


class DataItem:
    def __init__(self, dataset: DataSet):
        if dataset is None:
            raise ValueError("El item debe pertenecer a un DataSet")
        self.__dataset = dataset

Asociaciones dirigidas

Las asociaciones dirigidas sólo se pueden recorrer en un sentido.

Asociaciones dirigidas

La propiedad sólo aparece en el objeto del que sale la flecha.

class DataSet:
    def __init__(self):
        self.__items = []


class DataItem:
    def __init__(self):
        # DataItem no sabe a qué DataSet pertenece

Multiplicidad

Notación Significado
0..1 Cero o una instancia (opcional)
1..1 Exactamente una instancia
0..* Cero o más instancias (muchos)
1..* Al menos una instancia
m..n Entre m y n instancias

Multiplicidad

Notación Significado
0..1 Cero o una instancia (opcional)
1 Exactamente una instancia
* Cero o más instancias (muchos)
1..* Al menos una instancia
m..n Entre m y n instancias

Multiplicidad

Cuando se omite la multiplicidad se asume el valor 1..1

Dependencias

  • Representan una relación débil donde una clase usa o depende temporalmente de otra.
  • Se indica con una línea discontinua con flecha (..>).

Dependencias

class ProcesadorImagen:
    def analizar(self, imagen: Imagen):
        api_vision = APIVision()
        api_vision.detectar_objetos(imagen)

Estereotipos

  • Los estereotipos en UML son etiquetas especiales que se utilizan para categorizar elementos dentro de un diagrama.
  • Se representan con el formato <<estereotipo>> sobre el nombre de una clase, paquete o componente.
  • También se usan en las relaciones para indicar el tipo de conexión entre clases o componentes.
  • Ayudan a diferenciar roles o tipos de elementos en el diseño del software.

Diagramas de paquetes

Diagramas de paquetes

  • Un diagrama de paquetes es una representación visual de la organización de módulos o componentes en un sistema.
  • Se utiliza para agrupar clases y definir dependencias entre diferentes partes del sistema.
  • Facilita la modularidad, escalabilidad y mantenibilidad del software.

Implementación de paquetes en Python

Los paquetes UML se implementan como paquetes Python (carpetas con fichero __init__.py)

Cada clase va en su propio módulo (modulo.py)

Implementación de paquetes en Python

Los paquetes UML se implementan como paquetes Python (carpetas con fichero __init__.py)

Cada clase va en su propio módulo (modulo.py)

Esto es una convención establecida en la asignatura.

Implementación de paquetes en Python

Existen alternativas, puedes investigar qué dice el estándar PEP8 al respecto

Implementación de paquetes en Python

Los paquetes UML se implementan como paquetes Python (carpetas con fichero __init__.py)

Cada clase va en su propio módulo (modulo.py)

📂 /
└── 📂 ml/
    │── 📄 __init__.py 
    └── 📄 procesador_datos.py (contiene ProcesadorDatos)

Más información en los materiales de prácticas (Programación Modular)

En esta clase hemos aprendido…

  • Por qué es importante diseñar bien el código antes de escribirlo.
  • Algunos principios fundamentales de diseño a tener en cuenta.
  • Cómo interpretar diagramas UML de clases y paquetes.