Unidad III · Tema 3.0 Disponible

Del mundo real a las clases

Cómo abordar un problema, abstraerlo y convertirlo en clases: un método de seis pasos aplicado a un caso completo.

Objetivo de aprendizaje

Al concluir este tema, el estudiante será capaz de:

  1. Explicar por qué un modelo de software conserva solo lo relevante para un propósito (abstracción) y justificar qué se conserva y qué se descarta.
  2. Traducir el enunciado de un problema en candidatos a clases, atributos y comportamientos, y decidir con criterios explícitos cuál es cuál.
  3. Asignar responsabilidades a las clases mediante fichas CRC y el principio del experto en información.
  4. Distinguir una relación de herencia («es un») de una de composición o asociación («tiene un», «usa»).
  5. Construir de manera incremental una solución en Python y probarla con escenarios derivados del enunciado.

Ruta de trabajo del tema

Etapa Contenido Producto del estudiante
1 Planteamiento inicial Hipótesis escrita sobre cómo empezar
2 Modelar es decidir qué conservar Modelos distintos para propósitos distintos
3 Las piezas de la programación orientada a objetos Vocabulario para traducir el problema
4 El método de seis pasos Receta de trabajo
5 Aplicación guiada: la biblioteca de la facultad Un análisis completo, paso a paso
6 Del diseño al código Programa construido por versiones y probado
7 Un segundo caso para practicar Análisis propio del estacionamiento
8 Verificación y reformulación Cuestionario y respuesta revisada

1. Planteamiento inicial

Quien comienza a programar suele recibir problemas ya descompuestos: «escriba una función que sume dos números». Los problemas reales llegan de otra manera: como un texto, escrito por una persona que conoce su oficio y no conoce la programación. Considere el siguiente enunciado.

Enunciado: la biblioteca de la facultad. La biblioteca desea controlar el préstamo de sus materiales.

R1. Cada material tiene un título y un código de inventario; puede ser un libro (que además tiene un autor) o una revista (que además tiene un número).

R2. Los socios, que pueden ser estudiantes o docentes, se identifican con una matrícula o un número de empleado y tienen un nombre.

R3. Un socio puede tener a la vez, como máximo, tres materiales prestados.

R4. Un material que está prestado no puede prestarse de nuevo hasta que se devuelva.

R5. Cada préstamo registra el material, el socio y el día de entrega; el plazo es de siete días.

R6. Si el material se devuelve después del plazo, se cobra una multa de 5 pesos por cada día de retraso.

R7. El personal quiere consultar qué materiales están disponibles y qué tiene prestado cada socio.

Este laboratorio interactivo requiere JavaScript habilitado en el navegador.

Esta hipótesis se retomará al final del tema.


2. Modelar es decidir qué conservar

Definición — Modelo: representación simplificada de una parte de la realidad, construida con un propósito. Abstracción es el proceso de seleccionar los aspectos relevantes para ese propósito y omitir el resto.

Una persona real tiene miles de rasgos: estatura, historial médico, gustos, calificaciones, amistades. Ningún programa los representa todos, y ninguno debería hacerlo. Cada rasgo conservado debe almacenarse, validarse, protegerse y mantenerse; cada rasgo innecesario es costo sin beneficio.

El propósito determina el modelo. La misma persona es, para la biblioteca, un socio con matrícula y préstamos; para el servicio médico, un paciente con alergias y tipo de sangre; para el control escolar, un estudiante con materias y promedio. Ninguno de los tres modelos es el «correcto»: cada uno es adecuado para su problema.

Realice la siguiente práctica. Elija un propósito, marque los datos que conservaría y compare con la propuesta. Después cambie el propósito y observe qué cambia.

Este laboratorio interactivo requiere JavaScript habilitado en el navegador.

Nota: si dos personas diseñan el mismo sistema y conservan datos distintos, no necesariamente alguna se equivoca; es probable que hayan entendido el propósito de manera distinta. Por eso el primer paso del método es delimitar el problema.


3. Las piezas de la programación orientada a objetos

En el paradigma orientado a objetos, un programa es un conjunto de objetos que colaboran enviándose mensajes (es decir, invocando métodos). El vocabulario básico es el siguiente.

Pieza Definición breve Pregunta que ayuda a identificarla
Objeto Entidad con identidad, estado (datos) y comportamiento (operaciones) ¿Hay algo concreto que se distinga de otros, aunque se parezcan?
Clase Plantilla que describe a un conjunto de objetos con los mismos atributos y operaciones ¿Existen varios elementos de la misma categoría con el mismo tipo de datos y reglas?
Atributo Dato que describe el estado de un objeto ¿Es un valor que describe a otra cosa y no hace nada por sí mismo?
Método Operación que un objeto sabe realizar o consultar ¿Es una acción o una consulta que se hace sobre un objeto?
Relación Vínculo entre clases: herencia, composición o asociación ¿«Es un tipo de», «tiene» o «usa» a otra?
Invariante Condición que debe cumplirse siempre sobre el estado de un objeto ¿Qué estado nunca debe llegar a existir?

La traducción del enunciado a estas piezas sigue una correspondencia general:

En el enunciado aparece… En el modelo suele corresponder a…
Un sustantivo que designa cosas con identidad y reglas propias («préstamo», «socio») Una clase
Un sustantivo que solo describe a otro («título», «matrícula») Un atributo
Un verbo o una consulta («prestar», «devolver», «calcular la multa») Un método
«Es un tipo de», «puede ser un… o un…» Herencia
«Tiene», «se compone de», «usa» Composición o asociación
Una regla («como máximo tres», «no puede prestarse de nuevo») Una validación dentro de un método y, en su conjunto, un invariante

Importante: esta correspondencia es una guía para generar candidatos, no un algoritmo. Cada candidato debe pasar por una decisión razonada: no todo sustantivo es una clase ni todo verbo es un método.


4. El método de seis pasos

Los diseñadores de software no dependen de la inspiración; siguen una secuencia de preguntas. La siguiente es una versión adecuada para el nivel de este curso.

Paso Qué se hace Pregunta guía Producto
1. Delimitar Establecer el propósito, numerar los requisitos y decidir qué queda fuera ¿Qué debe poder hacer el sistema y qué no? Lista numerada de requisitos
2. Identificar candidatos Subrayar sustantivos y verbos del enunciado ¿Qué cosas y qué acciones se mencionan? Tabla de candidatos
3. Decidir su papel Clasificar cada candidato como clase, atributo, cálculo o fuera del modelo ¿Tiene identidad y reglas propias, o solo describe a otra cosa? Clases con sus atributos
4. Asignar responsabilidades Repartir los métodos entre las clases con fichas CRC ¿Quién conoce los datos necesarios para hacerlo? Fichas CRC
5. Relacionar y proteger Establecer herencia, composición y asociaciones; enumerar invariantes ¿«Es un» o «tiene un»? ¿Qué estado nunca debe existir? Diagrama de clases e invariantes
6. Construir y probar Programar por versiones y verificar con escenarios del enunciado ¿Cada requisito se comprueba con un caso concreto? Código y escenarios probados

Nota: el proceso es iterativo. Al programar se descubre que una clase sobraba o que una responsabilidad estaba mal ubicada, y se vuelve a un paso anterior. Eso no es un fracaso: es la manera normal de trabajar.


5. Aplicación guiada: la biblioteca de la facultad

Paso 1. Delimitar el problema

Propósito: controlar qué materiales están prestados, a quién y con qué consecuencias si se devuelven tarde.

Requisitos: R1 a R7 del enunciado de la sección 1. Para evitar una falsa precisión, se supone que las fechas se representan con números de día (día 1, día 2, …) y no con calendarios reales.

Queda fuera del alcance: búsqueda por tema o autor, reservaciones, cobro real de multas, renovaciones, datos de contacto del personal. Declarar lo que queda fuera es tan importante como declarar lo que entra: protege contra el crecimiento sin límite del modelo.

Paso 2. Identificar candidatos

Se subrayan los sustantivos y los verbos del enunciado.

Candidato Tipo de palabra Aparece en
biblioteca, material, libro, revista, socio, préstamo, personal Sustantivo (cosas) R1 a R7
título, código de inventario, autor, número, matrícula, nombre, día de entrega Sustantivo (datos) R1, R2, R5
multa, plazo, días de retraso, límite de tres Sustantivo (valores o reglas) R3, R5, R6
prestar, devolver, cobrar multa, consultar disponibles, consultar préstamos Verbo o consulta R3 a R7

Paso 3. Decidir el papel de cada candidato

Para cada sustantivo se aplican tres preguntas, en este orden:

  1. ¿Tiene identidad propia y reglas o comportamiento? Si es así, es candidato a clase.
  2. ¿Es un valor que describe a otra cosa? Entonces es un atributo de esa otra cosa.
  3. ¿Puede obtenerse a partir de otros datos? Entonces es un cálculo (método o propiedad), y guardarlo como dato corre el riesgo de que quede desactualizado.

Si ninguna de las tres aplica, el candidato probablemente queda fuera del modelo. Clasifique los candidatos del enunciado y lea la razón de cada decisión.

Este laboratorio interactivo requiere JavaScript habilitado en el navegador.

Resultado del análisis:

Clase Atributos Decisiones notables
Material codigo, titulo, disponible Agrupa lo común a libros y revistas
Libro (especializa Material) autor Solo agrega un dato
Revista (especializa Material) numero Solo agrega un dato
Socio matricula, nombre, prestamos, LIMITE Estudiante y docente no son subclases: solo difieren en un dato
Prestamo material, socio, vence, PLAZO, MULTA_DIA La multa no se guarda: se calcula
Biblioteca activos (y la colección) Coordina las operaciones entre las demás clases

Error común: crear una clase por cada sustantivo. «Pesos» y «personal» no son clases en este problema, y «estudiante» frente a «docente» solo justificaría subclases si se comportaran de manera distinta.

Paso 4. Asignar responsabilidades

Cada verbo del enunciado debe terminar en un método de alguna clase. La pregunta es cuál. Un criterio sólido, conocido como principio del experto en información, dice: la responsabilidad se asigna a la clase que posee los datos necesarios para cumplirla. Así los objetos no necesitan pedir datos a otros para hacer su trabajo, y los cambios quedan localizados.

Asigne las responsabilidades siguientes.

Este laboratorio interactivo requiere JavaScript habilitado en el navegador.

Una ficha CRC (Clase, Responsabilidades, Colaboradores) resume cada clase en tres campos: su nombre, lo que sabe y hace, y las clases con las que trabaja para lograrlo. Complete las fichas de las cuatro clases principales con sus propias palabras y compare con la propuesta.

Este laboratorio interactivo requiere JavaScript habilitado en el navegador.

Paso 5. Establecer las relaciones y proteger los invariantes

Para decidir entre herencia y composición se aplica una prueba sencilla: «¿A es un B?» debe ser verdadero en todos los casos del problema para que haya herencia. Si lo que se afirma es «A tiene un B» o «A usa un B», corresponde composición o asociación.

Este laboratorio interactivo requiere JavaScript habilitado en el navegador.

El diagrama de clases resultante es el siguiente (la flecha con triángulo indica herencia; el rombo, que una clase contiene a otras; la flecha simple, que una clase se relaciona con otra).

classDiagram
  class Material {
    +codigo
    +titulo
    +disponible
    +prestar()
    +devolver()
  }
  class Libro {
    +autor
  }
  class Revista {
    +numero
  }
  class Socio {
    +matricula
    +nombre
    +prestamos
    +puede_pedir()
  }
  class Prestamo {
    +material
    +socio
    +vence
    +multa(dia)
  }
  class Biblioteca {
    +activos
    +prestar(material, socio, dia)
    +devolver(prestamo, dia)
  }
  Material <|-- Libro
  Material <|-- Revista
  Biblioteca o-- Socio
  Biblioteca o-- Material
  Prestamo --> Material
  Prestamo --> Socio
  Socio --> Prestamo

Los invariantes son las condiciones que el diseño debe garantizar siempre. Se obtienen de las reglas del enunciado:

Invariante Origen Dónde se protege
Un material prestado no se presta de nuevo R4 Biblioteca.prestar (consulta Material.disponible)
Un socio nunca tiene más de tres préstamos vigentes R3 Socio.puede_pedir, consultado por Biblioteca.prestar
La multa nunca es negativa R6 Prestamo.multa

Definición — Invariante: condición sobre el estado de un objeto que debe ser verdadera antes y después de cada operación pública. Diseñar buscando proteger los invariantes es la razón de ser del encapsulamiento, que se estudia en el tema 3.1.


6. Del diseño al código

El código se construye por versiones, cada una verificable. Así, cuando algo falla, el error se encuentra en el último paso agregado.

Versión 0. El punto de partida sin clases

Antes de usar clases conviene ver qué ocurre sin ellas. Ejecute el programa y observe el estado del diccionario después de la última modificación.

Este laboratorio interactivo requiere JavaScript habilitado en el navegador.

Los datos y las operaciones viven en lugares distintos y nada obliga a usarlos juntos. Esa separación es la que las clases resuelven.

Versión 1. Las clases con sus atributos

Se transcribe el resultado del paso 3. El código está documentado con comentarios; después de él, una lectura guiada explica cada fragmento.

# Clase Material: lo que tienen en común libros y revistas.
class Material:
    def __init__(self, codigo, titulo):    # constructor: se ejecuta al crear un objeto
        self.codigo = codigo               # atributo: identifica al material
        self.titulo = titulo               # atributo: describe al material
        self.disponible = True             # estado inicial: nadie lo tiene prestado


# Clase Socio: persona que puede pedir materiales prestados.
class Socio:
    def __init__(self, matricula, nombre):
        self.matricula = matricula
        self.nombre = nombre
        self.prestamos = []                # lista (inicialmente vacía) de sus préstamos


m = Material(101, "Fisica I")              # se crea un objeto de la clase Material
s = Socio(2024001, "Ana")                  # se crea un objeto de la clase Socio
print(m.titulo, s.nombre)

Este laboratorio interactivo requiere JavaScript habilitado en el navegador.

Compruebe el resultado en la corrida y observe que cada llamada a la clase crea un objeto con su propio estado.

Este laboratorio interactivo requiere JavaScript habilitado en el navegador.

Versión 2. Las responsabilidades como métodos

Se incorporan las reglas del paso 4 en la clase que posee los datos. La regla R4 («un material prestado no puede prestarse de nuevo») se convierte en una validación dentro de Material.

class Material:
    def __init__(self, codigo, titulo):
        self.codigo = codigo
        self.titulo = titulo
        self.disponible = True

    def prestar(self):
        # R4: un material prestado no puede prestarse de nuevo
        if not self.disponible:
            raise ValueError("El material ya esta prestado")   # se rechaza la operación
        self.disponible = False

    def devolver(self):
        self.disponible = True


m = Material(101, "Fisica I")
m.prestar()
print(m.disponible)    # False
m.devolver()
print(m.disponible)    # True

Este laboratorio interactivo requiere JavaScript habilitado en el navegador.

Este laboratorio interactivo requiere JavaScript habilitado en el navegador.

Versión 3. Las clases colaboran: el escenario completo

Con las cinco clases definidas, el programa reproduce el escenario del enunciado: un socio pide un material el día 1 y lo devuelve el día 12.

class Material:                                    # --- Material ---------------------------
    def __init__(self, codigo, titulo):
        self.codigo = codigo
        self.titulo = titulo
        self.disponible = True


class Socio:                                       # --- Socio ------------------------------
    LIMITE = 3                                     # atributo de CLASE: la regla R3

    def __init__(self, matricula, nombre):
        self.matricula = matricula
        self.nombre = nombre
        self.prestamos = []

    def puede_pedir(self):
        return len(self.prestamos) < Socio.LIMITE  # R3: como máximo tres a la vez


class Prestamo:                                    # --- Prestamo ---------------------------
    PLAZO = 7                                      # R5
    MULTA_DIA = 5                                  # R6

    def __init__(self, material, socio, dia):
        self.material = material
        self.socio = socio
        self.vence = dia + Prestamo.PLAZO          # día límite de devolución

    def multa(self, dia):                          # la multa se CALCULA, no se guarda
        retraso = dia - self.vence
        if retraso > 0:
            return retraso * Prestamo.MULTA_DIA
        return 0


class Biblioteca:                                  # --- Biblioteca (coordinadora) ----------
    def __init__(self):
        self.activos = []

    def prestar(self, material, socio, dia):
        if not material.disponible:                # R4
            raise ValueError("Material no disponible")
        if not socio.puede_pedir():                # R3
            raise ValueError("El socio llego al limite")
        p = Prestamo(material, socio, dia)         # se crea el préstamo
        material.disponible = False                # se actualizan los estados
        socio.prestamos.append(p)
        self.activos.append(p)
        return p

    def devolver(self, prestamo, dia):
        prestamo.material.disponible = True
        prestamo.socio.prestamos.remove(prestamo)
        self.activos.remove(prestamo)
        return prestamo.multa(dia)                 # R6


b = Biblioteca()                                   # --- Escenario --------------------------
m = Material(101, "Fisica I")
s = Socio(2024001, "Ana")
p = b.prestar(m, s, 1)                             # día 1
print("multa:", b.devolver(p, 12))                 # día 12: 4 días de retraso
print(m.disponible, len(s.prestamos))

Este laboratorio interactivo requiere JavaScript habilitado en el navegador.

Antes de ejecutar el programa, anticipe su salida. Active «Predecir la salida» y también «Predecir el método que se ejecuta», que pide identificar cuál implementación se ejecutará en cada llamada a un método.

Este laboratorio interactivo requiere JavaScript habilitado en el navegador.

Probar con escenarios

Un diseño no se valida por cómo luce, sino por lo que produce en casos concretos derivados de los requisitos. Cada requisito debería tener al menos un escenario.

Escenario Qué se hace Resultado esperado Requisito
E1 Prestar un material disponible Queda no disponible; el socio tiene 1 préstamo R4, R5
E2 Prestar el mismo material otra vez Error «Material no disponible» R4
E3 Devolver el día 8 (el último del plazo) Multa de 0 pesos R5, R6
E4 Devolver el día 12 Multa de 20 pesos R6
E5 Pedir un cuarto material con tres vigentes Error «El socio llego al limite» R3

Utilice la pestaña «Mi programa» de la corrida para escribir sus propios escenarios con las clases anteriores y compruebe cada uno.


7. Un segundo caso para practicar: el estacionamiento

El método se aprende aplicándolo. Resuelva este problema completo, sin ayuda del ejemplo, y compare después su análisis con el de una persona compañera.

Enunciado: el estacionamiento de la facultad. El estacionamiento tiene 40 lugares. Cada vehículo se identifica por su placa y es un automóvil o una motocicleta. Una motocicleta ocupa un lugar y paga 10 pesos por hora; un automóvil ocupa un lugar y paga 20 pesos por hora. Cuando un vehículo entra, se le asigna el primer lugar libre y se registra la hora de entrada; si no hay lugares libres, se rechaza. Al salir, se calcula el importe por las horas transcurridas (cada fracción de hora se cobra completa) y se libera el lugar. Se desea saber en cualquier momento cuántos lugares libres hay.

Aplique los seis pasos y registre su tabla de candidatos con la decisión que tomó para cada uno.

Este laboratorio interactivo requiere JavaScript habilitado en el navegador.

Nota: el caso tiene una decisión deliberadamente discutible: ¿la tarifa distinta justifica dos subclases de Vehiculo? La respuesta depende de si habrá otro comportamiento distinto además de la tarifa. Lo importante es argumentar la decisión con los criterios del paso 3.


8. Errores de diseño frecuentes

Error común — una clase por sustantivo: crear clases para valores simples («Pesos», «Titulo») o para actores que el sistema no modela. Resultado: muchas clases sin comportamiento.

Error común — la clase todopoderosa: concentrar todo en una clase «Sistema» o «Manager» que conoce y modifica los datos de las demás. Los demás objetos quedan convertidos en simples contenedores de datos, como el diccionario de la versión 0.

Error común — herencia por conveniencia: hacer que Biblioteca herede de Lista solo para reutilizar append. La herencia afirma una relación «es un»; para reutilizar servicios se usa composición.

Error común — guardar lo que se puede calcular: almacenar la multa como atributo. Si cambia el día o el plazo, el dato queda inconsistente.

Error común — confundir clase y objeto: la clase Material describe; cada material concreto (el libro «Fisica I») es un objeto. Los atributos de los objetos pertenecen a cada uno.

Error común — diseñar sin escenarios: un diseño que «se ve bien» pero nunca se ejecutó con los casos del enunciado suele esconder reglas olvidadas.


9. Verificación y reformulación

Este laboratorio interactivo requiere JavaScript habilitado en el navegador.

Este laboratorio interactivo requiere JavaScript habilitado en el navegador.


10. Libreta de trabajo

Reúna sus respuestas, sus predicciones y los resultados de las prácticas; revíselos y expórtelos como evidencia para el portafolio.

Este laboratorio interactivo requiere JavaScript habilitado en el navegador.


11. Preguntas de reflexión

  1. Elija un objeto o un proceso de su vida cotidiana (una cafetería, una agenda, un torneo deportivo). Indique tres modelos distintos de él, cada uno con un propósito diferente, y qué conserva cada uno.
  2. En el análisis de la biblioteca, estudiante y docente se modelaron como un atributo y no como subclases. Describa una regla nueva del problema que justificaría convertirlos en subclases.
  3. ¿Qué ocurre con el diseño si cambia la regla R3 (el límite pasa de tres a cinco)? ¿En cuántos lugares del código hay que cambiarlo y por qué conviene que sea uno solo?
  4. ¿Qué diferencia hay entre «la biblioteca tiene materiales» y «el libro es un material»? Dé un ejemplo propio de cada relación.
  5. Describa un caso en el que, al programar, descubrió (o descubriría) que una responsabilidad estaba mal asignada. ¿Qué indicios lo revelan?

12. Lecturas recomendadas

  • Larman, C. (2004). Applying UML and patterns: An introduction to object-oriented analysis and design and iterative development (3rd ed.). Prentice Hall. (Identificación de clases conceptuales y asignación de responsabilidades; principio del experto en información.)
  • Beck, K., & Cunningham, W. (1989). A laboratory for teaching object-oriented thinking. En OOPSLA '89 Conference Proceedings. ACM. (Origen de las fichas CRC.)
  • Booch, G., Maksimchuk, R., Engle, M., Young, B., Conallen, J., & Houston, K. (2007). Object-oriented analysis and design with applications (3rd ed.). Addison-Wesley.
  • Wirfs-Brock, R., & McKean, A. (2003). Object design: Roles, responsibilities, and collaborations. Addison-Wesley.
  • Sebesta, R. (2015). Concepts of programming languages (11th ed.), capítulo sobre soporte para la programación orientada a objetos. Pearson.
  • Python Software Foundation. The Python Tutorial, sección «Classes». Acceso libre en docs.python.org.