Índice

Modelo

Un modelo es una representación de la realidad que es obtenido por medio de un proceso de abstracción con el propósito de ayudar a comprender y razonar sobre esa realidad. Un modelo es expresado en un lenguaje.

Abstracción: Se ignoran los detalles y nos centramos en las características esenciales que nos interesan.

Un modelo de software es una descripción abstracta de algún aspecto de los sistemas software, por ejemplo: los requistos, su estructura, el comportamiento, los datos, etc.

UML

A mediados de los años noventa existían muchos métodos de análisis y diseño orientado a objectos, sin embargo, solían ser los mismos conceptos expresados con notaciones diferentes, lo cual provocaba una gran confusión. En 1994, Grady Booch, James Rumbaugh e Ivar Jacobson deciden unificar las notaciones de sus métodos, dando así lugar al Unified Modeling Language (UML).

Posteriormente, el lenguaje fue sometido a un proceso de estandarización promovido por el OMG (Object Management Group), una organización sin fines de lucro que cuida y promueve el uso de tecnología orientada a objetos mediante guías y especificaciones.

Las ventajas de UML son:

    ✅ Eliminar confusión.

    ✅ Proporcionar estabilidad al mercado.

    ✅ Reunir los puntos fuertes de cada método.

    ✅ Añadir mejoras.

UML es un lenguaje para visualizar, especificar, construir y documentar los modelos de un sistema software desde una perspectiva orientada a objetos.

diagrama_secuencia

A la izquierda: ejemplo de diagrama de clase UML. A la derecha: ejemplo de diagrama de secuencia UML.

Los modelos UML son útiles para documentar decisiones de diseño e implementación, para facilitar la comunicación y los razonamientos gracias a su fácil visualización. Incluso pueden servir para la generación de código utilizando ciertas herramientas, pasando de ser modelos para documentar a ser modelos para generar el código.

Modelo vs. Diagrama

Un modelo UML es una especificación de un aspecto del sistema de interés para un grupo de usuarios o desarrolladores.

Un diagrama UML representa una parte de la información del modelo en forma gráfica o textual. En el diagrama de clases, por ejemplo, no aparecen todas las clases, atributos, métodos y asociaciones, sino solo aquellas que se consideran relevantes para el propósito.

Uso del modelado UML

Of the 14 million or so software professionals around the world, many know of the existence of the UML yet only a modest percent use (about 7%) the UML on a daily basis. (Grady Booch, 2002)

Un estudio reciente reveló que un 30% de profesionales usa UML, aunque lo hacen muy poco a lo largo de un desarrollo.

En palabras de Grady Booch:

The UML should be used to reason about alternatives. Put up some diagrams. Throw some use cases against it. Throw away those diagrams then write some code against you best decision. When we began with the UML, we never intended it to become a programming language. We never got the notation for collaborations right. Component and deployment diagrams needed more maturing. The UML metamodel became grossly bloated, because of the drive to model driven development. I think that complexity was an unnecessary mistake. Seriously, you need about 20% of the UML to do 80% of the kind of design you might want to do in a project – agile or not – but use the UML with a very light touch: use the notation to reason about a system, to communicate your intent to others…and then throw away most of your diagrams.

Es decir, solo debemos usar diagramas para aquellas cosas sobre las que no se puede razonar sobre el código.

Modelos UML más usados

Modelado estructural: diagrama de clases

En un modelado estructural se describen los tipos de objetos de un sistema y las relaciones que existen entre ellos.

Un diagrama de clases es una representación gráfica de un modelo estructural.

Exiten diferentes usos para un diagrama de clases: para un modelo conceptual, para un modelo de clases del diseño o de la implementación.

Ejemplos de diagramas de clases:

Clase

Para representar a una clase dibujaremos un rectángulo con 3 divisiones horizontales. En la primera pondremos el nombre de la clase, el la segunda división pondremos sus atributos, por último, en la tercera pondremos sus operaciones. Además, aquellas clases o métodos que sean abstractos irán en cursiva, mientras que las variables y los métodos de clase irán subrayados.

Atributos

Para representar a los atributos de una clase, debemos seguir la siguiente notación:

[visibilidad] nombre [:tipo] ['['multiplicidad']']

Pudiendo ser la visibilidad:

Operaciones

Para representar a las operaciones de una clase, debemos seguir la siguiente notación:

[visibilidad] nombre ['('lista_parámetros_')'][:tipo_retorno]

Pudiendo representarse la visibilidad de la misma forma que con los atributos. Los parámetros que conforman la lista de parámetros irán separados por comas.

Ejemplos de Atributos y Operaciones

Ejemplo 1:

Coche+km: int-matricula~precio+acelerar() : int«abstract»abstractVehículo#pi: float = 3.14Moto

Nota: Al ser Coche y Moto hijas de vehículo, ambas flechas pueden combinarse en una sola, es decir, sale una flecha de cada una de las clases, y se unifican todas en una sola que llega a Vehículo.

Ejemplo 2:

Código:

public class Canguro implements Saltador{
    public int id;
    private String nombre;

    public Canguro (String n){
        nombre = n;
    }

    public void saltar(){
        System.out.println("SALTO");
    }
}


public interface Saltador{
    public void saltar();
}

Diagrama:

«interface»Saltador+saltar()Canguro+id: int-nombre: String+Canguro()+saltar()

Relaciones

PlanDeCurso+añadir(c: Curso)+eliminar(c: Curso)Curso
CuentaAhorroCuentaCuentaCorrienteTextWindowWindowBoxDialog

Nota: Igual que se ha visto antes, dos o más relaciones con un mismo destino pueden juntarse en una sola línea.

Empleado-nif-salarioEmpresa-nombre-cif1..*1

    Las asociaciones son bidireccionales, pero es posible restringir la navegación de     una asociación a una sola dirección añadiendo una flecha, de esta manera se     determina si una clase de un extremo de la asociación tiene conocimiento de la clase     del otro extremo.     La asociación se utiliza en diagramas de diseño e implementación.

class Pedido{...
    private ArrayList<ItemPedido> items;
    private Date fecha;
    private boolean esCompleto;
}

class ItemPedido{...
    private Producto producto;
    private int cantidad;
...}

class Producto{...
    private Money precio;
    private String descripcion;
}
Pedido-fecha: Date-esCompleto: booleanItemPedido-cantidad: intProducto-precio: Money-descripcion: Stringcontiene11..*describe*1
DocumentoCapituloTPVLectorCódigoCajónconsta demeteDinerolee códigos

Una interfaz es una colección de operaciones que especifican los servicios de una clase o componente. Una interfaz permite separar la especificación de la implementación.

«interface»ICollection+add()+remove()+contains()List

        Las interfaces también pueden representarse haciendo uso de la notación

        *lollipop*:

Modelado del comportamiento: Diagramas de Interacción

Diagramas de Interacción: Definiciones

El siguiente diagrama de clases:

ClienteCuentaTarjetaTransacciónReintegroIngreso

Podría dar lugar al siguiente diagrama de objetos:

«:Cuenta»Cuentaid = 3333444saldo = 1000000«:Cliente»Clientedni = 12345678nombre = JJGM«:Tarjeta»TarjetaFecha: 09/2011«:Reintegro»Reintegrocantidad = 100«:Integro»Integrocantidad = 200

Diagramas de Secuencia

Están formados por:

Tipos de mensajes

Además, en UML 2.0 se incorporan los siguientes mensajes en diagramas de comunicación:

La numeración puede ser jerárquica, secuencial, o ninguna.

GUIPedidoControladorPedidosPedidoLineaPedidoItemItemPedidoItemEntregadopreparar()preparar()*preparar()hayStock:=check()[hayStock] eliminar()pedir:=checkPedir()[pedir]<<create>>[hayStock]<<create>>GUIPedidoControladorPedidosPedidoLineaPedidoItemItemPedidoItemEntregado

Foco de control o activación

El foco de control es un rectángulo que representa el tiempo durante el que un objeto está activo ejecutando una acción:

ObjetomensajeObjeto

Diagrama de Colaboración o Comunicación

directionLRGUIPedidoControladorPedidosPedido4. hayStock=check()5. [hayStock] eliminar()LineaPedidoItemItemPedidoItemEntregadoSyntax error in graphmermaid version 8.8.4

Diagrama de Secuencia vs. Diagrama de Colaboración

Sencuencia:

Nota: El <<destroy>> no lo representaremos dada su inexistencia en Java.

Colaboración:

Utilizaremos el diagrama de secuencia cuando nos interese que se vea de forma sencilla el orden de los mensajes. Si lo que deseamos es ver de forma clara los objetos que participan en la interacción y los mensajes que se envían, entonces interesa usar el diagrama de colaboración.

El punto negativo de los diagramas de secuencia es su longitud, mientras que en los de colaboración es la posible dificultad a la hora de encontrar un mensaje por su numeración.

Operadores de Control (NO recomendado)

En los diagramas de secuencia se pueden usar operadores de control para agrupar trozos de la secuencia:

No se recomienda usarlos porque estaríamos programando con UML.

Numeración secuencial

En los diagramas de comunicación, el uso de la numeración secuencial puede provocar confusión, ya que pueden interpretarse de maneras distintas.

Por ejemplo, el siguiente diagrama:

Podría tener las siguientes interpretaciones como diagrama de secuencia:

Nota: Hay un error en esta última imagen, lo correcto es indicar que el flujo de ejecución de m2() abarca también a m3() y m4().

Numeración jerárquica

La solución es usar una numeración jerárquica:

En la numeración jerárquica agrupan en subgrupos los mensajes: 1, 1.1, 1.2, 2, 2.1, 3...

Patrones GRASP

Los patrones GRASP describen los principios básicos de asignación de responsabilidades a clases. Distribuir responsabilidades es la parte más difícil del diseño orientado a objetos, es por ello que esta fase es la que consume la mayor parte del tiempo.

Existen los siguientes patrones GRASP:

Responsabilidades y Métodos

Una responsabilidad se implementa mediante uno o más métodos y puede asignarse a varias clases según la granularidad (tamaño).

Cuando se crea una clase, esa clase tiene un contrato u obligación, que puede dividirse en dos categorías:

Los diagramas de interacción muestran elecciones en la asignación de responsabilidades.

Experto

Es el patrón más importante de todos, ya que ayuda a decidir en qué clase debemos colocar cada método.

¿Cómo se asignan responsabilidades?

Se debe asignar una responsabilidad a la clase que tiene la información necesaria para cumplimentarla.

El objetivo es distribuir las responsabilidades de forma homogénea, evitando crear clases dios (es decir, una clase que reuna todos los métodos).

Los beneficios de seguir el patrón experto son: la conservación de la encapsulación (bajo acoplamiento), y una alta cohesión (clases más ligeras).

Ejemplo 1

Dado el siguiente diagrama de clases, ¿quién es el responsable de conocer el total de una venta?

Venta-fecha-notaLineaVenta-cantidadProducto-descripcion-precio-codigocontiene1..11..*descrita por10..*

Lo intuitivo tal vez sería programar:

public double getTotal(){
    double total = 0.0;
    for(LineaVenta lv : lineasVenta){
        double cant = lv.getCantidad();
        Producto pr = lv.getProducto();
        total += cant*pr.getPrecio();       
    }
    return total;
}

Pero entonces estaríamos violando el patrón experto, representado con un diagrama de secuencia veríamos esto:

GUIRealizarCompraVentaLineaVentaProducto1. getTotal()2. getCantidad()3. getProducto()4. getPrecio()GUIRealizarCompraVentaLineaVentaProducto

Para no violar el experto, buscamos el siguiente diagrama:

GUIRealizarCompraVentaLineaVentaproductogetTotal()*st = getSubtotal()pr= getPrecio()GUIRealizarCompraVentaLineaVentaproducto

Lo cual se corresponde con el siguiente código, que no viola el patrón experto:

class Venta {..

    public double getTotal() {
        double total = 0.0;
        for (LineaVenta lv : lineasVenta)
            total = total + lv.getSubtotal());
        return total;
    }
}

class LineaVenta{..
    private int cantidad;
    private Producto producto;

    public double getSubtotal()
        return cantidad*producto.getPrecio();
}

Ejemplo 2

¿Quién es el responsable de conocer todos los mensajes recibidos entre dos fechas?

LectorCorreoBuzonCarpetaMensaje

Violando el experto, haríamos esto:

para cada mensaje m en cada carpeta c
    si f en rango [f1,f2]
        añadir al conjunto s
return s

Que se corresponde con:

mainLectorCorreoBuzonCarpetaMensajeSetgetMensajes(f1,f2)c = getCarpetas()*m = getmensajes()*f = getFecha()[f en rango] add()mainLectorCorreoBuzonCarpetaMensajeSet

Sin violar el experto haríamos:

mainLectorCorreoBuzonCarpetaMensajegetMensajes(f1,f2)m = getMensajes(f1,f2)*m = getmensajes()*entreFechas(f1,f2)mainLectorCorreoBuzonCarpetaMensaje

Ejemplo 3

¿Quién es el responsable de conocer el número de proyectos de cierto tipo realizados en una universidad en un rango de fechas?

ControladorProyectosnumProyectos()Universidad+getGrupos()CatalogoUniversidades+getUniversidad()GrupoInvestigacion+getproyectos()Proyecto+getFecha()+getTipo()0..*1..*0..*

Con violación del experto haríamos:

public class ControladorProyectos {...
    public int numProyectos(int id, int tipo, Date f1, Date f2) {
        int totalProyectos = 0;
        Universidad uni = CatalogoUniversidades.getUniversidad(id);
        Collection<Grupo> grupos = uni.getGrupos();
        for (Grupo grupo: grupos) {
            Collection<Proyecto> proyectos = grupo.getProyectos();
            for (Proyecto proyecto : proyectos){
                fechaProyecto = proyecto.getFecha();
                if((fechaProyecto.before(f2)) &&(fechaProyecto.after(f1))
                      && (tipo == proyecto.getTipo()))
                    totalProyectos= totalProyectos + 1;
                }
        }
        return totalProyectos;
    }
}

Lo que daría lugar al siguiente diagrama de colaboración:

Que en diagrama de secuencia sería:

GUIResponsableControladorProyectosControladorUniversidadesUniversidadGrupoInvestigacionProyecto1. numProyectos()2. getUniversidad()3. getGrupos()4. getProyectos()5. getFecha()6. getTipo()GUIResponsableControladorProyectosControladorUniversidadesUniversidadGrupoInvestigacionProyecto

Sin violar el experto:

public class ControladorProyectos {...
    public int numProyectos(int id, int tipo, Date f1, Date f2){
        Universidad uni = CatalogoUniversidades.getUniversidad(id);
        return uni.numProyectos(tipo, f1, f2);
    }
}
public class Universidad {...
    private Collection<GrupoInvestigacion> grupos;
    public int numProyectos(int tipo, Date f1, Date f2) {
        int total = 0;
        for (Grupo grupo: grupos)
            total = total + grupo.numProyectos(tipo, f1, f2);
        return total;
    }
}
public class GrupoInvestigacion {...
    private List<Proyecto> proyectos;
    public int numProyectos(int tipo, Date f1, Date f2) {
        int total = 0;
        for (Proyecto proyecto : proyectos){
            if ((proyecto.esTipo(tipo)) &&
                   (proyecto.estaEntre(f1, f2)))
                total = total + 1;
        }
        return total;
    }
}
public class Proyecto {...
    private Date fecha;
    private int tipo;
    public boolean esTipo(int tipo) {
        return (this.tipo == tipo);
    }
    public boolean estaEntre(Date f1, Date f2) {
        return ((fecha.after(f1)) && (fecha.before(f2)));
    }
}

Cuyo diagrama de colaboración es:

Que en diagrama de secuencia sería:

GUIResponsableControladorProyectosControladorUniversidadesUniversidadGrupoInvestigacionProyecto1. numProyectos()2. getUniversidad()3. numProyectos()4. numProyectos()5. estaEntre()6. esTipo()GUIResponsableControladorProyectosControladorUniversidadesUniversidadGrupoInvestigacionProyecto

Creador

El patrón Creador se pregunta cuál es el responsable de crear una nueva instancia de una cierta clase.

Debemos asignar a las clase B la responsabilidad de crear instancias de una clase A si:

Gracias a usar el patrón Creador, conseguiremos un bajo acoplamiento entre nuestras clases.

Ejemplo 1

Venta debería crear las instancias de LineaVenta

Venta-fecha+getTotal()+añadirLineaVenta(pr, cant)LineaVenta-unidades+getSubTotal()+getCantidad()+getProducto()Producto-precio-descripcion+getPrecio()TPV-id+iniciar()+realizarCompra()+realizarPago()-lineasVenta 0..*-producto 10..1 -ventaActual

¿Pero quién debería crear las instancias de Venta? En este caso está claro que sería el TPV.

Ejemplo 2

¿Quién es el responsable de crear una instancia de Puja?

GUISubastasControladorSubastasClienteCatalogoClientesSubastaCatalogoSubastasPuja0..10..*0..* -pujas
realizarPuja(subastaActual, cant)
1.2 realizarPuja(cant)
1.2.1 create
1.2.2 add(puja) 0..*
:GUISubastas
:ControladorSubastas
subastaActual:Subasta
puja:Puja
pujas:Puja

Controlador

El patrón Controlador se encarga de asignar la responsabilidad de manejar eventos externos a uno o más objetos controlador que puede representar, por ejemplo, el sistema o una funcionalidad concreto (caso de uso o historia de uso). Es decir, se pregunta acerca de quién es el encargado de atender los eventos del sistema, generalmente una GUI.

Distingue entre la fachada entre clases de la capa presentación (GUI) y la capa del dominio. Incluye, al menos, un método por cada operación atendida.

Los beneficios de un controlador son:

Patrón arquitectural Layers

En la arquitectura lógica organizada en "capas", cada capa reúne clases relacionadas con un mismo aspecto del sistema, por ejemplo: la interfaz de usuario, dominio o almacenamiento.

Esto tiene dos utilidades:

Arquitecura lógica organizada en 4-capas

Arquitectura de 3 capas (Muy extendida)

Separación Modelo-Vista

Las clases del modelo (dominio) NO deben conocer/acceder a los objetos de la vista (presentación o interfaz).

Las clases del modelo encapsulan la información y el comportamiento relacionado con la lógica del negocio o la aplicación.

Las clases la vista son responsables de la entrada y salida, capturando y manejando los eventos, pero NO encapsulan la lógica del negocio/aplicación.

Las clases de la vista delegan en un objeto controlador para comunicarse con el modelo.

No obstante, una clase de la vista sí puede invocar peticiones get sobre un objeto del dominio que le ha pasado el Controlador o un catálogo.

Por ejemplo:

La capa presentación no debería tener ninguna responsabilidad de la lógica del negocio/aplicación, solo debería ser responsable de las tareas propias de la interfaz de usuario: mostrar ventanas y la captura de eventos. Por lo tanto, debería remitir las responsabilidades propias de la capa del dominio a esa capa, a través de un controlador.

La separación Modelo/Vista:

Volviendo al patrón Controlador...

Las clases de la interfaz delegan en un controlador la realización de las tareas ligadas a cada evento del sistema que procede, normalmente, de la GUI.

El controlador no es más que una fachada entre las clases de la vista y las del dominio. Su función es la de separar la interfaz de usuario de la lógica del dominio o de la aplicación.

El controlador no pertenece a la capa de presentación, sino a la del dominio.

En el siguiente ejemplo no se cumple la separación Modelo/Vista:

Lo cual da lugar a un acoplamiento inadecuado de la capa presentación con la capa del dominio.

Cumpliendo la separación Modelo/Vista:

En este caso sí que hay un acoplamiento adecuado de la capa presentación con la capa del dominio. En swing, esto se corresponde con:

public class JFrameProcesarVenta extends JFrame
    private TPV tpv; //objeto controlador
    public JFrameProcesarVenta (TPV _tpv) {
        this.tpv = _tpv;
    }
    private JButton botonAddItem;
    private JButton getBotonAddItem() {..
        botonAddItem.addActionListener(new ActionListener(){..
        public void actionPerformed(ActionEvent ev) {
            ...
            tpv.introducirItem(cant, producto);
        });
        return botonAddItem;
        }
    }
}

En las diapositivas 97...101 puede encontrarse un ejemplo de ejercicio en el que se deberá modificar el código de una aplicación para no violar ningún patrón GRASP.

Técnicas básicas orientadas a objetos

Bajo Acoplamiento

Para reducir las dependencias entre clases, debemos asignar las responsabilidades de manera que se consiga un bajo acoplamiento. No debemos considerarlo de forma independiente, sino junto a los patrones Experto y Alta Cohesión.

Beneficios:

Se considera un patrón evaluativo, existe acoplamiento entre una clase A y otra B cuando:

Ejemplo simple de evitar el acoplamiento:

Alta Cohesión

La alta cohesión comprueba hasta qué punto están relacionadas las responsabilidades de una clase. Busca asignar una responsabilidad de modo que la cohesión siga siendo alta.

Una clase debería representar una única abstracción bien definida, que tiene un conjuto de métodos muy relacionados funcionalmente.

Se considera baja cohesión:

Ejemplos de cohesión

Polimorfismo

Cuando existe un comportamiento que depende de una variación de tipos, se debe asignar la responsabilidad de cada comportamiento al correspondiente tipo.

No es correcto realizar un análisis de casos basado en el tipo de objetos:

Sino aplicar una jerarquía de herencia. Para ello, definiremos una interfaz que proporcione los métodos que permitan la interacción sin conocer clases concretas:

Las conexiones hacia la interfaz se denominan Patrón Adaptador.

Las interfaces son claves para la pluggability, ya que se añaden fácilmente nuevas alternativas y los clientes no conocen las implementaciones concretas que usan.

Indirección

Para evitar el acoplamiento directo entre dos clases, se asigna la responsabilidad a un intermediario que crea una indirección:

Esta situación se produce en muchos patrones de diseño (Adaptador, Observador, proxy, etc).

Variaciones Protegidas (VP)