UML - Diagramas de paquetes

[ Home > Apuntes Tácticos > UML ]


Mapa General

UML

Resumen

Explica como usar los diagramas de paquetes, que permiten mostrar las clases que contiene el paquete y sus relaciones., o por el contrario, las relaciones de los paquetes entre si y las dependencias del sistema


Arriba

Índice de Contenidos


Introducción

Una de las preguntas más antiguas en los métodos de software es: ¿cómo se puede fragmentar un sistema grande en sistemas más pequeños? Preguntamos esto porque, en la medida en que los sistemas se hacen más grandes, se vuelve más difícil comprenderlos, así como entender sus cambios.

Los métodos estructurados se valieron de la descomposición funcional, en la cual el sistema en su conjunto se correlacionaba como función y se dividía en subfunciones, que a su vez se dividían en otras subfunciones, y así sucesivamente. Las funciones eran como los casos de uso en un sistema orientado a objetos, en el que las funciones representaban algo que hacía el sistema como un todo.

Eran los días en que el proceso y los datos estaban separados. De tal modo que, además de una descomposición funcional, también había una estructura de datos. Esta última ocupaba el segundo lugar, aunque ciertas técnicas de ingeniería de información agrupaban los registros de datos en áreas temáticas y producían matrices que mostraban la interrelación entre las funciones y los registros de datos.

Es desde este punto de vista que podemos apreciar el gran cambio que han significado los objetos. Ha desaparecido la separación entre el proceso y los datos, y la descomposición funcional, pero la vieja pregunta sigue en pie. Una idea es agrupar las clases en unidades de nivel más alto. Esta idea aparece, aplicada de manera muy libre, en muchos métodos orientados a objetos. En el UML, a este mecanismo de agrupamiento se le llama paquete.

La idea de un paquete se puede aplicar a cualquier elemento de un modelo, no sólo a las clases. Sin cierta heurística que agrupe las clases, el agrupamiento se vuelve arbitrario. El que yo he encontrado más útil, que también es el que recibe mayor énfasis en el UML, es la de pendencia. Empleo el término diagrama de paquetes para indicar un diagrama que muestra los paquetes de clases y las dependencias entre ellos.

Hablando estrictamente, los paquetes y las dependencias son elementos de un diagrama de clases, por lo cual un diagrama de paquetes es sólo una forma de un diagrama de clases. En la práctica dibujo estos diagramas por diferentes razones, así que me gusta manejar nombres diferentes.

Existe una (dependency) dependencia entre dos elementos si los cambios a la definición de un elemento pueden causar cambios al otro. En las clases, la dependencia existe por varias razones: una clase envía un mensaje a otra; una clase tiene a otra como parte de sus datos; una clase menciona a otra como parámetro para una operación. Si una clase cambia su interfaz, entonces los mensajes que envía pueden dejar de ser válidos.

En forma ideal, sólo los cambios a una interfaz de clase deberían afectar a otra clase. El arte del diseño en gran escala implica minimizar las dependencias, de modo tal que se reduzcan los efectos del cambio y se requiera de menos esfuerzo para cambiar el sistema.

En la Figura 7-1 tenemos las clases de dominio que modelan el negocio, las cuales se agrupan en dos paquetes: Pedidos y Clientes. Ambos paquetes son parte de un paquete que abarca todo el dominio. La aplicación de Captura de pedidos tiene dependencias con los dos paquetes del dominio. La Interfaz de Usuario (IU) para Captura de pedidos tiene dependencias con la Aplicación Captura de pedidos y con AWT (un juego de herramientas GUI de Java).

Existe una dependencia entre dos paquetes si existe algún tipo de dependencia entre dos clases cualquiera en los paquetes. Por ejemplo, si cualquier clase en el paquete Lista de correo depende de cualquier clase del paquete Clientes, entonces se da una dependencia entre sus paquetes correspondientes.

Existe una similitud obvia entre dependencia de paquetes y dependencias de compilación. Pero también, de hecho, hay una diferencia vital: con los paquetes, las dependencias no son transitivas.

Un ejemplo de relación transitiva es aquella en la que Jim tiene una barba más larga que Grady y éste, una más larga que Ivar, por lo que se deduce que Jim tiene una barba más larga que Ivar. Otros ejemplos incluyen relaciones como "está al norte de" y "es más alto que". Por otra parte, "es un amigo de" no constituye una relación transitiva.

Para apreciar por qué es importante esto para las dependencias, obsérvese de nuevo Figura 7-1 . El cambio a una clase del paquete Pedidos no indica que el paquete IU Captura de pedidos deba ser cambiado. Indica tan sólo que debe revisarse el paquete de aplicación Captura de pedidos para ver si cambia. Sólo si se altera la interfaz del paquete de aplicación Captura de pedidos hay necesidad de cambiar el paquete IU Captura de pedidos. Si esto es así, la aplicación Captura de pedidos está protegiendo a la IU Captura de pedidos de cambios a los pedidos.

Este comportamiento representa el propósito clásico de una arquitectura en capas. De hecho, ésta es la semántica del comportamiento de las "imports" de Java, pero no la del comportamiento de los "include" de C/C ++, ni la de los prerrequisitos de Envy. El include de C/C ++ es transitivo, lo que significa que la IU Captura de pedido sería dependiente del paquete de Pedidos. Una dependencia transitiva hace difícil limitar el alcance de los cambios mediante la compilación.

¿Qué significa trazar una dependencia con un paquete que contenga subpaquetes? Los diseñadores se sirven de convenciones diferentes.

Figura 07-01(8K)

Figura 7-1: Diagrama de paquetes

Algunos suponen que dibujar una dependencia hacia un paquete "contenedor" da visibilidad al contenido de todos los paquetes contenidos y a sus respectivos contenidos. Otros consideran que sólo se ven las clases dentro del paquete contenedor, pero no las clases dentro de los paquetes anidados (o sea, la visión es opaca).

Debe declararse explícitamente la convención que se está utilizando en el proyecto o dejarla planteada de manera muy clara colocando estereotipos en los paquetes. Sugiero usar el estereotipo «transparente» para indicar que se pueden ver los paquetes anidados, y usar el estereotipo «opaco» para indicar que no se pueden ver. Aquí, mi convención es que los paquetes sean transparentes.

¿Qué es lo que se ve, si se tiene una dependencia hacia un paquete? En esencia, se ven todas las clases públicas del paquete y todos sus métodos públicos. Bajo el esquema de visibilidad de C++, esto puede causar un problema, debido a que tal vez se quiera tener una clase que contenga métodos que pueden ser vistos por otros objetos dentro del mismo paquete, pero no por objetos que pertenezcan a otros paquetes.

Ésta es la razón por la cual Java tiene la visibilidad de paquete. Por supuesto, esto le simplifica las cosas a Java. Dentro de C++ se pueden marcar clases y operaciones con visibilidad de paquetes. Aun cuando esta convención no es aplicada por el compilador, sigue siendo útil para el diseño.

Una técnica eficaz aquí es reducir aún más la interfaz del paquete exportando sólo un pequeño subconjunto de las operaciones asociadas con las clases del paquete. Esto se puede hacer dando a todas las clases visibilidad de paquete, de tal modo que sólo puedan ser vistas por otras clases del mismo paquete y añadiendo otras clases públicas para el comportamiento público. Estas clases adicionales, llamadas Fachadas (Facades) delegan operaciones públicas a sus compañeras más tímidas dentro del paquete.

Los paquetes no dan respuestas sobre la manera de reducir las dependencias en el sistema, pero sí ayudan a ver cuáles son las dependencias, y sólo se puede efectuar el trabajo para reducidas, cuando es posible verlas. Los diagramas de paquetes son, desde mi punto de vista, una herramienta clave para mantener el control sobre la estructura global de un sistema.

Figura 07-02(13K)

Figura 7-2:Diagrama de paquetes avanzado

La Figura 7-2 es un diagrama de paquetes más complejo que contiene artificios adicionales.

En primer lugar, vemos que he añadido un paquete de Dominio que contiene paquetes de pedidos y clientes. Esto es de utilidad, pues significa que puedo trazar dependencias desde y hacia el paquete general, en lugar de trazar muchas dependencias separadas.

Cuando se muestra el contenido de un paquete, se pone el nombre del paquete en una "etiqueta" y el contenido dentro del cuadro principal. Estos contenidos pueden ser una lista de clases (tal y como sucede en el paquete Común), otro diagrama de paquetes (como en Dominio) o un diagrama de clases (que no se muestra, pero el concepto ya debe ser obvio).

La mayor parte de las veces, considero suficiente listar las clases clave, pero en algunas ocasiones es útil otro diagrama. En el presente caso he mostrado que, mientras la aplicación Captura de órdenes tiene una dependencia con todo el paquete Dominio, la aplicación Lista de correo sólo depende del paquete Clientes. Estrictamente hablando, el mero listado de clases no es UML puro (se deben mostrar los iconos de clase), pero éste constituye una de las áreas en que me inclinaría a las reglas.

La Figura 7-2 muestra el paquete Común marcado como {global}. Esto significa que todos los paquetes del sistema tienen una dependencia hacia él. Por supuesto, se debe emplear este artificio con moderación, pero las clases comunes (como Dinero) se emplean en todas partes.

Con los paquetes se puede aplicar la generalización. Esto significa que el paquete específico debe conformarse a la interfaz del paquete general. Esto es comparable con la perspectiva de especificación de la subtipificación en los diagramas de clases (véase el capítulo 4). Por tanto, de acuerdo con la Figura 7-2 , el agente de la base de datos puede usar la Interfaz con Oracle o la Interfaz con Sybase. Cuando se usa la generalización de este modo, el paquete general puede marcarse como {abstracto} para mostrar que sólo define una interfaz implementada por un paquete más específico.

La generalización implica una dependencia del subtipo al supertipo (no se necesita mostrar la dependencia extra; basta con la generalización misma). El poner las clases abstractas en un paquete de supertipo es una buena forma de romper ciclos en la estructura de dependencias. En tal situación, los paquetes de interfaz con la base de datos son los responsables de cargar y guardar los objetos de dominio en una base de datos. Por lo tanto, necesitan saber sobre los objetos del dominio. Los objetos del dominio, sin embargo, deben disparar las operaciones de carga y guardado.

La generalización nos permite poner la interfaz disparadora necesaria (varias operaciones de carga y guardado) en el paquete de interfaz con la base de datos. Estas operaciones, posteriormente, son implementadas por clases dentro de los paquetes de subtipo. No necesitamos una dependencia entre el paquete de interfaz con la base de datos y el paquete de interfaz con Oracle, ya que al momento de ejecución será en realidad el paquete de subtipo el que va a ser llamado por el dominio. Pero el dominio sólo piensa que está tratando con el paquete de interfaz (más simple) con la base de datos. El polimorfismo es tan útil para los paquetes como lo es para las clases.

Como regla general, es buena idea quitar los ciclos de la estructura de dependencias. No estoy convencido de que se puedan quitar todos los ciclos, pero ciertamente se deberán minimizar. Si se tienen, habrá que tratar de contenerlos dentro de un paquete contenedor más grande. En la práctica he encontrado casos en que no me ha sido posible evitar ciclos entre paquetes de dominio, pero de todas maneras trato de eliminarlos de las interacciones entre el dominio y las interfaces externas. La generalización de paquetes es un elemento clave para hacerlo.

En un sistema ya existente, las dependencias se pueden deducir observando las clases. Ésta es una tarea muy útil que puede ser llevada a cabo por una herramienta. Encuentro esto muy útil, si trato de mejorar la estructura de un sistema ya existente. Un paso útil inicial es dividir las clases en paquetes y analizar las dependencias entre estos últimos. Después realizo un reordenamiento de factores para reducir las dependencias.


Arriba

Cuándo utilizar los diagramas de paquetes

Los paquetes son una herramienta vital para los proyectos grandes. Úselos siempre que un diagrama de clases que abarque todo el sistema ya no sea legible en una hoja de papel tamaño carta (o A4).

Deberá mantener sus dependencias al mínimo, ya que ello reduce el acoplamiento. Sin embargo, la heurística de esto no está bien comprendida.

Los paquetes son especialmente útiles para pruebas. Aunque yo escribo algunas pruebas para verificar clase por clase, prefiero hacer mis pruebas unitarias en el nivel de paquete por paquete. Cada paquete deberá tener una o más clases de pruebas que verifiquen su comportamiento.


Arriba

Para mayor información

La fuente original de paquetes era Grady Booch (1994); los llamaba categorías de clase. Su análisis, sin embargo, era muy breve. El mejor estudio que conozco sobre este tema es el de Robert Martin (1995), cuyo libro da varios ejemplos de utilización de Booch y C++, prestando gran atención a la minimización de las dependencias. También puede encontrarse información valiosa en Wirfs-Brock (1990), autora que se refiere a los paquetes como subsistemas.


Arriba

Índice


Arriba

Referencia Bibliográfica

Fowler, Martín
UML, gota a gota
Addison Wesley Longman de México,SA de CV
México 1.999
ISBN: 968-44-364-1
Formato 17x23
Paginas 224


Atrás arriba Adelante

© 2.003 - La Güeb de Joaquín - Apuntes Tácticos - UML
[Arriba] [Home] [Apuntes] [UML] [Correo]
La documentación es como el sexo: cuando es bueno, es muy,
muy bueno; y cuando es malo, es mejor que nada.
Fecha de la última actualización.:  17 / Septiembre / 2003
Esta página es española