Diagrama de clases

Un diagrama de clases describe los tipos de objetos en el sistema y los distintos tipos de relaciones estáticas que existen entre ellos. Los diagramas de clases muestran también las propiedades y operaciones de una clase y de las restricciones que se aplican a la manera en que los objetos están conectados. Para crear nuestros diagramas de clase usaremos el Lenguaje Unificado de Modelado (UML, por sus siglas en inglés, Unified Modeling Language), que es un lenguaje gráfico para visualizar, especificar, construir y documentar un sistema.  El UML utiliza el término característica como un término general que cubre las propiedades y operaciones de una clase.

Propiedades

Las propiedades representan las características estructurales de una clase. Son un único concepto, pero aparecen en dos notaciones distintas: atributos y asociaciones.

Atributos

La notación de atributo describe un propiedad como una línea de texto dentro de la caja de la clase. La forma completa de un atributo es:

visibility name : type multiplicity = default {property-string}

Por ejemplo:

- name : String [1] = "Untitled" {readOnly}

Solo el nombre (name) es necesario.

  • visibility indicará si el atributo es público (+) o privado (-). Cabe destacar que para poder ser modificadas desde el editor de Unity, las variables deben ser públicas (public).
  • name se corresponde al nombre del campo en el lenguaje de programación.
  • type indica qué tipo de objeto puede ocupar el atributo. Es el tipo del campo en el lenguaje de programación.
  • multiplicity  (multiplicidad) se explicará más adelante.
  • default indica el valor por defecto para un objeto creado si el atributo no está especificado durante la creación.
  • {property-string} permite indicar propiedades adicionales para el atributo. {readOnly} indica que no puede ser modificada esta propiedad.

Usaremos como ejemplo la clase Scores de nuestro juego (se mostrará la clase completa con los métodos posteriormente):

Scores(only_fields)

Asociaciones

La otra manera para anotar una propiedad es con una asociación. Una asociación es una línea entre dos clases, dirigida desde la clase de origen hacia la clase objetivo. El nombre de la propiedad va al objetivo final de la asociación, junto con su multiplicidad. El objetivo final de la asociación se une a la clase que indica el tipo de la propiedad.

Para Scores sería:

Scores_asociaciones

Podríamos también mostrar las asociaciones entre los sólidos de Episodix y el objeto padre Solids al que están asociados.

solids_hierarchy_dia

Si abrimos nuestro script Scores.cs en Visual Studio (el cual podemos instalar con Unity) se generará de manera automática el diagrama de clases UML a partir de nuestro código. Para acceder a él vamos a Class View y hacemos clic derecho en Scores. Seleccionamos View Class Diagram y se nos mostrará el diagrama de la clase Score.

Scores

Scores2

El diagrama de la clase Score se nos mostrará de este modo. Podemos ver los detalles de los campos (Fields), los métodos (Methods), etc, en la parte de abajo del programa. A continuación se muestra Scores y sus Fields (los métodos están ocultos, se mostrarán más adelante).

Diagrama_Scores_VS_sin_metodos

También podemos crear un diagrama de una clase y obtener el código para dicha clase. Para ello creamos un diagrama de una clase de prueba a la que llamaremos Test_class. Le añadimos algunas variables (públicas y privadas) y métodos, y hacemos clic derecho en ella. Seleccionamos View Code y nos mostrará el código correspondiente al diagrama creado (lo que hará cada método lo tendremos que programar nosotros obviamente).

Test_class

Test_class2

Multiplicidad

La multiplicidad de una propiedad es una indicación de cuántos objetos podrían rellenar la propiedad. Las comunes son:

  • 1 (indica que debe haber uno)
  • 0..1 (puede haber uno o ningún)
  • * (no hay límite)

En general las multiplicidades son definidas con unos límites inferior y superior. El límite inferior puede ser cualquier número positivo o cero, mientras que el superior será cualquier número positivo o * (ilimitado). Si los límites coinciden sólo se podrá usar ese número, por lo tanto es equivalente 1 y 1..1 o * y 0..*.

En atributos, existen varios términos para referirse a la multiplicidad:

  • Optional : límite inferior de 0.
  • Mandatory : límite inferior de 1 o más.
  • Single-valued : límite superior 1.
  • Multivalued : límite superior mayor que 1, normalmente *.

Si en un sistema multivalued el orden de los objetos tiene sentido es necesario añadir {ordered} al final de la asociación. Si se quieren permitir duplicados añadimos {nonunique}. Para el valor por defecto {unorderer}{unique}. Para unorderer y nonunique se puede usar {bag}.

La multiplicidad por defecto de un atributo es [1]. Sin embargo, no se puede asumir que si la multiplicidad no está indicada esta sea necesariamente [1]. Por tanto, en caso de ser importante es mejor indicar [1] en la multiplicidad.

En el ejemplo anterior de Scores la multiplicidad de todas sus variables es 1, por tanto lo dejamos sin indicar. Puesto que no tenemos ningún ejemplo en nuestro código del juego para la multiplicidad distinta de 1, usaremos el siguiente:

ej_multiplicidad_atributos

Si este fuese el ejemplo en la notación de atributos de nuestra clase, la siguiente imagen muestra cómo sería la notación con asociaciones.

ej_multiplicidad_asociaciones

Asociaciones bidireccionales

Las asociaciones que hemos visto hasta ahora son unidireccionales. Una asociación bidireccional es un par de propiedades que están vinculadas juntas y son inversas.

person_car_bidireccional

En el ejemplo , la clase Car tiene la propiedad owner:Person[1], y la clase Person tiene la propiedad cars:Car[*].

La unión inversa entre ellos implica que si tu sigues ambas propiedades, deberías regresar a un conjunto que contenga tu punto de inicio.

Como alternativa al etiquetado de una asociación por una propiedad, podemos etiquetar la asociación usando un verbo de manera que la asociación pueda ser usada en una frase. Esto es válido y puedes agregar una flecha a la asociación para evitar ambigüedad.

person_car_bidireccional_alternativo

La naturaleza bidireccional de una asociación es obvia añadiendo flechas de navegación en ambos extremos. En el caso del uso de un verbo no existirán estas flechas.

A la hora de implementar una asociación bidireccional en un lenguaje de programación debemos asegurarnos de que ambas propiedades se mantienen sincronizadas. Se usaría el siguiente código para implementar la relación bidireccional.

class Car . . .
public Person Owner {
get {return owner ;}
set {
if (owner != null) _owner .friendCars Q .Remove(this) ;
_owner = value ;
if (owner != null) _owner .friendCars Q .Add(this) ;
}
}
private Person owner ;


class Person . . .
public IList Cars {
get {return ArrayList .ReadOnly(_cars) ;}
}
public void AddCar(Car arg) {
arg .Owner = this ;
}
private IList _cars = new ArrayList Q ;
internal IList friendCars Q {
//should only be used by Car .Owner
return _cars ;
}

Lo principal es permitir a uno de los lados de la asociación -de un único valor a ser posible- controlar la relación. La parte esclava debe pasar sus datos encapsulados a la parte maestra.

Operaciones

Las operaciones son las acciones que la clase conoce para llevar a cabo. Las operaciones más obvias se corresponden con los métodos en la clase. Normalmente no se mostrarán estas operaciones que simplemente manipulan propiedades, porque en general pueden ser deducidas.

La sintaxis completa de UML para operaciones es:

visibility name (parameter-list) : return-type {property-string}

  • visibility : pública (+) o privada (-).
  • name: el nombre, es un string.
  • parameter-list : lista de los parámetros para la operación.
  • return-type : el tipo del valor que devuelve, si hay alguno.
  • property-string : indica los valores de la propiedad que se aplican a la operación dada.

Los parámetros en la lista de parámetros son anotados de una manera similar a los atributos. La forma es:

direction name : type = default value

  • nametypedefault value son igual que para los atributos.
  • direction indica si los parámetros son de entrada (in), salida (out) o ambos (inout). Si no se indica ninguno, se asume que es in.

UML define una query (consulta) como una operación que obtiene un valor de una clase sin cambiar el estado del sistema. Puedes marcar tal operación con la property-string {query}. Aquellas operaciones que modifican el estado del sistema son los modifiers (modificadores), también llamados comandos. La diferencia entre las consultas y los modificadores es si cambian un estado observable (que se puede percibir desde fuera). Se puede cambiar el orden de las consultas sin modificar el comportamiento del sistema. Como convención se suele tratar de escribir las operaciones que son modificadores no devuelvan ningún valor, de manera que podemos confiar en que las que sí los devuelven sean consultas: Principio de separación Command-Query.

Otros términos usados son getting method, que devuelve un valor de un campo, y setting method, que pone un valor en un campo. Desde fuera no se podrá saber si una consulta o un modificador son uno u otro método, ese conocimiento es enteramente interno de la clase.

Para distinguirlos sabemos que una operación es invocada en un objeto, mientras que un método está en el cuerpo del proceso.

Ejemplo para el caso de la clase Scores:

Diagrama_Scores_VS

Generalización

Un ejemplo típico de generalización implica los clientes (Customer) personales (Personal Customer) y corporativos (Corporate Customer) de un negocio. Tienen diferencias, pero también similitudes entre ellos. Las similitudes pueden ser colocadas en una clase general Customer, con Personal Customer y Corporate Customer como subtipos.

Conceptualmente podemos decir que Corporate Customer es un subtipo de Customer si todas las instancias de Corporate Customer son también instancias de Customer. Corporate Customer es entonces un tipo especial de Customer. El concepto clave es que todo lo que digamos de un Customer -asociaciones, atributos, operaciones- es verdades para un Corporate Customer.

La interpretación de esto, desde una perspectiva de software, es la herencia: Corporate Customer es una subclase de Customer. En los principales lenguajes orientados a objetos (OO), la subclase hereda todas las características de la superclase y puede anular anular cualquier método de la superclase.

Un principio importante de usar la herencia efectivamente es la sustituibilidad. Debemos poder sustituir un Corporate Customer dentro de cualquier código que requiera un Customer y todo debería funcionar correctamente. El código creado para un Customer debería ser válido para cualquier subtipo de Customer.

En resumen, la generalización representa relaciones entre clases en lugar de entre instancias de las clases (asociaciones).

En el caso de la clase Scores, está relacionada con la clase MonoBehaviour, que es la clase de la que derivan todos los scripts en Unity. Funciones como Start( ) o Update( ) forman parte de la clase MonoBehaviour: Update( ) es llamada cada frame, mientras que Start( ) es llamada en el frame en el que un script es activado justo antes de que Update( ) sea llamado por primera vez. Además, cuando usamos C# tenemos que derivar explícitamente de Monobehaviour ( public class Scores : MonoBehaviour ).

generalización

Recordemos que el código de Scores.cs es el siguiente:

using UnityEngine;
using System.Collections;

public class Scores : MonoBehaviour {

public float rightAnswers; // Mostrara el numero de respuestas correctas en inspector.
float rA; // Numero de respuestas correctas con las que trabajaran las funciones.
public float totalAnswers; // Mostrara el numero de respuestas totales en inspector.
float tA; // Numero de respuestas totales con las que trabajaran las funciones.
public float totalQuestions; // Numero de preguntas totales. Se establecera su valor en Inpector.
public float results; // Almacenara el porcentaje de acierto de las respuestas.

bool answer; // Almacenara la respuesta a una pregunta (Si o No)

// Recogera la respuesta del usuario a una pregunta.
//
public void CollectAnswer(bool ans)
{
answer = ans;
}

// Comprobara si el usuario ha acertado con su respuesta a la pregunta, añadira el resultado y lo almacenara con el resto.
// Si era la ultima pregunta del test, calculara el porcentaje de acierto del usuario a todas las respuestas.
//
public void AddScore (GameObject solid)
{
rightAnswers = rA;
totalAnswers = tA;

if (solid.activeInHierarchy==answer) // Comprueba si el objeto esta activo en Hierarchy.
{
++rA;
rightAnswers = rA;
++tA;
totalAnswers = tA;
} else
{
++tA;
totalAnswers = tA;
}

if (totalQuestions == tA) {
results = (rA / tA) * 100;
rA = 0;
tA = 0;
}
}
}

En el caso de los objetos en Unity, todos a los que puede hacer referencia pertenecen a la clase Object. Los métodos y atributos de los objetos en Unity serán los correspondientes a los de su clase Object y a los de sus subclases asociadas a cada objeto en concreto (dependiendo de qué componentes tenga asociados), que veremos a continuación. Dos clase derivan de Object: 1) clase GameObject, clase base para todas las entidades en las escenas en Unity, y 2) clase Component, clase base para todo lo unido a GameObjects. Nótese que nuestro código nunca creará directamente un Component. En su lugar, escribimos un script y añadimos el script a un GameObject. Todas las clases de cada uno de los distintos componentes en Unity derivarán de la clase Component.

Ya hemos dicho que los scripts en Unity deben derivar de MonoBehaviour, pero éste a su vez deriva de la clase Behaviour, que son los Components que pueden ser activados o desactivados. La clase Behaviour deriva a su vez de la clase Component que hemos comentado previamente. MonoBehaviour le proporciona a Component los eventos y llamadas a métodos necesarios por el motor. Así mismo, las variables globales de dichas clases, son mostradas por el Inspector y pueden modificarse en tiempo real mientras se previsualiza el juego.

En la siguiente imagen se muestra un diagrama de la relación entre todas las clases anteriormente mencionadas (abajo de todo se muestran los 6 scripts creados hasta ahora para el proyecto Episodix):

esquema_global_clases

Un comentario en “Diagrama de clases

Deja un comentario