Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

Programación Orientada a Objetos

Encapsulamiento y Relaciones entre Objetos

Protegiendo el Estado y Modelando Colaboraciones

Universidad Nacional de Rio Negro - Sede Andina

En el capítulo anterior (Fundamentos de la Programación Orientada a Objetos) nos enfocamos en el análisis conceptual de un dominio: utilizamos La Heurística Lingüística para identificar objetos a partir de sustantivos, definir su comportamiento a partir de verbos, y aplicar los El Filtro de Abstracción y El Filtro de Complejidad: ¿Valor simple o Clase nueva? para refinar el modelo.

En este capítulo profundizamos en dos conceptos fundamentales de OOP:

  1. Encapsulamiento: El principio de proteger el estado interno de los objetos

  2. Relaciones: Cómo los objetos colaboran entre sí (Asociación, Composición, Agregación)


Encapsulamiento: Protegiendo el Estado

¿Qué es el Encapsulamiento?

El encapsulamiento es uno de los cuatro pilares fundamentales de la Programación Orientada a Objetos (junto con herencia, polimorfismo y abstracción). Se trata del principio de ocultar los detalles internos de un objeto y exponer únicamente lo necesario para que otros objetos interactúen con él.

Analogía: El Control Remoto

Considerá un control remoto de televisión:

No necesitás conocer cómo funcionan los circuitos internos para usar el control. Los botones son la interfaz pública que te permite interactuar con la funcionalidad sin acceder directamente al estado interno.

Del mismo modo, un objeto bien encapsulado:

Fusión de Datos y Comportamiento

El primer aspecto del encapsulamiento es la fusión de datos y comportamiento en una misma unidad. En el paradigma estructurado, datos y funciones están separados; en POO, están unificados en el objeto.

Comparación visual:

Comparación entre el paradigma estructurado (datos y funciones separados) y el paradigma orientado a objetos (unificados en una sola unidad).

Figure 1:Comparación entre el paradigma estructurado (datos y funciones separados) y el paradigma orientado a objetos (unificados en una sola unidad).

Esta fusión tiene consecuencias profundas:

  1. Los datos conocen sus operaciones: No hay funciones “sueltas” que operen sobre datos ajenos

  2. Cohesión alta: Todo lo relacionado con una responsabilidad está en un solo lugar

  3. Menor acoplamiento: No hay dependencias globales a funciones externas

Ocultamiento de Información

El segundo aspecto del encapsulamiento es igualmente fundamental: ocultar los detalles de implementación.

Analogía Extendida: El Televisor

Cuando usás un televisor, interactuás con él a través de una interfaz simple: botones de encendido, volumen, canales. No necesitás saber:

El televisor oculta toda esa complejidad y te expone solo lo que necesitás para usarlo. Si el fabricante cambia la tecnología interna (de LCD a OLED, por ejemplo), tu forma de usar el televisor no cambia.

El encapsulamiento en un televisor: la interfaz pública oculta la complejidad de la implementación interna.

Figure 2:El encapsulamiento en un televisor: la interfaz pública oculta la complejidad de la implementación interna.

¿Por Qué Encapsular?

El encapsulamiento no es una restricción arbitraria; tiene beneficios concretos y medibles en el desarrollo de software.

1. Control de Invariantes

Una invariante es una condición que debe mantenerse siempre verdadera durante toda la vida del objeto.

Ejemplo: Cuenta Bancaria

Clase: CuentaBancaria
Invariante: El saldo nunca debe ser negativo

Si permitimos acceso directo al atributo saldo, cualquier parte del código podría violarlo:

// Mal diseño (sin encapsulamiento)
cuenta.saldo = -1000;  // ❌ Viola la invariante

Con encapsulamiento, el objeto controla cómo se modifica el saldo:

// Buen diseño (con encapsulamiento)
cuenta.retirar(1500);  // ✓ El método verifica que haya fondos

El método retirar() puede validar:

2. Facilita el Mantenimiento

Cuando el estado es privado, podés cambiar la representación interna sin afectar al código que usa la clase.

Ejemplo: Representación de Fecha

Podríamos almacenar una fecha de dos formas diferentes:

Versión 1:

Atributos: dia, mes, anio

Versión 2:

Atributo: timestampUnix (long)

Si exponemos métodos públicos (obtenerAnio(), obtenerMes(), etc.), podemos cambiar de Versión 1 a Versión 2 sin que nadie que use la clase lo note.

3. Reduce el Acoplamiento

El acoplamiento mide cuánto depende una clase de los detalles internos de otra.

El encapsulamiento reduce el acoplamiento porque las clases solo dependen de interfaces públicas, no de detalles internos.

4. Seguridad y Consistencia

Al controlar el acceso al estado, garantizamos que:

Ejemplo Completo: Termostato

Para consolidar los conceptos de encapsulamiento, analicemos un ejemplo más completo.

Escenario: Un termostato inteligente que controla la temperatura de una habitación.

Invariantes del termostato:

  1. La temperatura objetivo debe estar entre 15°C y 30°C

  2. El modo solo puede ser “calefacción”, “refrigeración” o “apagado”

  3. Si el termostato está apagado, no puede estar calentando ni enfriando

Sin encapsulamiento (mal diseño):

Termostato {
    temperaturaObjetivo: 25      // Cualquiera podría poner -100
    modo: "calefacción"          // Cualquiera podría poner "hola"
    estaActivo: true             // Inconsistente con modo "apagado"
}

// Código externo puede hacer:
termostato.temperaturaObjetivo = -50;  // ❌ Viola invariante 1
termostato.modo = "explosión";         // ❌ Viola invariante 2
termostato.modo = "apagado";
termostato.estaActivo = true;          // ❌ Viola invariante 3

Con encapsulamiento (buen diseño):

Beneficios observados:

AspectoSin encapsulamientoCon encapsulamiento
ValidaciónNo hayCada método valida
ConsistenciaPuede romperseSiempre garantizada
Cambios futurosAfectan a todosLocalizados en la clase
ResponsabilidadDel usuarioDel objeto

Relaciones entre Objetos

En sistemas orientados a objetos, raramente los objetos existen de forma aislada. Los objetos colaboran para resolver problemas complejos, y estas colaboraciones se modelan mediante relaciones.

Notación UML y Cardinalidad

El Lenguaje de Modelado Unificado (UML, Unified Modeling Language) proporciona una notación gráfica estándar para representar relaciones entre clases.

Representación de Asociaciones

En UML, una asociación se representa como una línea que conecta dos clases. La línea puede tener:

Ejemplo básico:

Ejemplo de asociación básica entre una Persona y su Dirección.

Figure 3:Ejemplo de asociación básica entre una Persona y su Dirección.

Esto se lee: “Una Persona vive en una Dirección”.

Cardinalidad (Multiplicidad)

La cardinalidad especifica cuántas instancias de una clase pueden estar relacionadas con instancias de otra clase. Se anota en los extremos de la línea de asociación.

Table 1:Notaciones de cardinalidad

NotaciónSignificadoEjemplo
1Exactamente unoUna persona tiene exactamente un DNI
0..1Cero o uno (opcional)Un empleado tiene 0 o 1 cónyuge registrado
* o 0..*Cero o másUna empresa tiene cero o más empleados
1..*Uno o másUn auto tiene uno o más neumáticos
n..mEntre n y mUn curso tiene entre 5 y 40 estudiantes

Ejemplo con cardinalidad:

Asociación con cardinalidad: una empresa emplea a cero o más empleados.

Figure 4:Asociación con cardinalidad: una empresa emplea a cero o más empleados.

Se lee: “Una Empresa (1) emplea a cero o más (*) Empleados”.

Cómo Leer la Cardinalidad

La cardinalidad se lee desde el otro extremo de la relación:

Cómo interpretar las cardinalidades en una asociación entre dos clases A y B.

Figure 5:Cómo interpretar las cardinalidades en una asociación entre dos clases A y B.

Ejemplo práctico:

Relación muchos a muchos: un autor escribe uno o más libros, y un libro puede tener varios autores.

Figure 6:Relación muchos a muchos: un autor escribe uno o más libros, y un libro puede tener varios autores.


Tipos de Relaciones

Existen tres tipos principales de relaciones entre objetos, cada una con semántica diferente:

  1. Asociación: Relación general, conexión débil

  2. Composición: Relación fuerte, el todo “contiene” las partes

  3. Agregación: Relación débil, el contenedor agrupa elementos

Asociación

La asociación es la relación más general entre dos clases. Indica que existe alguna conexión o conocimiento entre ellas, pero no implica propiedad ni ciclo de vida compartido.

Características de la Asociación

Ejemplos de asociación:

Notación UML

Línea simple sin decoración especial.

Ejemplo:

Asociación entre Persona y Pasaporte: una persona puede tener o no un pasaporte.

Figure 7:Asociación entre Persona y Pasaporte: una persona puede tener o no un pasaporte.

Una persona puede tener 0 o 1 pasaporte. El pasaporte puede existir antes de ser asignado a una persona.

Direccionalidad de la Asociación

Las asociaciones pueden ser:

Unidireccional: Solo una clase conoce a la otra.

Asociación unidireccional: el Pedido conoce sus Productos, pero no viceversa.

Figure 8:Asociación unidireccional: el Pedido conoce sus Productos, pero no viceversa.

El pedido conoce qué productos contiene, pero el producto no sabe en qué pedidos está.

Bidireccional: Ambas clases se conocen mutuamente.

Asociación bidireccional: tanto el Estudiante como el Curso se conocen mutuamente.

Figure 9:Asociación bidireccional: tanto el Estudiante como el Curso se conocen mutuamente.

El estudiante sabe en qué cursos está inscripto, y el curso sabe qué estudiantes tiene.


Composición

La composición es una relación fuerte de tipo “tiene-un” donde:

Analogía: El Auto y su Motor

Un auto está compuesto por un motor, ruedas, carrocería, etc.

Representación UML

La composición se representa con un diamante relleno (♦) en el extremo del contenedor:

Composición en un automóvil: las partes (motor, carrocería, ruedas) pertenecen exclusivamente al auto.

Figure 10:Composición en un automóvil: las partes (motor, carrocería, ruedas) pertenecen exclusivamente al auto.

El diamante negro indica propiedad fuerte.

Características de la Composición

  1. Ciclo de vida dependiente: Cuando se destruye el todo, se destruyen las partes

  2. Creación por el contenedor: El todo típicamente crea las partes en su constructor

  3. Cardinalidad del todo: Una parte pertenece a exactamente un todo

  4. Semántica fuerte: “No puede existir sin”

Ejemplos de composición:

Ejemplo Detallado: Factura y Líneas de Detalle

Una factura está compuesta por líneas de detalle:

¿Por qué es composición?

  1. Las líneas de detalle no existen sin la factura — no tiene sentido una línea suelta

  2. La factura crea las líneas cuando se agregan productos

  3. Si se elimina la factura, las líneas desaparecen con ella

  4. Una línea pertenece a exactamente una factura

Ciclo de vida:

Ciclo de vida en una relación de composición: las partes mueren con el todo.

Figure 11:Ciclo de vida en una relación de composición: las partes mueren con el todo.


Agregación

La agregación es una relación débil de tipo “tiene-un” donde:

Analogía: Departamento y Profesores

Un departamento universitario tiene profesores, pero:

Representación UML

La agregación se representa con un diamante vacío (◊) en el extremo del contenedor:

Agregación: el Departamento agrupa Profesores que existen independientemente.

Figure 12:Agregación: el Departamento agrupa Profesores que existen independientemente.

El diamante blanco indica una relación de pertenencia más laxa.

Características de la Agregación

  1. Ciclo de vida independiente: Las partes pueden existir antes y después del contenedor

  2. Recepción de referencias: El contenedor recibe objetos ya creados (típicamente en constructor o setters)

  3. Cardinalidad flexible: Una parte puede pertenecer a múltiples contenedores

  4. Semántica débil: “Agrupa a” o “Contiene temporalmente”

Ejemplos de agregación:

Ejemplo Detallado: Equipo de Fútbol

Un equipo de fútbol agrupa jugadores:

¿Por qué es agregación?

  1. Los jugadores existían antes de unirse al equipo (nacieron, crecieron, jugaron en otros equipos)

  2. Los jugadores pueden cambiar de equipo (pases, transferencias)

  3. Si el equipo se disuelve, los jugadores siguen existiendo (pueden retirarse o ir a otro equipo)

  4. Un jugador puede incluso pertenecer a múltiples contextos (equipo + selección nacional)

Ciclo de vida:

Ciclo de vida en una relación de agregación: las partes sobreviven al contenedor.

Figure 13:Ciclo de vida en una relación de agregación: las partes sobreviven al contenedor.


Comparación: Composición vs Agregación

Table 2:Diferencias clave entre Composición y Agregación

AspectoComposición (♦)Agregación (◊)
Ciclo de vidaAcoplado (se crea y destruye junto)Independiente (preexiste y sobrevive)
CreaciónEl todo crea las partesLas partes se pasan al todo
DestrucciónSe destruyen con el todoSobreviven al todo
ExclusividadUna parte pertenece a un solo todoUna parte puede pertenecer a varios contenedores
Semántica“No existe sin”“Pertenece temporalmente a”
FuerzaRelación fuerteRelación débil

¿Cuándo Usar Cada Una?

Usá Composición cuando:

Usá Agregación cuando:


El Patrón Todo-Parte

Tanto la composición como la agregación son instancias del patrón estructural Todo-Parte, que modela la relación entre un objeto complejo (todo) y sus componentes (partes).

Beneficios del Patrón

  1. Modularidad: El todo delega responsabilidades a las partes

  2. Reusabilidad: Las partes pueden reutilizarse en diferentes contextos (especialmente en agregación)

  3. Jerarquía conceptual: Modela naturalmente estructuras jerárquicas

Ejemplo Mixto: Sistema de Biblioteca

Un sistema de biblioteca puede combinar composición y agregación:

Interpretación:

Caso de Estudio: Sistema Universitario

Analicemos un sistema más complejo para practicar la identificación de relaciones.

Requerimiento:

“Una universidad tiene facultades. Cada facultad ofrece carreras. Los estudiantes se inscriben en una carrera y cursan materias. Los profesores dictan materias y pertenecen a un departamento dentro de una facultad.”

Paso 1: Identificar clases principales

Paso 2: Analizar relaciones

RelaciónTipoJustificación
Universidad – FacultadLas facultades no existen sin la universidad
Facultad – DepartamentoLos departamentos son parte de la facultad
Facultad – CarreraLas carreras pertenecen a una facultad específica
Carrera – MateriaLas materias son parte del plan de estudios
Estudiante – CarreraAsocUn estudiante puede cambiarse de carrera
Estudiante – MateriaAsocSe inscribe/cursa/aprueba materias
Profesor – DepartamentoEl profesor existe independientemente
Profesor – MateriaAsocEl profesor “dicta” materias (no las posee)

Paso 3: Diagrama resultante

Jerarquía de clases en el sistema universitario, mostrando las relaciones de composición y agregación.

Figure 14:Jerarquía de clases en el sistema universitario, mostrando las relaciones de composición y agregación.

Paso 4: Cardinalidades

Cardinalidades detalladas para cada una de las relaciones del sistema universitario.

Figure 15:Cardinalidades detalladas para cada una de las relaciones del sistema universitario.

Lecturas:


Referencias Cruzadas y Asociaciones Bidireccionales

En algunos casos, dos objetos necesitan conocerse mutuamente. Esto se llama asociación bidireccional.

¿Cuándo Son Necesarias?

Ejemplo: Pedido y Cliente

Esto crea una navegación en ambas direcciones:

Asociación bidireccional entre Cliente y Pedido.

Figure 16:Asociación bidireccional entre Cliente y Pedido.

Problemas de las Asociaciones Bidireccionales

  1. Complejidad: Hay que mantener la consistencia en ambas direcciones

  2. Acoplamiento: Las dos clases dependen mutuamente

  3. Riesgo de inconsistencia: Si actualizás una dirección y no la otra

Cuándo Son Aceptables

Las asociaciones bidireccionales son aceptables cuando:

Ejemplo: Implementación Correcta

Si decidís que necesitás bidireccionalidad entre Cliente y Pedido, tenés que mantener la consistencia:

Clase Cliente {
    - pedidos: Lista<Pedido>
    
    + agregarPedido(pedido) {
        pedidos.agregar(pedido)
        pedido.establecerCliente(this)  // Mantiene consistencia
    }
    
    + removerPedido(pedido) {
        pedidos.remover(pedido)
        pedido.establecerCliente(null)  // Mantiene consistencia
    }
}

Clase Pedido {
    - cliente: Cliente
    
    // Método interno, no público
    ~ establecerCliente(nuevoCliente) {
        this.cliente = nuevoCliente
    }
}

Patrón recomendado: Un solo lado de la relación (el “dueño”) maneja las modificaciones. El otro lado tiene métodos de acceso restringido.

Alternativa: Evitar la Bidireccionalidad

En muchos casos, podés evitar la bidireccionalidad usando un repositorio o servicio:

Clase RepositorioPedidos {
    + buscarPorCliente(cliente): Lista<Pedido> {
        // Busca en la base de datos o colección
        return pedidos.filtrar(p -> p.cliente == cliente)
    }
}

Así, Cliente no necesita conocer sus pedidos directamente; los consultás a través del repositorio cuando los necesitás.


Resumen

Encapsulamiento

Asociación

Composición (♦)

Agregación (◊)

Cardinalidad


Próximos Pasos

En este capítulo viste los conceptos fundamentales de encapsulamiento y relaciones entre objetos. Para implementar estos conceptos en Java, consultá:


Ejercicios

Identificación de Relaciones

Solution to Exercise 1

Respuesta: Agregación (◊)

Justificación:

  1. Los productos preexisten al pedido — están en el catálogo antes de que alguien los pida

  2. Si se cancela el pedido, los productos siguen existiendo en el catálogo

  3. Un producto puede estar en múltiples pedidos simultáneamente

  4. El pedido no “crea” los productos, solo los referencia

Diagrama:

Relación de agregación entre Pedido y Producto en el contexto de un catálogo.

Figure 17:Relación de agregación entre Pedido y Producto en el contexto de un catálogo.

Nota: El pedido contiene líneas de pedido (cantidad + producto), pero la relación con el producto en sí es de agregación. Las líneas de pedido serían composición del pedido.

Solution to Exercise 2

a) ComputadoraProcesador: Composición (♦)

  • El procesador fue diseñado/elegido para esa computadora específica

  • Si destruís la computadora, el procesador pierde su contexto de uso

  • Generalmente un procesador no se “reutiliza” en otra computadora

Sin embargo, esto es debatible: si considerás que los procesadores pueden venderse como repuestos, sería agregación. Depende del contexto del sistema.

b) PlaylistCancion: Agregación (◊)

  • Las canciones preexisten a la playlist

  • Una canción puede estar en múltiples playlists

  • Si elimino la playlist, las canciones siguen existiendo

c) DocumentoParrafo: Composición (♦)

  • Los párrafos se crean dentro del documento

  • Un párrafo no tiene sentido fuera de su documento

  • Si elimino el documento, los párrafos desaparecen

d) ProyectoDesarrollador: Agregación (◊)

  • Los desarrolladores existen antes de unirse al proyecto

  • Un desarrollador puede trabajar en múltiples proyectos

  • Si termina el proyecto, los desarrolladores siguen existiendo

Cardinalidad y UML

Solution to Exercise 3

Diagrama:

Asociación con cardinalidad específica entre Estudiante y Curso.

Figure 18:Asociación con cardinalidad específica entre Estudiante y Curso.

Lectura:

  • Un estudiante está inscripto en 0 o más cursos (*)

  • Un curso tiene entre 5 y 40 estudiantes (5..40)

Alternativa con clase de asociación (si necesitamos guardar información de la inscripción):

Clase de asociación ‘Inscripción’ para capturar datos adicionales de la relación entre Estudiante y Curso.

Figure 19:Clase de asociación ‘Inscripción’ para capturar datos adicionales de la relación entre Estudiante y Curso.

Solution to Exercise 4

Diseño de la clase:

Clase Rectangulo {
    // Atributos privados
    - ancho: double
    - alto: double
    
    // Constructor que valida
    + Rectangulo(ancho, alto) {
        validarDimension(ancho, "ancho")
        validarDimension(alto, "alto")
        this.ancho = ancho
        this.alto = alto
    }
    
    // Métodos de consulta (getters válidos en este caso)
    + obtenerAncho(): double { return ancho }
    + obtenerAlto(): double { return alto }
    
    // Métodos de modificación con validación
    + cambiarAncho(nuevoAncho) {
        validarDimension(nuevoAncho, "ancho")
        this.ancho = nuevoAncho
    }
    
    + cambiarAlto(nuevoAlto) {
        validarDimension(nuevoAlto, "alto")
        this.alto = nuevoAlto
    }
    
    // Métodos de comportamiento
    + calcularArea(): double {
        return ancho * alto
    }
    
    + calcularPerimetro(): double {
        return 2 * (ancho + alto)
    }
    
    // Método privado de validación
    - validarDimension(valor, nombre) {
        if (valor <= 0) {
            lanzar error "El " + nombre + " debe ser mayor a 0"
        }
    }
}

Garantías:

  1. Los atributos son privados → no se pueden modificar directamente

  2. El constructor valida → no se puede crear un rectángulo inválido

  3. Los métodos de modificación validan → no se puede violar la invariante después

  4. La lógica de validación está centralizada → fácil de mantener

Solution to Exercise 5

Análisis:

Caso de usoDirección necesaria
Dado un libro, obtener su editorialLibro → Editorial
Dada una editorial, obtener sus librosEditorial → Libro

Parece que necesitamos bidireccionalidad, pero hay alternativas.

Opción 1: Bidireccional simple

Libro {
    - editorial: Editorial
    + obtenerEditorial(): Editorial
}

Editorial {
    - libros: Lista<Libro>
    + obtenerLibros(): Lista<Libro>
}

Problema: Hay que mantener consistencia en ambos lados.

Opción 2: Unidireccional + Repositorio (recomendada)

Libro {
    - editorial: Editorial
    + obtenerEditorial(): Editorial
}

Editorial {
    // No conoce directamente sus libros
}

RepositorioLibros {
    + buscarPorEditorial(editorial): Lista<Libro>
}

Ventajas:

  • LibroEditorial es la relación natural (el libro sabe quién lo publicó)

  • La consulta inversa se hace a través del repositorio

  • No hay riesgo de inconsistencia

  • El modelo es más simple

Recomendación: Opción 2, especialmente si usás una base de datos donde las consultas inversas son eficientes.

Ejercicio Integrador

Solution to Exercise 6

1. Clases identificadas:

  • Hospital

  • Piso

  • Habitacion

  • Cama

  • Paciente

  • Medico

  • Especialidad

  • Enfermera

2. Análisis de relaciones:

RelaciónTipoJustificación
Hospital – PisoLos pisos son parte estructural del hospital
Piso – HabitacionLas habitaciones son parte del piso
Habitacion – CamaLas camas son parte de la habitación
Cama – PacienteAsocEl paciente existe independientemente
Medico – PacienteAsocRelación de “atiende”, no de posesión
Medico – EspecialidadLas especialidades preexisten
Piso – EnfermeraLas enfermeras pueden cambiar de piso

3. Diagrama con cardinalidades:

Modelo de clases completo para el sistema hospitalario, integrando diversos tipos de relaciones y cardinalidades.

Figure 20:Modelo de clases completo para el sistema hospitalario, integrando diversos tipos de relaciones y cardinalidades.

4. Justificaciones detalladas:

Hospital ♦── Piso:

  • Un piso no tiene sentido fuera del hospital

  • El hospital define cuántos pisos tiene desde su construcción

Piso ♦── Habitacion:

  • Las habitaciones son parte del piso

  • Si “destruís” el piso (remodelación total), las habitaciones desaparecen

Habitacion ♦── Cama:

  • Las camas están fijas en la habitación

  • Cardinalidad 1..4 porque puede ser simple (1) o compartida (2-4)

Cama ── Paciente:

  • Asociación simple: el paciente “ocupa” la cama temporalmente

  • Cardinalidad 0..1: una cama puede estar vacía o tener un paciente

Piso ◊── Enfermera:

  • Las enfermeras existen independientemente del piso

  • Pueden ser reasignadas a otros pisos

  • Cardinalidad 1..* en piso: al menos una enfermera por piso

Medico ── Paciente:

  • Relación de “atiende”, no posesión

  • Un médico atiende varios pacientes

  • Un paciente puede ser atendido por varios médicos

Medico ◊── Especialidad:

  • Las especialidades (Cardiología, Pediatría) preexisten

  • Un médico puede tener varias especialidades

  • Las especialidades no desaparecen si el médico deja el hospital

Próximo paso

Para seguir, conviene pasar a el material siguiente, donde el recorrido continúa sobre esta base.