📌 En síntesis: Toma de decisiones arquitectónicas de alto nivel: arquitecturas monolíticas en capas (N-Tier), microservicios, eventos distribuidos y API Gateways.
Guía técnica desarrollada por el equipo de Proyecto Luna para formación profesional y consulta técnica abierta.
📌
📌 Aprendizaje esperado
📌
Aplicar los conceptos de clase y objetos para diseñar con UML las clases, y relaciones entre ellas, que representan la estructura de un sistema a partir de los casos de uso.
🚀 Introducción
En el proceso de , la representación visual del sistema a través de diagramas es esencial para comunicar ideas, validar arquitecturas y documentar decisiones de diseño. El Lenguaje Unificado de Modelado (UML, por sus siglas en inglés, Unified Modeling Language) constituye el estándar de facto en la industria para la modelación estructural y de comportamiento de sistemas orientados a objetos. En las clases anteriores se abordaron los fundamentos de los diagramas UML —casos de uso, clases y secuencia— como herramientas para capturar y comunicar los distintos aspectos de un sistema. En la presente clase profundizaremos en los diagramas UML de estructura, en particular los diagramas de clases y diagramas de objetos, y su vinculación con un principio fundamental de la Programación Orientada a Objetos (POO): el polimorfismo. Este documento tiene como propósito entregarte una guía teórica y aplicada que permita comprender cómo los diagramas estructurales se relacionan con los principios de abstracción, herencia y polimorfismo, favoreciendo la extensibilidad y mantenibilidad del software. Como caso de estudio, utilizaremos el desarrollo del sistema Delivery Runner, una aplicación destinada a gestionar pedidos, repartidores y clientes en un entorno de entregas rápidas, similar a servicios reales como Rappi o PedidosYa.
Los diagramas de estructura son aquellos que muestran los componentes estáticos de un sistema: clases, objetos, paquetes, componentes, nodos y sus relaciones. Permiten visualizar la arquitectura interna y entender cómo se organiza la información y las dependencias entre entidades. De acuerdo con Booch, Rumbaugh y Jacobson (2006), los diagramas de estructura se centran en la organización lógica del software, estableciendo la base para los modelos de comportamiento y los procesos de implementación. 1.1. Diagrama de clases El diagrama de clases representa las clases que componen un sistema, sus atributos, operaciones (métodos) y las relaciones entre ellas: asociación, agregación, composición y herencia. Cada clase se representa mediante un rectángulo dividido en tres secciones:
-
Nombre de la clase (en la parte superior, en negrita).
-
Atributos (en el centro), indicando su visibilidad: + público - privado # protegido
-
Operaciones o métodos. El diagrama de clases es la base del modelado estructural en UML y facilita la comunicación entre analistas, diseñadores y desarrolladores.
⚙️ Diagramas UML de estructura
Producto - Producto: int - Producto: String - Producto: Float + calularDescuento(): float
Figura 1: Diagrama de Clases general del sistema Delivery Runner
Tabla 1. Tipos de relaciones en un diagrama de clases Fuente: ChatGPT (2023) Tipos de diagrama Descripción Asociación Relación estructural entre clases. Ejemplo: un Cliente puede tener muchos Pedidos. Agregación Relación “todo-parte”, donde las partes pueden existir independientemente. Ejemplo: un Restaurante contiene varios Repartidores. Composición Relación “todo-parte” donde las partes no pueden existir sin el todo. Ejemplo: una RutaDeEntrega depende completamente del Pedido. Herencia o generalización Indica que una clase hija hereda atributos y métodos de una clase padre. Repartidor +entrenadorPedido +calcularETA() Pedido -estado +calcularCosto() Ciclista -Pedido +calcularCosto() Caminante -nombre +recibirPedido() Punto de referencia Restaurante -nombre +recibirPedido() Cliente Cliente -nombre +realizarPedido() RutaDeEntrega -coordenadas
1.1.2. Ventajas del diagrama de clases
- Permite visualizar la estructura del sistema antes de la codificación.
- Favorece la identificación de dependencias y componentes reutilizables.
- Sirve como documento técnico para mantenimiento y comunicación de equipos. 1.2. Diagrama de objetos El diagrama de objetos muestra una instancia del diagrama de clases, es decir, cómo se comportan las clases en un momento específico de la ejecución. Mientras que el diagrama de clases representa el modelo conceptual, el diagrama de objetos representa un escenario concreto.
Figura 2: Ejemplo Diagrama de Clases detallado con interfaces
y relaciones de un sistema Delivery Runner Fuente: ChatGPT (2023) Pedido - id/int - Fecha - Total + calcularCosto() Ciclista - entregarPedido() Caminante - entregarPedido()
Ejemplo en Delivery Runner:
-
Un objeto cliente1 de la clase Cliente.
-
Un objeto pedido23 de la clase Pedido.
-
Un objeto motoR1 de la clase MotoRepartidor. 1.2.1. Uso pedagógico y profesional Los diagramas de objetos son especialmente útiles para:
-
Validar la correcta instanciación de las relaciones entre clases.
-
Representar casos de prueba o escenarios de ejecución.
-
Visualizar estados iniciales o finales de un sistema.
![Figura 3. Ejemplo Diagrama de Objetos del sistema Delivery Runner]](/img/recursos/diseno-software-estilos-arquitectonicos-capas-microservicios/figura-p10-243.png) Figura 3. Ejemplo Diagrama de Objetos del sistema Delivery Runner] (Fuente: ChatGPT (2023)
cliente1 Cliente + Ana Gómez MotoRepartidor - EntregarPedido() pedido23 Id: 23 Fecha: 2024-04-02 Total:15,5
2.1. Concepto general El polimorfismo proviene del griego polymorphos, “de muchas formas”. En POO, se refiere a la capacidad de un objeto de responder de diferentes maneras a un mismo mensaje o método. En términos simples, diferentes clases pueden implementar un mismo método de forma distinta, permitiendo que el sistema invoque automáticamente la versión adecuada según el tipo de objeto que esté ejecutando la acción. 2.2. Tipos de polimorfismo 2.2.1. Polimorfismo estático (sobrecarga de métodos) En el sistema Delivery Runner, la clase Pedido puede calcular el costo total de una orden de dos maneras diferentes: una versión del método realiza el cálculo estándar, mientras que otra versión aplica un descuento cuando corresponde. Ambos métodos comparten el mismo nombre —calcularCosto()—, pero difieren en los parámetros que reciben. Este fenómeno se conoce como polimorfismo estático o sobrecarga de métodos, y permite definir múltiples comportamientos con un mismo identificador, diferenciándolos por el tipo o la cantidad de argumentos que reciben. Gracias a esta característica, el código se vuelve más legible y flexible, ya que el programador puede invocar la versión del método más adecuada al contexto de ejecución. 2.2.2. Polimorfismo dinámico (sobreescritura de métodos) En Delivery Runner, todos los repartidores heredan de una clase base llamada Repartidor, que define un método general llamado entregarPedido().
🌐 El principio de polimorfismo
Sin embargo, cada tipo de repartidor (motorizado, ciclista o caminante) implementa este método de manera distinta, de acuerdo con sus características específicas. Por ejemplo, un MotoRepartidor puede calcular rutas más rápidas considerando tráfico vehicular, mientras que un Ciclista puede elegir trayectos más cortos a través de ciclovías. Ambos comparten la misma interfaz, pero su comportamiento interno varía. Este tipo de comportamiento se conoce como polimorfismo dinámico o sobreescritura de métodos, ya que la selección de la implementación concreta ocurre en tiempo de ejecución. El sistema invoca el método entregarPedido() de la clase específica del objeto, permitiendo que distintas clases respondan de manera diferente al mismo mensaje. 2.2.3. Ejemplo de polimorfismo estático y dinámico Para comprender mejor los tipos de polimorfismo, a continuación se presenta un ejemplo implementado en el lenguaje Java, basado en el caso Delivery Runner. Polimorfismo estático (sobrecarga de métodos) En este caso, la clase Pedido define dos métodos con el mismo nombre calcularCosto(), pero con diferente número de parámetros. Esto permite que el compilador decida, en tiempo de ejecución, cuál versión del método debe ejecutar según los argumentos recibidos.
Explicación: Aquí el método calcularCosto() tiene dos formas (sobrecarga), lo que constituye un polimorfismo estático. La selección de la versión correcta se realiza durante la ejecución, según los parámetros entregados al método. Polimorfismo dinámico (sobreescritura de métodos) En este segundo ejemplo, las clases MotoRepartidor, Ciclista y Caminante heredan de una clase base Repartidor. Cada una redefine el método entregarPedido() para ajustar su comportamiento, el cual será determinado en tiempo de ejecución, dependiendo del tipo real del objeto. public class Pedido { private double costoBase; public Pedido(double costoBase) { this.costoBase = costoBase; } // Método 1: cálculo estándar public double calcularCosto() { return costoBase; } // Método 2: cálculo con descuento public double calcularCosto(double descuento) { return costoBase - (costoBase * descuento / 100); } public static void main(String[] args) { Pedido pedido1 = new Pedido(10000); System.out.println(“Costo sin descuento: “ + pedido1.calcularCosto()); System.out.println(“Costo con 10% de descuento: “ + pedido1.calcularCos- to(10)); } }
// Clase base class Repartidor { public void entregarPedido() { System.out.println(“Entregando pedido de forma genérica…”); } } // Subclases especializadas class MotoRepartidor extends Repartidor { @Override public void entregarPedido() { System.out.println(“Entregando pedido en moto, optimizando ruta según tráfico.”); } } class Ciclista extends Repartidor { @Override public void entregarPedido() { System.out.println(“Entregando pedido en bicicleta, usando ciclovías disponibles.”); } } class Caminante extends Repartidor { @Override public void entregarPedido() { System.out.println(“Entregando pedido a pie, optimizando trayectos peatonales.”); } } // Clase principal public class SistemaEntrega { public static void main(String[] args) { Repartidor r1 = new MotoRepartidor(); Repartidor r2 = new Ciclista(); Repartidor r3 = new Caminante(); r1.entregarPedido(); r2.entregarPedido(); r3.entregarPedido(); } }
Salida esperada: Este ejemplo permite observar cómo los dos tipos de polimorfismo se complementan para generar software más robusto y mantenible:
-
El polimorfismo estático (sobrecarga) facilita la reutilización de nombres de métodos y mejora la claridad del código.
-
El polimorfismo dinámico (sobrescritura) favorece la extensibilidad del sistema, ya que permite agregar nuevas clases sin modificar las existentes. Ambos principios potencian la flexibilidad del diseño orientado a objetos, cumpliendo con el principio de abierto/cerrado (Open/Closed Principle): “El software debe estar abierto a la extensión, pero cerrado a la modificación.”
Figura 4: Ejemplo Diagrama de jerarquía de clases
con polimorfismo en Delivery Runner Fuente: ChatGPT (2023) Entregando pedido en moto, optimizando ruta según tráfico. Entregando pedido en bicicleta, usando ciclovías disponibles. Entregando pedido a pie, optimizando trayectos peatonales. MotoRepartidor Caminante Ciclista Repartidor entregarPedido():void
2.2.3. Aplicación del polimorfismo en UML En un diagrama UML de clases, el polimorfismo se representa mediante una relación de herencia (flecha con triángulo vacío apuntando hacia la clase base) y la redefinición de operaciones en las clases derivadas. En el caso de Delivery Runner, la clase abstracta Repartidor define métodos genéricos como entregarPedido() y calcularETA(). Las subclases MotoRepartidor, Ciclista y Caminante heredan estos métodos, pero los redefinen según su modalidad de transporte. Esto permite modelar el principio de “una interfaz, múltiples comportamientos”, garantizando que el sistema pueda gestionar diferentes tipos de repartidores sin necesidad de duplicar código. Ejemplo teórico: El diagrama UML de polimorfismo mostraría la clase Repartidor como abstracta, conectada a las subclases que implementan versiones propias de los métodos. En cada subclase, las operaciones entregarPedido() y calcularETA() se marcan como redefinidas, indicando su comportamiento particular.
Figura 5: Ejemplo diagrama UML de polimorfismo
del sistema Delivery Runner Fuente: ChatGPT (2023) MotoRepartidor / EntregarPedido() / calcularETA() Ciclista / EntregarPedido() / calcularETA() Caminante / EntregarPedido() / calcularETA() Repartidor Caminante / EntregarPedido() / calcularETA()
⚙️ Ejemplo caso aplicado: Sistema Delivery Runner
Un sistema Delivery Runner es una aplicación orientada a objetos diseñada para la gestión integral de pedidos, clientes, restaurantes y repartidores. Su objetivo principal es permitir el control eficiente de las entregas a domicilio mediante un modelo extensible y reutilizable, apoyado en los principios del diseño orientado a objetos y las buenas prácticas de modelado UML. 3.1. Descripción general del sistema El sistema contempla un flujo en el que un cliente realiza un pedido a un restaurante determinado, el cual asigna la orden a un repartidor disponible. Este repartidor puede ser de distintos tipos —motorizado, ciclista o caminante— dependiendo de la zona de entrega y las condiciones logísticas. Cada pedido se asocia a una ruta de entrega, la cual define un conjunto de puntos de paso (waypoints) que orientan el recorrido hasta la dirección final del cliente. La arquitectura del sistema se apoya en la herencia, el polimorfismo y la composición para representar las relaciones estructurales entre los objetos y facilitar la escalabilidad del software. 3.2. Clases clave y sus responsabilidades Cliente La clase Cliente representa a la persona que utiliza la aplicación para realizar pedidos. Contiene atributos como nombre, dirección, y teléfono, y métodos como realizarPedido() y consultarHistorial(). Cada cliente puede tener múltiples pedidos activos o finalizados, por lo que la relación entre Cliente y Pedido es de uno a muchos (1..*). Esta asociación permite, por ejemplo, que el cliente pueda visualizar su historial de compras y volver a pedir productos de manera rápida.
Pedido La clase Pedido centraliza la información asociada a una orden, incluyendo su número identificador, estado, fecha de creación, monto total y productos seleccionados. Entre sus métodos destacan:
-
calcularCosto(): calcula el valor total del pedido.
-
asignarRepartidor(): asocia el pedido a un repartidor específico.
-
actualizarEstado(): permite cambiar el estado del pedido (pendiente, en entrega, completado, cancelado). La clase Pedido se relaciona con Cliente, Restaurante y Repartidor, desempeñando un papel central en la estructura del sistema. Además, cada pedido compone una instancia de RutaDeEntrega, lo que significa que la ruta no puede existir sin su pedido asociado. Repartidor (Abstracta) La clase Repartidor es abstracta, ya que no se instanciará directamente, sino que sirve como plantilla común para las subclases concretas. Define atributos como nombre, identificador, y vehículo, y métodos como:
-
entregarPedido(): comportamiento general de entrega.
-
calcularETA(): estima el tiempo de llegada al cliente. Este diseño aprovecha el polimorfismo dinámico, ya que las subclases especializadas redefinen estos métodos de acuerdo con sus características específicas. Por ejemplo:
-
Un MotoRepartidor podría calcular la ETA basándose en la velocidad promedio del tráfico.
-
Un Ciclista podría hacerlo según la distancia por ciclovías.
-
Un Caminante consideraría rutas peatonales. Ejemplo de abstracción en el sistema Delivery Runner En Programación Orientada a Objetos, la abstracción permite representar las características esenciales de una clase sin entrar en los detalles de su implementación. Una clase abstracta actúa como una plantilla base, definiendo atributos y métodos que serán heredados y especializados por las subclases concretas. En el caso del sistema Delivery Runner, la clase Repartidor se define como abstracta, porque no se instanciará directamente: solo sirve como modelo para distintos tipos de repartidores. Ejemplo en Java: // Clase abstracta abstract class Repartidor { protected String nombre; protected String vehiculo; public Repartidor(String nombre, String vehiculo) { this.nombre = nombre; this.vehiculo = vehiculo; } // Método concreto (con implementación común) public void entregarPedido() { System.out.println(nombre + “ está preparando el pedido en su “ + vehiculo + “.”); } // Método abstracto (debe ser implementado por las subclases) public abstract double calcularETA(double distancia); } // Subclase 1 class MotoRepartidor extends Repartidor { public MotoRepartidor(String nombre) { super(nombre, “moto”); }
@Override public double calcularETA(double distancia) { return distancia / 40; // velocidad promedio 40 km/h } } // Subclase 2 class Ciclista extends Repartidor { public Ciclista(String nombre) { super(nombre, “bicicleta”); } @Override public double calcularETA(double distancia) { return distancia / 15; // velocidad promedio 15 km/h } } // Subclase 3 class Caminante extends Repartidor { public Caminante(String nombre) { super(nombre, “a pie”); } @Override public double calcularETA(double distancia) { return distancia / 5; // velocidad promedio 5 km/h } } // Clase principal public class SistemaDelivery { public static void main(String[] args) { Repartidor r1 = new MotoRepartidor(“Juan”); Repartidor r2 = new Ciclista(“Pedro”); Repartidor r3 = new Caminante(“Luis”); r1.entregarPedido(); System.out.println(“ETA estimada: “ + r1.calcularETA(10) + “ horas”); r2.entregarPedido(); System.out.println(“ETA estimada: “ + r2.calcularETA(10) + “ horas”);
Salida esperada: MotoRepartidor, Ciclista y Caminante Estas tres clases derivadas de Repartidor implementan sus propias versiones de los métodos heredados, personalizando el comportamiento de entrega según su modalidad de transporte. De esta manera, el sistema puede manejar distintos tipos de repartidores sin modificar la lógica central, demostrando el principio de abierto/cerrado (Open/Closed Principle) de la programación orientada a objetos. Restaurante El Restaurante administra los productos disponibles para los clientes y coordina la preparación de los pedidos. Entre sus métodos principales destacan:
- recibirPedido()
- actualizarStock()
- prepararPedido() Cada restaurante puede tener múltiples pedidos asociados, estableciendo así una relación de uno a muchos (1..*) con la clase Pedido. Juan está preparando el pedido en su moto. ETA estimada: 0.25 horas Pedro está preparando el pedido en su bicicleta. ETA estimada: 0.67 horas Luis está preparando el pedido en su a pie. ETA estimada: 2.0 horas
Tabla 2. Relaciones principales Fuente: ChatGPT (2023) 3.3. Relaciones principales RutaDeEntrega y Waypoint La clase RutaDeEntrega define el recorrido de un pedido desde el restaurante hasta el cliente. Contiene atributos como distancia total, duración estimada, y una lista de waypoints, que representan puntos intermedios de la ruta (por ejemplo, calles o intersecciones relevantes). La relación entre RutaDeEntrega y Waypoint es una composición (1..*), ya que los waypoints solo existen como parte de la ruta a la que pertenecen. Tipo de relación ¿Cuándo usarla? Pista visual Asociación Cliente – Pedido Un cliente puede generar múltiples pedidos. Composición RutaDeEntrega – Waypoint Cada ruta está compuesta por uno o más puntos de paso. Composición Pedido – RutaDeEntrega La ruta depende del pedido para existir. Herencia Repartidor – MotoRepartidor/Ciclista/Caminante Las subclases heredan métodos y redefinen comportamientos. Asociación Pedido – Repartidor Un pedido es entregado por un repartidor. Asociación Pedido – Restaurante Cada pedido pertenece a un único restaurante.
Figura 6: Diagrama de clases con composición entre
Pedido, RutaDeEntrega y Waypoint Fuente: ChatGPT (2023) Repartidor EntregarPedido() clacularETA() Cliente Pedido Caminante MotoRepartidor Ciclista Waypoint RutaDeEntrega
El uso de diagramas UML de estructura, en particular los diagramas de clases y objetos, permite comprender la arquitectura interna de los sistemas orientados a objetos y establecer las bases para el desarrollo modular y mantenible. El polimorfismo, como principio central de la POO, aporta flexibilidad y extensibilidad al diseño, permitiendo que diferentes objetos respondan de manera única a una misma interfaz o método. En combinación con UML, este principio facilita la representación visual de comportamientos comunes y especializados. Aplicar estos conceptos al caso Delivery Runner permite visualizar cómo la estructura del sistema y la dinámica de sus componentes cooperan para ofrecer un servicio robusto y escalable. La correcta comprensión de estos modelos es un paso esencial en la formación de un diseñador de software profesional.
💡 Conclusión
-
Booch, G., Rumbaugh, J. y Jacobson, I. (2006). El lenguaje unificado de modelado: Guía del usuario (2ª ed.). Pearson/Addison Wesley.
-
Fowler, M. (2004). UML distilled: A brief guide to the standard object modeling language (3rd ed.). Addison-Wesley.
-
Jacobson, I., Booch, G. y Rumbaugh, J. (2000). El proceso unificado de desarrollo de software. Pearson Educación.
-
Larman, C. (2005). Applying UML and Patterns: An Introduction to Object-Oriented Analysis and Design and Iterative Development. Prentice Hall.
-
Pressman, R. (2020). Ingeniería del software: Un enfoque práctico (9ª ed.). McGraw-Hill.
-
Sommerville, I. (2016). Ingeniería del software (10ª ed.). Pearson Educación.
🚀 Potencia tus proyectos tecnológicos con Proyecto Luna
En Proyecto Luna desarrollamos arquitectura de software de alto rendimiento, optimización de bases de datos críticas y soluciones aplicadas de inteligencia artificial y automatización para empresas y profesionales.
- 💼 Soluciones de ingeniería: Conoce nuestras capacidades y casos de estudio en proyectoluna.com/work/.
- 🛡️ Seguridad y auditoría: Orientación técnica y respuesta ante incidentes en ¿Te han hackeado?.
- 📩 Contacto directo: Escríbenos a través de nuestro formulario de contacto o a
contacto@proyectoluna.com.




