Tarea: Diseño de clase
Esta tarea define cómo diseñar la estructura de clases de un subsistema o componente.
Disciplinas: Análisis y diseño
Objetivo
  • Garantizar que la clase proporciona el comportamiento que requieren las realizaciones de los casos de uso
  • Garantizar que se proporciona suficiente información para implementar la clase sin ambigüedad
  • Manejar los requisitos no funcionales relacionados con la clase
  • Incorporar los mecanismos de diseño que utiliza la clase
Relaciones
Descripción principal

Las clases son la fuerza de trabajo del proyecto de diseño, las que realizan el trabajo real del sistema. Los otros elementos de diseño como, por ejemplo, los subsistemas, los paquetes y las colaboraciones, describen cómo se agrupan y cómo interactúan las clases.

Las cápsulas son también clases estereotipadas, que se utilizan para representar hebras de ejecución concurrentes en sistemas en tiempo real. En estos casos, las otras clases de diseño son clases pasivas, que se ejecutan en el contexto de ejecución proporcionado por las cápsulas activas. Cuando el diseñador y el arquitecto de software deciden no utilizar un enfoque de diseño basado en cápsulas, todavía se puede modelar el comportamiento concurrente utilizando clases activas.

Las clases activas son clases de diseño que coordinan y controlan el comportamiento de las clases pasivas: una clase activa es una clase cuyas instancias son objetos activos, que poseen su propia hebra de control.

Pasos
Utilizar mecanismos y patrones de diseño

Utilice los mecanismos y los patrones de diseño que se adapten a la clase o posibilidad que está diseñando, de acuerdo con las directrices de diseño del proyecto.

La incorporación de un patrón y/o un mecanismo significa realizar de forma eficaz muchos de los pasos posteriores en esta tarea (añadir nuevas clases, operaciones, atributos y relaciones), de acuerdo con las reglas definidas por el patrón o mecanismo.

Tenga en cuenta que los patrones y los mecanismos se incorporan normalmente a medida que evoluciona el diseño, no sólo como el primer paso en esta tarea. También se aplican a menudo entre un conjunto de clases, en lugar de sólo a una clase.

Crear clases de diseño inicial

Cree una o varias clases de diseño inicial para la clase de análisis proporcionada como entrada a esta tarea y asigne dependencias de rastreo. Las clases de diseño creadas en este paso se perfeccionarán, ajustarán, dividirán o fusionarán en los pasos posteriores cuando se les asignen distintas propiedades de diseño como, por ejemplo, operaciones, métodos y una máquina de estado, que describen cómo se diseña la clase de análisis.

Dependiendo del tipo de clase de análisis (límite, entidad o control) que se está diseñando, existen estrategias específicas que puede utilizar para crear clases de diseño inicial.

Diseño de clases de límite

Las clases de límite representan interfaces con los usuarios u otros sistemas.

Normalmente, las clases de límite que representan interfaces con otros sistemas se modelan como subsistemas, ya que a menudo tienen un comportamiento interno complejo. Si el comportamiento de la interfaz es sencillo (por ejemplo, si actúa sólo como paso a través a una API existente del sistema externo), puede elegir representar la interfaz con una o más clases de diseño. Si elige esta ruta, utilice una única clase de diseño por protocolo, interfaz o API, y tenga en cuenta los requisitos especiales sobre los estándares que ha utilizado en los requisitos especiales de la clase.

Las clases de límite que representan interfaces con usuarios generalmente siguen la regla de una clase de límite para cada ventana, o una para cada formulario, en la interfaz de usuario. Por lo tanto, las responsabilidades de las clases de límite pueden tener un nivel bastante alto, y se deben perfeccionar y detallar en este paso. Los modelos o prototipos adicionales de la interfaz de usuario pueden ser otra fuente de entrada a considerar en este paso.

El diseño de las clases de límite depende de las herramientas de desarrollo de la interfaz de usuario (UI) que hay disponibles para el proyecto. Utilizando la tecnología actual, lo normal es que la UI se construya directamente en la herramienta de desarrollo de forma visual. Esto crea automáticamente clases de UI que se deben relacionar con el diseño de las clases de control y entidad. Si el entorno de desarrollo de UI crea automáticamente las clases de soporte que necesita para implementar la UI, no es necesario considerarlas en el diseño. Sólo es necesario diseñar aquellos elementos que no crea el entorno de desarrollo automáticamente.

Diseño de clases de entidad

Durante el análisis, las clases de entidad representan unidades de información manipuladas. A menudo son pasivas y persistentes, y se pueden identificar y asociar con el mecanismo de análisis de la persistencia. Los detalles del diseño de un mecanismo de persistencia basado en base de datos se describen en Tarea: Diseño de base de datos. Las consideraciones del rendimiento pueden forzar la refactorización de las clases persistentes, provocando cambios en el modelo de diseño que se describen conjuntamente en Rol: Diseñador de base de datos y Rol: Diseñador.

Puede encontrar más adelante una descripción más amplia de los temas de diseño de las clases persistentes en la cabecera Identificar clases persistentes.

Diseño de clases de control

Un objeto de control es responsable de gestionar el flujo de un caso de uso y, por lo tanto, coordina la mayoría de sus acciones; los objetos de control encapsulan la lógica que no está particularmente relacionada con los aspectos de la interfaz de usuario (objetos de límite) o los aspectos de la ingeniería de datos (objetos de entidad). Esta lógica se denomina también lógica de aplicaciones o la lógica empresarial.

Tenga en cuenta los aspectos siguientes cuando diseñe clases de control:

  • Complejidad: Puede manejar el comportamiento de coordinación o control que no es complicado utilizando clases de límite o entidad. No obstante, a medida que aumenta la complejidad de la aplicación, este enfoque presenta algunos inconvenientes significativos como, por ejemplo:
  • el comportamiento de coordinación del caso de uso se incorpora en la UI, lo que hace que sea más difícil cambiar el sistema
  • la misma UI no se puede utilizar en realizaciones de casos de uso diferentes sin dificultad
  • la UI se sobrecarga de funcionalidad adicional, lo que reduce su rendimiento
  • los objetos de entidad se pueden sobrecargar con el comportamiento específico del caso de uso , lo que reduce su generalidad

Para evitar estos problemas, se introducen las clases de control para proporcionar un comportamiento relacionado con la coordinación de flujos de sucesos.

  • Probabilidad de cambio: Si la probabilidad de cambiar flujos de sucesos es menor o el coste es insignificante, el gasto y la complejidad adicionales de añadir clases de control puede no estar justificada.
  • Distribución y rendimiento: La necesidad de ejecutar componentes de la aplicación en distintos nodos, o en distintos espacios de proceso, introduce la necesidad de especializar los elementos del modelo de diseño. Esta especialización se consigue normalmente añadiendo objetos de control y distribuyendo el comportamiento desde las clases de límite y entidad a las clases de control. De esta forma, las clases de límite migran a proporcionar sólo servicios de UI, las clases de entidad pasan a proporcionar sólo servicios de datos y las clases de control proporcionan el resto.
  • Gestión de transacciones: La gestión de transacciones es una actividad de coordinación clásica. Sin una infraestructura para manejar la gestión de transacciones, una o varias clases de gestor de transacciones tendrán que interactuar para garantizar que se mantiene la integridad de las transacciones.

En los últimos dos casos, si la clase de control representa una hebra de control independiente, puede que sea más adecuado utilizar una clase activa para modelar la hebra de control. En un sistema en tiempo real, el uso de Producto de trabajo: Cápsulas es el enfoque de modelado preferido.

Identificar clases persistentes

Las clases que deben almacenar su estado en un medio permanente se conocen como persistentes. La necesidad de almacenar su estado puede ser para el registro permanente de información de las clases, como copia de seguridad en el caso de una anomalía del sistema, o para el intercambio de información. Una clase persistente puede tener instancias persistentes y transitorias; el etiquetado de una clase como persistente significa sólo que algunas instancias de la clase puede que tengan que ser persistentes.

Incorpore los mecanismos de diseño correspondientes a los mecanismos de persistencia encontrados durante el análisis. Por ejemplo, dependiendo de las necesidades de la clase, el mecanismo de análisis de persistencia puede realizarse mediante uno de estos mecanismos de diseño:

  • Almacenamiento en memoria
  • Tarjeta Flash
  • Archivo binario
  • Sistema de gestión de bases de datos (DBMS)

Los objetos persistentes puede que no se deriven sólo de clases de entidad; los objetos persistentes también pueden ser necesarios para manejar requisitos no funcionales en general. Algunos ejemplos son los objetos persistentes necesarios para mantener la información relevante para el control de procesos o para mantener información de estado entre transacciones.

La identificación de clases persistentes permite notificar al Rol: Diseñador de base de datos que la clase necesita especial atención a las características de almacenamiento físico. También notifica al Rol: Arquitecto de software que la clase debe ser persistente y al Rol: Diseñador responsable del mecanismo de persistencia que las instancias de la clase deben convertirse en persistentes.

Debido a la necesidad de una estrategia de persistencia coordinada, el Rol: Diseñador de base de datos es el responsable de correlacionar las clases persistentes en la base de datos, utilizando una infraestructura de persistencia. Si el proyecto desarrolla una infraestructura de persistencia, el desarrollador de la infraestructura también será responsable de entender los requisitos de persistencia de las clases de diseño. Para proporcionar a estas personas la información que necesitan, es suficiente en este punto indicar que la clase es persistente o, más concretamente, que las instancias de la clase son persistentes.

Definir la visibilidad de la clase

Para cada clase, determine la visibilidad de la clase en el paquete en el que reside. Se puede hacer referencia a una clase pública fuera del paquete que la contiene. Sólo se puede hacer referencia a una clase privada (o una cuya visibilidad es implementación) desde las clases dentro del mismo paquete.

Definir operaciones

Identificación de operaciones

Para identificar operaciones en las clases de diseño:

  • Estudie las responsabilidades de cada clase de análisis correspondiente, creando una operación para cada responsabilidad. Utilice la descripción de la responsabilidad como la descripción inicial de la operación.
  • Estudie las realizaciones de los casos de uso en la clase participates para ver cómo utilizan las operaciones. Amplíe las operaciones, para cada realización de caso de uso , y perfecciones las operaciones, sus descripciones, tipos de retorno y parámetros. Los requisitos de cada realización de caso de uso pertenecientes a las clases se describen textualmente en el Flujo de sucesos de la realización del caso de uso .
  • Estudie el caso de uso Requisitos especiales para asegurarse de que no omite los requisitos implícitos en la operación que allí se indica.

Las operaciones son necesarias para dar soporte a los mensajes que aparecen en los diagramas de secuencia, ya que los scripts -especificaciones de mensajes temporales que no se han asignado todavía a las operaciones- describen el comportamiento que se espera de la clase. En la Figura 1 se muestra un ejemplo de un diagrama de secuencia.

Diagrama descrito en el texto adjunto.

Figura 1: Los mensajes constituyen la base para identificar operaciones

Las realizaciones de los casos de uso no pueden proporcionar suficiente información para identificar todas las operaciones. Para encontrar las operaciones restantes, tenga en cuenta lo siguiente:

  • ¿Hay alguna forma de inicializar una nueva instancia de la clase, incluido conectarla a instancias de otras clases con las que está asociada?
  • ¿Es necesario probar si dos instancias de la clase son iguales?
  • ¿Es necesario crear una copia de una instancia de la clase?
  • ¿Hay operaciones necesarias en la clase por los mecanismos que utilizan? Por ejemplo, un mecanismo de recopilación de basura puede necesitar que un objeto pueda eliminar todas sus referencias a los demás objetos, para que se puedan liberar los recursos no utilizados.

No defina operaciones que sólo obtengan y establezcan los valores de atributos públicos (consulte Definir atributos y Definir asociaciones). Normalmente, se generan mediante recursos de generación de código y no es necesario definirlas de forma explícita.

Denominación y descripción de las operaciones

Utilice convenios de denominación para el lenguaje de implementación cuando denomine operaciones, tipos de retorno, y parámetros y sus tipos. Se describen en las Directrices específicas del proyecto.

Para cada operación, debe definir lo siguiente:

  • El nombre de operación: Proporcione un nombre abreviado y descriptivo del resultado de la operación.
    • Los nombres de las operaciones deben seguir la sintaxis del lenguaje de implementación. Por ejemplo: find_location será aceptable para C++ o Visual Basic, pero no para Smalltalk (donde no se utilizan subrayados); un nombre más adecuado será findLocation.
    • Evite nombres que impliquen cómo se realiza la operación. Por ejemplo, Employee.wages() es mejor que Employee.calculateWages(), ya que el último implica que se realiza un cálculo. La operación puede devolver simplemente un valor en una base de datos.
    • El nombre de una operación debe mostrar claramente su objetivo. Evite nombres poco específicos como, por ejemplo, getData, que no son descriptivos del resultado que devuelven. Utilice un nombre que muestre exactamente qué se espera como, por ejemplo, getAddress. O mejor aún, haga que el nombre de la operación sea el nombre de la propiedad que se devuelve o se establece. Si tiene un parámetro, establece la propiedad. Si no tiene ningún parámetro, obtiene la propiedad. Por ejemplo: la operación address devuelve la dirección de un Cliente, mientras que address(aString) establece o cambia la dirección del Cliente. La naturaleza de obtener y establecer de la operación es implícita desde la firma de la operación.
    • Las operaciones que son conceptualmente idénticas deberían tener el mismo nombre, aunque las definan clases diferentes, se implementen de forma completamente distinta o tengan un número de parámetros diferente. Por ejemplo, una operación que crea un objeto debería tener el mismo nombre en todas las clases.
    • Si hay operaciones en varias clases que tienen la misma firma, la operación debe devolver el mismo tipo de resultado adecuado para el objeto receptor. Este es un ejemplo del concepto de polimorfismo, que establece que objetos diferentes deben responder al mismo mensaje de forma parecida. Por ejemplo: la operación name debe devolver el nombre del objeto, independientemente de cómo se almacene o se derive el nombre. Si se sigue este principio, el modelo es más fácil de entender.
  • El tipo de retorno: El tipo de retorno debe ser la clase de objeto que devuelve la operación.
  • Una breve descripción: Aunque proporcione un nombre de operación significativo, a menudo sólo es vagamente útil cuando se intenta entender qué hace la operación. Proporcione una breve descripción de la operación formada por un par de frases, escritas desde la perspectiva del usuario de la operación.
  • Los parámetros: Para cada parámetro, cree un nombre descriptivo abreviado, decida su clase y proporcione una breve descripción. Cuando especifique parámetros, recuerde que cuanto menor sea el número de parámetros, mayor es la capacidad de reutilización. Un número pequeño de parámetros hace que la operación sea más fácil de entender y, por lo tanto, hay una mayor probabilidad de encontrar operaciones parecidas. Puede que tenga que dividir una operación con muchos parámetros en varias operaciones. La operación debe ser comprensible para aquellos que deseen utilizarla. La breve descripción debe incluir:
    • el significado de los parámetros, si no es aparente en el nombre
    • si el parámetro se pasa por valor o por referencia
    • parámetros que deben tener valores proporcionados
    • parámetros que pueden ser opcionales y los valores por omisión, si no se proporciona ningún valor
    • rangos válidos para los parámetros, si es aplicable
    • qué se hace en la operación
    • qué parámetros por referencia modifica la operación

Una vez definidas las operaciones, complete los diagramas de secuencia con información sobre qué operaciones se invocan para cada mensaje.

Consulte el apartado Directriz de producto de trabajo: Clase de diseño para obtener más información.

Definición de la visibilidad de la operación

Para cada operación, identifique la visibilidad de exportación de la operación a partir de estas opciones:

  • Pública: la operación es visible para modelar elementos diferentes de la propia clase.
  • Implementación: la operación es visible sólo dentro de la misma clase.
  • Protegida: la operación es visible sólo para la clase, sus subclases, o para amigos de la clase (dependientes del lenguaje).
  • Privada: la operación sólo es visible para la clase y los amigos de la clase.

Elija la visibilidad más restringida posible que pueda lograr los objetivos de la operación. Para ello, observe los diagramas de secuencia y, para cada mensaje, determine si el mensaje proviene de una clase fuera del paquete del receptor (requiere visibilidad pública), del interior del paquete (requiere visibilidad de implementación), de una subclase (requiere visibilidad protegida), o de la propia clase o un amigo (requiere visibilidad privada).

Definición de las operaciones de clase

En la mayoría de los casos, las operaciones son operaciones de instancias; esto es, se ejecutan en instancias de la clase. No obstante, hay casos en los que una operación se aplica a todas las instancias de la clase y, por lo tanto, es una operación de ámbito de clase. El receptor de la operación de clase es una instancia de una metaclase -la descripción de la propia clase- en lugar de una instancia específica de la clase. Los ejemplos de las operaciones de clase incluyen mensajes que crean nuevas instancias, que devuelven todas las instancias de una clase.

La serie de la operación se subraya para denotar una operación de ámbito de clase.

Definir métodos

Un método especifica la implementación de una operación. En muchos casos en los que el comportamiento necesario para la operación está suficientemente definido por el nombre de operación, la descripción y los parámetros, los métodos se implementan directamente en el lenguaje de programación. Cuando la implementación de una operación requiere el uso de un algoritmo específico o más información de la que se presenta en la descripción de la operación, se necesita una descripción de método aparte. El método describe cómo funciona la operación, no sólo lo que hace.

El método debe describir cómo:

  • se implementarán las operaciones
  • se implementarán los atributos y se utilizarán para implementar operaciones
  • se implementarán las relaciones y se utilizarán para implementar operaciones

Los requisitos variarán de un caso a otro, aunque las especificaciones de método de una clase deben indicar siempre:

  • qué se hará de acuerdo con los requisitos
  • qué otros objetos se utilizarán (y sus operaciones)

Otros requisitos más específicos hacen referencia a:

  • cómo se implementarán los parámetros
  • qué algoritmos especiales, si hay alguno, se utilizarán

Los diagramas de secuencia constituyen una fuente importante de información. A partir de ellos, se deduce qué operaciones se utilizarán en otros objetos cuando se ejecute una operación. La especificación de qué operaciones se utilizarán en otros objetos es necesaria para la completa implementación de una operación. Por lo tanto, la producción de una especificación de método completa requiere que identifique las operaciones de los objetos implicados e inspeccione los diagramas de secuencia correspondientes.

Definir estados

Para algunas operaciones, el comportamiento de la operación depende del estado en el que está el objeto receptor. Una máquina de estado es una herramienta que describe los estados que puede asumir un objeto y los sucesos que hacen que el objeto pase de un estado a otro (consulte Técnica: Diagrama de gráfico de estados). Las máquinas de estado son muy útiles para describir clases activas. La utilización de máquinas de estado es particularmente importante para definir el comportamiento de Producto de trabajo: Cápsulas.

En la Figura 2 se muestra un ejemplo de una máquina de estado simple.

Diagrama descrito en el texto adjunto.

Figura 2: Un diagrama de gráfico de estados simple de un dispensador de combustible

Cada suceso de transición de estado se puede asociar con una operación. Dependiendo del estado del objeto, la operación puede tener un comportamiento diferente y los sucesos de transición describen este cambio de comportamiento.

La descripción de método de la operación asociada se debe actualizar con la información específica del estado, indicando para cada estado relevante qué debe hacer la operación. Los estados se representan a menudo utilizando atributos; los diagramas de gráfico de estados sirven como entrada en el paso de identificación del atributo.

Para obtener más información, consulte Técnica: Diagrama de gráfico de estados.

Definir atributos

Durante la definición de los métodos y la identificación de los estados, se identifican los atributos que necesita la clase para llevar a cabo sus operaciones. Los atributos proporcionan almacenamiento de información para la instancia de clase y a menudo se utilizan para representar el estado de la instancia de clase. La información mantenida por la clase se mantiene mediante sus atributos. Para cada atributo, defina:

  • el nombre, que debe seguir los convenios de denominación del lenguaje de implementación y el proyecto
  • el tipo, que será un tipo de datos elemental soportado por el lenguaje de implementación
  • el valor inicial o por omisión con el que se inicializa cuando se crean nuevas instancias de la clase
  • la visibilidad, que tomará uno de los valores siguientes:
    • Pública: el atributo es visible desde dentro y fuera del paquete que contiene la clase
    • Protegida: el atributo es visible sólo para la clase, sus subclases, o para amigos de la clase (dependientes del lenguaje)
    • Privada: el atributo sólo es visible para la clase o los amigos de la clase
    • Implementación: el atributo es visible sólo para la clase
  • las clases persistentes, tanto si el atributo es persistente (valor por omisión) como si es transitorio. Aunque la clase sea persistente, no todos los atributos de la clase tienen que ser persistentes.

Compruebe que todos los atributos son necesarios. Los atributos deben estar justificados; es fácil que los atributos se añadan al principio del proceso y sobrevivan cuando ya no son necesarios debido a falta de previsión. Los atributos adicionales, multiplicados por miles o millones de instancias, pueden tener un efecto negativo en el rendimiento y los requisitos de almacenamiento de un sistema.

Consulte el apartado Atributos en Directriz de producto de trabajo: Clase de diseño para obtener más información sobre los atributos.

Definir dependencias

En los casos en los que se necesite la comunicación entre objetos, hágase estas preguntas:

  • ¿La referencia al receptor se ha pasado como parámetro a la operación? En caso afirmativo, establezca una dependencia entre las clases de emisor y receptor en un diagrama de clase que contenga las dos clases. Asimismo, si se utiliza el formato de diagrama de comunicación para las interacciones, califique la visibilidad del enlace y establézcala como parámetro.
  • ¿El receptor es global? En caso afirmativo, establezca una dependencia entre las clases de emisor y receptor en un diagrama de clase que contenga las dos clases. Asimismo, si se utiliza el formato de diagrama de comunicación para las interacciones, califique la visibilidad del enlace y establézcala como global.
  • ¿El receptor es un objeto temporal creado y destruido durante la operación? En caso afirmativo, establezca una dependencia entre las clases de emisor y receptor en un diagrama de clase que contenga las dos clases. Asimismo, si se utiliza el formato de diagrama de comunicación para las interacciones, califique la visibilidad del enlace y establézcala como local.

Tenga en cuenta que los enlaces modelados de esta forma son enlaces transitorios, que sólo existen un tiempo limitado en el contexto específico de la colaboración; en este sentido, son instancias del rol de asociación en la colaboración. No obstante, la relación en un modelo de clase (es decir, independiente del contexto) debe ser una dependencia, tal como se ha indicado anteriormente. Como se describe en [RUM98], en la definición de enlace transitorio: "Todos estos enlaces se pueden modelar como asociaciones, pero las condiciones de las asociaciones se deben establecer de forma amplia, y pierden mucha precisión en la restricción de combinaciones de objetos". En esta situación, el modelado de una dependencia es menos importante que el modelado de la relación en la colaboración, ya que la dependencia no describe la relación completamente; sólo que existe.

Definir asociaciones

Las asociaciones proporcionan el mecanismo para que los objetos se comuniquen entre ellos. Proporcionan a los objetos un conducto por el que pueden fluir los mensajes. También documentan las dependencias entre clases, resaltando que los cambios en una clase se pueden sentir en muchas otras clases.

Examine las descripciones de método de cada operación para entender cómo se comunican y colaboran las instancias de la clase con otros objetos. Para enviar un mensaje a otro objeto, un objeto debe tener una referencia al receptor del mensaje. Un diagrama de comunicación (una representación alternativa de un diagrama de secuencia) mostrará la comunicación del objeto en términos de enlaces, tal como se muestra en la Figura 3.

Diagrama descrito en el texto adjunto.

Figura 3: Un ejemplo de un diagrama de comunicación

Definición de asociaciones y agregaciones

Los restantes mensajes utilizan la asociación o la agregación para especificar la relación entre las instancias de dos clases que se comunican. Consulte los apartados Técnica: Asociación y Técnica: Agregación para obtener información sobre cómo elegir la representación adecuada. Para estas dos asociaciones, establezca la visibilidad del enlace como campo en los diagramas de comunicación. Otras tareas posibles son:

  • Establezca la navegabilidad de las asociaciones y las agregaciones. Para ello, considere que las navegabilidades son necesarias en las creaciones de instancias de enlaces en los diagramas de interacción. Como la navegabilidad es true por omisión, sólo tiene que encontrar asociaciones (y agregaciones) allí donde todos los roles de enlace opuestos de todos los objetos de una clase en la asociación no requieran navegabilidad. En estos casos, establezca la navegabilidad en false en el rol de la clase.
  • Si hay atributos en la propia asociación (representados por clases de asociación), cree una clase de diseño para representar la clase de asociación, con los atributos correspondientes. Interponga esta clase entre las otras dos clases y establezca asociaciones con la multiplicidad adecuada entre la clase de asociación y las otras dos clases.
  • Especifique si los extremos de la asociación se deben ordenar o no; este es el caso cuando los objetos asociados con un objeto en el otro extremo de la asociación tienen un orden que se debe conservar.
  • Si sólo se hace referencia a la clase asociada (o agregada) en la clase actual, considere si se debe anidar la clase. Las ventajas de anidar las clases son una mensajería más rápida y un modelo de diseño más sencillo. Las desventajas son la asignación estática del espacio de la clase anidada, independientemente de si hay instancias de la clase anidada, la falta de identidad de los objetos independiente de la clase inclusiva, o la incapacidad de hacer referencia a instancias de clases anidadas desde fuera de la clase inclusiva.

Las asociaciones y las agregaciones se definen mejor en un diagrama de clase que describa las clases asociadas. El diagrama de clase debe ser propiedad del paquete que contiene las clases asociadas. En la Figura 4 se muestra un ejemplo de un diagrama de clase donde se describen las asociaciones y las agregaciones.

Diagrama descrito en el texto adjunto.

Figura 4: Ejemplo de un diagrama de clase donde se muestran las asociaciones, agregaciones y generalizaciones entre clases

Manejo de asociaciones de suscripción entre clases de análisis

Las asociaciones de suscripción entre clases de análisis se utilizan para identificar las dependencias de sucesos entre clases. En el modelo de diseño, debe manejar estas dependencias de sucesos de forma explícita. Para ello, utilice las infraestructuras de manejador de sucesos disponibles, o bien diseñe y cree su propia infraestructura de manejador de sucesos. En algunos lenguajes de programación como, por ejemplo, Visual Basic, es sencillo: el usuario declara, detecta y maneja los sucesos correspondientes. En otros lenguajes, deberá utilizar una biblioteca adicional de funciones reutilizables para manejar las suscripciones y los sucesos. Si la funcionalidad no se puede adquirir, deberá diseñarla y construirla. Consulte también Técnica: Asociación de suscripción.

Definir la estructura interna

Algunas clases pueden representar abstracciones complejas y pueden tener una estructura compleja. Cuando se modela una clase, el diseñador puede desear representar los elementos de participación interna y sus relaciones, para asegurarse de que el implementador implementa las colaboraciones que se producen dentro de la clase como corresponde.

En UML 2.0, las clases se definen como clases estructuradas, con la posibilidad de tener una estructura interna y puertos. Las clases se pueden descomponer en recopilaciones de componentes conectados que se pueden a descomponer, a su vez. Una clase se puede encapsular forzando las comunicaciones desde el exterior para pasar a través de los puertos que obedecen a las interfaces declaradas.

Cuando encuentre una clase compleja con una estructura compleja, cree un diagrama de estructura compuesta para esa clase. Modele las partes que ejecutarán los roles de ese comportamiento de clase. Establezca cómo se 'conectan' las partes entre sí utilizando conectores. Utilice los puertos con interfaces declaradas si desea permitir que distintos clientes de esa clase accedan a partes específicas del comportamiento que ofrece la clase. Utilice también los puertos para aislar completamente las partes internas de la clase de su entorno.

Para obtener más información sobre este tema y ejemplos de diagramas de estructura compuesta, consulte Concepto: Clase estructurada.

Definir generalizaciones

Las clases se pueden organizar en una jerarquía de generalizaciones para reflejar el comportamiento común y la estructura común. Se puede definir una superclase común, desde la que las subclases pueden heredar el comportamiento y la estructura. La generalización es un convenio de notación que permite definir una estructura y un comportamiento común en un lugar, y utilizarlos donde se encuentre un comportamiento y una estructura repetidos. Consulte el apartado Técnica: Generalización para obtener más información sobre las relaciones de generalización.

Cuando encuentre una generalización, cree una superclase común que contenga los atributos, las asociaciones, las agregaciones y las operaciones comunes. Elimine el comportamiento común de las clases que serán subclases de la superclase común. Defina una relación de generalización desde la subclase a la superclase.

Resolver colisiones de casos de uso

El objetivo de este paso es evitar los conflictos de concurrencia provocados cuando dos o más casos de uso pueden acceder simultáneamente a instancias de la clase de diseño, probablemente de forma incoherente.

Una de las dificultades de continuar "caso de uso " a "caso de uso " a través del proceso de diseño es que dos o más casos de uso pueden intentar invocar operaciones simultáneamente en objetos de diseño de forma que entren en conflicto. En estos casos, los conflictos de concurrencia se deben identificar y resolver explícitamente.

Si se utiliza la mensajería síncrona, la ejecución de una operación bloqueará las posteriores llamadas a los objetos hasta que finalice la operación. La mensajería síncrona implica un orden de "primero en llegar, primero en servirse" para el proceso de mensajes. Esto puede resolver el conflicto de concurrencia, especialmente en aquellos casos en los que todos los mensajes tienen la misma prioridad o en los que cada mensaje se ejecuta dentro de la misma hebra de ejecución. En los casos donde distintas hebras de ejecución (representadas por clases activas) pueden acceder a un objeto, se deben utilizar mecanismos explícitos para impedir o resolver el conflicto de concurrencia.

En sistemas en tiempo real en los que las hebras están representadas por Producto de trabajo: Cápsulas, este problema todavía tiene que resolverse para el acceso concurrente múltiple a objetos pasivos, mientras que las cápsulas proporcionan un mecanismo de cola y fuerzan la semántica de ejecución hasta el final para manejar el acceso concurrente. Una solución recomendada es encapsular los objetos pasivos en cápsulas, lo que evita el problema del acceso concurrente mediante la semántica de la propia cápsula.

Varias hebras de ejecución diferentes pueden invocar simultáneamente distintas operaciones en el mismo objeto sin que se produzca ningún conflicto de concurrencia; por ejemplo, se pueden modificar concurrentemente el nombre y la dirección de un cliente sin ningún conflicto. Es sólo cuando dos hebras de ejecución diferentes intentan modificar la misma propiedad del objeto cuando se produce el conflicto.

Para cada objeto al que pueden acceder concurrentemente varias hebras de ejecución diferentes, identifique las secciones de código que se deben proteger del acceso simultáneo. Al principio de la fase de elaboración, no se podrán identificar segmentos de código específicos; las operaciones que se deben proteger bastarán. Posteriormente, seleccione o diseñe los mecanismos de control de acceso adecuados para impedir los conflictos de acceso simultáneo. Ejemplos de estos mecanismos son la cola de mensajes para serializar el acceso, el uso de semáforos o señales para permitir el acceso a una hebra cada vez, u otras variantes de mecanismos de bloqueo. La elección del mecanismo tiende a depender mucho de la implementación y normalmente varía con el lenguaje de programación y el entorno operativo. Consulte las Directrices específicas del proyecto para obtener ayuda en la selección de mecanismos de concurrencia.

Manejar requisitos no funcionales en general

Las clases de diseño están perfeccionadas para manejar requisitos no funcionales generales. Una entrada importante para este paso son los requisitos no funcionales en una clase de análisis que puede tener ya establecidos sus requisitos especiales y responsabilidades. Estos requisitos se especifican a menudo en términos de qué mecanismos de arquitectura (análisis) se necesitan para realizar la clase; en este paso, la clase se perfecciona para incorporar los mecanismos de diseño correspondientes a estos mecanismos de análisis.

El arquitecto de software identifica y caracteriza los mecanismos de diseño disponibles. Para cada mecanismo de diseño necesario, califique tantas características como sea posible, proporcionando rangos cuando corresponda. Consulte el apartado Tarea: Identificar los mecanismos de diseño, Concepto: Mecanismos de análisis y Concepto: Mecanismos de implementación y diseño para obtener más información sobre los mecanismos de diseño.

Existen varios mecanismos y directrices de diseño generales que se deben tener en cuenta cuando se diseñan las clases, por ejemplo, cómo...

  • utilizar los productos y los componentes existentes
  • adaptarse al lenguaje de programación
  • distribuir objetos
  • conseguir un rendimiento aceptable
  • alcanzar determinados niveles de seguridad
  • manejar los errores
Evaluar los resultados

Compruebe el modelo de diseño en esta fase para verificar que el trabajo sigue la dirección correcta. No es necesario revisar el modelo al detalle, pero debe tener en cuenta las siguientes listas de comprobación:



Más información