UML
UML es un lenguaje común para los analistas de negocios, arquitectos de software y
desarrolladores que se usa para describir, diseñar y documentar procesos de negocios nuevos o
existentes, la estructura y el comportamiento de los artefactos de los sistemas de software.
UML se puede aplicar a diversos dominios de aplicaciones (por ejemplo, banca, finanzas, internet,
aeroespacial, salud, etc). Se puede usar con todos los métodos principales de desarrollo de
software de objetos y componentes y para varias plataformas de implementación.
Los diagramas UML permiten cualquier tipo de extensión de estereotipos podemos poner
cualquier cosa que necesitemos dentro de los <<>>.
Modelo Arquitectónico
La arquitectura del software se ocupa del diseño e implementación de la estructura de alto nivel
del software. Eso es el resultado de reunir un cierto número de elementos arquitectónicos en
algunas formas bien elegidas para satisfacer las principales funciones y requisitos de rendimiento
del sistema, así como algunos otros, no funcionales. Requisitos tales como confiabilidad,
escalabilidad, portabilidad y disponibilidad.
MODELO 4 + 1 P. KRUTCHEN 1995
Para describir una arquitectura de software, usamos un modelo compuesto de múltiples vistas o
perspectivas. El modelo que se propone se compone de cinco vistas principales.
- Vista Lógica: (Tiene el código): Muestra el código, el plano del código. Los diagramas que
se utilizan son: (algunos)
UML
De paquetes
Clases
Objetos
Estructura compuesta
- Vista de procesos: Captura los aspectos de concurrencia y sincronización del diseño. Trata
de aspectos de performance. Se utilizan diagramas de comportamiento, pueden ser
secuenciales o comunicación.
- Vista física: Describe los mapas del software en el hardware y refleja su aspecto
distribuido. Es donde se ejecutan los componentes. Se utilizan diagramas de despliegue.
- Vista de desarrollo: Describe la organización estática del software en su ambiente de
desarrollo. Contiene los elementos en tiempo de ejecución: ejecutables, dll, se usan
diagramas de componentes.
La descripción de una arquitectura, las decisiones tomadas, se puede organizar en torno a
estas cuatro vistas, y luego se ilustra con algunos casos de uso seleccionados o escenarios que
se convierten en una quinta vista.
En los escenarios se muestra que los elementos de las cuatro vistas trabajan juntos sin
problemas mediante el uso de un pequeño conjunto de escenarios, ejemplos de casos de uso
más generales, para los cuales describimos los scripts correspondientes (secuencias de
interacciones entre objetos y entre procesos). Los escenarios son, en cierto sentido, una
abstracción de los requisitos más importantes.
Diagramas de Máquina de estados
Los objetos tienen comportamiento y estado o, en otras palabras, hacen cosas y saben cosas.
Algunos objetos hacen y saben más cosas, o al menos cosas más complicadas, que otros
objetos. Algunos objetos son increíblemente complicados, tan complejos que los
desarrolladores pueden tener dificultades para entenderlos. Para comprender mejor las clases
complejas, particularmente aquellas que actúan de diferentes maneras según su estado, debe
desarrollar uno o más UML. Los diagramas de máquina de estado describen cómo funcionan
sus instancias.
Los diagramas de máquina de estados UML representan los diversos estados en los que puede
estar un objeto y las transiciones entre esos estados. Un estado representa una etapa en el
patrón de comportamiento de un objeto, y como los diagramas de actividad UML es posible
tener estados iniciales y estados finales. Un estado inicial, también llamado estado de
creación, es el estado en el que se encuentra un objeto cuando se crea por primera vez,
mientras que un estado final es aquel en el que no se producen transiciones. Una transición es
una progresión de un estado a otro y se activará por un evento que sea interno o externo al
objeto.
Las transiciones son el resultado de la invocación de un método que causa un cambio
importante en el estado. Entender que no todas las invocaciones de métodos resultarán en
transiciones es importante. Por ejemplo, la invocación de un método getter probablemente no
causaría una transición porque no está cambiando el estado del objeto.
DADO EN CLASE:
UML - Diagramas de estado : Se utilizan para especificar comportamiento de sistemas o partes
de sistemas que son determinísticos.
PSEUDOESTADO:
- Inicial -> Puede haber uno por máquina o estado compuesto
- Final -> Puede haber varios
Las máquinas de estado son fáciles de especificar y de implementar.
El comportamiento se modela mediante el recorrido de un grafo de nodos de estado
interconectados por transiciones. Cuando llego a un estado se ejecuta una acción, cuando hay un
evento que me saca de un estado se ejecuta el exit.
ESTADO
Entry / acción1
Exit / acción2
Do / actividad
On event / accion
Entry – Se realiza al entrar al estado
Exit – Se realiza cuando salgo del estado
Do – Mientras estoy en el estado -> Son interrumpibles
En los diagramas de máquina de estados también existe el estado Historia, este concepto se asocia
con las regiones de los estados compuestos. Mediante el cual, una región mantiene un registro de
la última configuración de estados activa. Permite, si se requiere, volver a esa misma configuración
de estados:
→ La próxima vez que la región este activa.
→ O si hay una transición que termina en su historia.
Un estado compuesto es una región que contiene subestados anidados. Los subestados dentro de
un compuesto pueden ser concurrentes (estado compuesto ortogonal) o secuenciales (estado
compuesto no ortogonal). En UML 2, un estado compuesto se representa igual que un estado
simple, pero con una máquina de estados anidada. Una transición desde fuera de un estado
compuesto puede apuntar a:
→ El estado compuesto (la máquina de estados anidada debe incluir un estado inicial, al
cual pasa el control al entrar al estado compuesto).
→ Un subestado anidado (el control pasa directamente a él).
Formas de implementar maquinas de estado
1- Por código con el uso de switch
Ejemplo: switch(state_actual)
Void event(evento e)
2- Matriz (Tablas)
Estado Inicial Evento Estado Final Accion(Puntero
a función)
3- Patrón State
Diagrama de Componentes
Un diagrama de componentes muestra los elementos de un diseño de un sistema de
software. Permite visualizar la estructura de alto nivel del sistema y el comportamiento del
servicio que estos componentes proporcionan y usan a través de interfaces.
1- Componente: Un fragmento reutilizable de funcionalidad del sistema. Un
componente proporciona y consume el comportamiento a través de interfaces y
puede usar otros componentes.
2- Puerto de la interfaz proporcionada: Representa un grupo de mensajes o llamadas
que implementa un componente y que pueden usar otros componentes o sistemas
externos. Un puerto es una propiedad de un componente que tiene una interfaz
como su tipo.
3- Puerto de la interfaz necesaria: Representa un grupo de mensajes o llamadas que
envía el componente a otros componentes o sistemas externos. El componente
está diseñado para acoplarse a componentes que proporcionan al menos estas
operaciones. El puerto tiene una interfaz como su tipo.
4- Dependencia: Se puede usar para indicar que una interfaz necesaria en un
componente se puede satisfacer mediante una interfaz proporcionada en otro.
5- Parte: Un atributo de un componente, cuyo tipo suele ser otro componente.
6- Parte Assembly: Una conexión entre los puertos de la interfaz requerida de un
elemento y los puertos de la interfaz provista de otro elemento.
7- Delegación: Vincula un puerto a una interfaz de uno de los elementos del
componente indica que los mensajes enviados al componente son administrados
por el elemento o que los mensajes enviados desde el elemento se envían desde el
componente primario.
Patrones de diseño
Si la forma de solucionar un problema se puede extraer, explicar y reutilizar en múltiples ámbitos,
entonces nos encontramos ante un patrón de diseño de software. Un patrón de diseño es una
forma reutilizable de resolver un problema común.
Patrón OBSERVER (Comportamiento):
Intención: Definir una dependencia 1:n de forma que cuando el objeto 1 cambie su estado, los n
objetos sean notificados y se actualicen automáticamente.
Patrón Adapter (Estructura):
Intención: Convertir la interfaz de una clase en otra interfaz esperada por los clientes. Permite que
clases con interfaces incompatibles se comuniquen.
Ventajas:
Se quiere utilizar una clase ya existente y su interfaz no se corresponde con la interfaz que
se necesita. Se quiere envolver código no orientado a objeto con forma de clase.
Patrón Strategy (Comportamiento)
Intención:
- Encapsular algoritmos relacionados en clases y hacerlos
intercambiables.
- Se permite que la selección del algoritmo se haga según el
objeto que se trate.
Venajas:
- Se permite cambiar el algoritmo dinámicamente.
- Se eliminan sentencias condicionales para seleccionar el
algoritmo deseado.
Patrón Template Method ( Comportamiento)
Define el esqueleto de un algoritmo en una operación, delegando algunos pasos a subclases. El
método de plantilla permite a las subclases redefinir ciertos pasos de un algoritmo sin cambiar la
estructura del algoritmo.
El patrón de método de la plantilla es un patrón de diseño de comportamiento que define el
esqueleto de programa de un algoritmo en un método, llamado método de plantilla, el cual difiere
algunos pasos a las subclases. Permite redefinir ciertos pasos seguros de un algoritmo sin cambiar
la estructura del algoritmo.
AbstractClass: define operaciones primitivas abstractas
que las clases concretas definen para implementar los
pasos de un algoritmo. Implementa un método de plantilla
que define el esqueleto de un algoritmo. El método de
plantilla llama operaciones primitivas así como también
operaciones definidas en AbstractClass u otras de otros
objetos.
ConcreteClass: implementa la operación primitiva para
llevar a cabo los pasos específicos que implementan las
subclases del algoritmo
Patrón Proxy (Estructura)
El patrón proxy se utiliza como intermediario para acceder a un objeto, permitiendo controlar el
acceso a él. Para ello obliga que las llamadas a un objeto ocurran indirectamente a través de un
objeto proxy, que actúa como un sustituto del objeto original, delegando luego las llamadas a los
métodos de los objetos respectivos.
Este patrón se debe utilizar cuando:
Se necesite retrasar el coste de crear e inicializar un objeto hasta que es realmente necesario.
Se necesita una referencia a un objeto más flexible o sofisticada que un puntero.
Algunas situaciones comunes de aplicación son:
- Proxy Remoto: Representa un objeto en otro espacio de direcciones. Esto quiere decir que
el proxy será utilizado de manera tal que la conexión con el objeto remoto se realice de
forma controlada sin saturar el servidor.
- Proxy Virtual: Crea objetos costosos por encargo. Cuando se utiliza un software no siempre
se cargan todas las opciones por default. Muchas veces se habilitan ciertos módulos sólo
cuando el usuario decide utilizarlos.
- Proxy de protección]: Controla el acceso a un objeto. Controla derechos de acceso
diferentes.
- Referencia inteligente: Sustituto de un puntero que lleva a cabo operaciones adicionales
cuando se accede a un objeto (ej. Contar el número de referencias, cargar un objeto
persistente en memoria, bloquear el objeto para impedir acceso concurrente, …).
Subject: interfaz o clase abstracta que proporciona un acceso común al objeto real y su
representante (proxy).
Proxy: mantiene una referencia al objeto real. Controla la creación y acceso a las operaciones del
objeto real.
RealSubject: define el objeto real representado por el Proxy.
Cliente: solicita el servicio a través del Proxy y es éste quién se comunica con el RealSubject.
Patrón Factory Method (Creacional)
Utilidad:
Separar la clase que crea los objetos, de la jerarquía de objetos a instanciar.
Un patrón método fábrica retorna una instancia de una de varias posibles clases dependiendo de
los datos provistos. La decisión de cuál retornar es enteramente de la fábrica.
Ventajas:
- Centralización de la creación de objetos
- Facilita la escalabilidad del sistema
- El usuario se abstrae de la instancia a crear
Patrón State (Comportamiento)
La intención del patrón State (Patrón de Comportamiento) es permitir a los objetos
que cambien su comportamiento cuando su estado interno cambia.
El patrón state tiene la misión fundamental de encapsular el comportamiento de un
objeto dependiendo del estado en el que éste se encuentre.
Ejemplo:
Frente a la aparición de nuevos estados simplemente se deben crear nuevas subclases
de estado, redefiniendo los métodos con la implementación que corresponda al
comportamiento para cada una de las operaciones de ese estado.
Abstract Factory (Creacional)
El patrón Abstract Factory nos permite crear, mediante una interfaz, conjuntos o
familias de objetos (denominados productos) que dependen mutuamente y todo esto
sin especificar cuál es el objeto concreto.
Este patrón se puede aplicar cuando:
- Un sistema debe ser independiente de cómo sus objetos son creados.
- Un sistema debe ser ‘configurado’ con una cierta familia de productos.
- Se necesita reforzar la noción de dependencia mutua entre ciertos objetos.
Patrón Composite (Estructura)
Intención: Componer objetos en jerarquías todo-parte y permitir a los clientes tratar
objetos simples y compuestos de manera uniforme.
Patrón Delegation
Utilidad: Cuando se quiere extender y reutilizar la funcionalidad de una clase sin
utilizar la herencia.
Patrón Singleton (Creacional)
El patrón Singleton brinda la posibilidad de controlar el número de instancias (la mayoría, una)
que se permite realizar. También recibimos un punto de acceso global a esta o estas.
Su intención consiste en garantizar que una clase solo tenga una instancia y proporcionar un
punto de acceso global a ella.
El patrón singleton se implementa creando en nuestra clase un método que crea una instancia
del objeto solo si todavía no existe alguna. Para asegurar que la clase no puede ser instanciada
nuevamente se regula el alcance del constructor (con modificadores de acceso como protegido
o privado).
Patrón Visitor
El patrón de diseño Visitor se utiliza para separar la lógica u operaciones que se pueden
realizar sobre una estructura compleja. En ocasiones nos podemos encontrar con
estructuras de datos que requieren realizar operaciones sobre ella, pero estas operaciones
pueden ser muy variadas e incluso se pueden desarrollar nuevas a medida que la
aplicación crece.
A medida que estas operaciones crecen, el número de operaciones que deberá tener la
estructura también crecerá haciendo que administrar la estructura sea muy complejo. Por
esta razón el patrón de diseño Visitor propone la separación de estas operaciones en
clases independientes llamadas Visitantes, las cuales son creadas implementando una
interface común y no requiere modificar la estructura inicial para agregar la operación.
Principios de Diseño a nivel de Paquetes o Componentes
Principios de Cohesión de Paquetes: Las clases en un mismo paquete se usan juntas, la
razón por la cual están en un mismo paquete es por esta razón.
Principios de Acoplamiento de paquetes: Son normas que rigen la interrelación entre
paquetes.
Principios de Cohesión:
- Principio de equivalencia de liberación y reuso (REP): Agrupar las clases reusables
en paquetes que se puedan administrar y controlar. “Las clases en un paquete
deben tener un motivo por el cual estar juntas”
- Principio de Agrupación común (CCP): Las clases que cambian juntas pertenecen a
un mismo paquete.
- Principio de reuso común (CRP): Clases que no son reusadas juntas no deben ser
agrupadas. “No hacer depender a otros de cosas que no necesitan”.
Principios de Acoplamiento:
- Principio de dependencia acíclica (ADP): Las dependencias entre paquetes no
deben formar ciclos.
- Principio de dependencia estable (SDP): La dependencia debe ser en dirección de
la estabilidad.
- Principio de abstracción estable (SAP): Los paquetes estables deben ser paquetes
abstractos.
Solid – Principios de Diseño
Son reglas, principios que debemos favorecer al momento de diseñar. Nos dicen cómo debemos
hacer las cosas pero no cómo tenemos que hacerlas.
El primero de todos es Single Responsibility Principle
Si tengo dos paquetes que acceden a un método de otro paquete por ejemplo que calcula el área
de una figura. Si cambio ese método por uno de los dos paquetes, me va a estar afectando el otro
paquete entonces tengo que compilar los tres paquetes.
AppCalculoArea AppGáfica
Rectángulo
Rectángulo
+Area() GUI
+Dibujar()
Una clase o un elemento de software sólo debe tener una razón por la cual cambiar, asociado a
esto, si una clase tiene más de una responsabilidad tiende a tener baja cohesión y por lo tanto más
de una razón por la cual cambiar.
Una clase debe capturar una sola abstracción y sus responsabilidades asociadas.
Cuando veo una clase entonces tengo que ver cuántos motivos de cambio tiene.
Open-Close Principle
Las clases deben estar abiertas a extensiones, pero cerradas a modificación.
Lo que tengo que tratar de hacer es diseñar una clase A y dejarla cerrada, sin tocar, sin tengo que
agregar algo la extiendo con una sub clase A2. Las clases actuales que ya tengo en el sistema no las
cambio. No se desea cambiar las clases actuales para que no impacte en las otras clases que
dependen de estas clases.
Las entidades del software deben ser abiertas a la extensión y cerradas a la modificación. Donde
abierto nos referimos a que se debe poder cambiar el comportamiento de un módulo ante
cambios en la aplicación o en los requerimientos. Donde cerrado se refiere a que el código del
módulo no debe cambiar debido a que otros módulos lo pueden estar utilizando.
Módulo = clase, paquete, proyecto, etc.
Consejos que derivan de ese principio:
Las variables de las clases deben ser siempre privadas.
No se debe utilizar variables globales. → Si un módulo la cambia, otro módulo la ve, se comienza a
complicar el diseño. El concepto de tipo comienza a perder.
No se debe utilizar RTTI. → Es el típico de caso de qué pasa si aparece un nuevo tipo. El módulo
que tiene el RTTI lo tengo que modificar ante la llegada de un nuevo tipo, si tiene un if o un switch
hay que agregar un caso más.
Liskov Sustitution Principle
Vector
+Add(t:T,pos:int): void
+Get(pos:int): T
+Remove(pos:int): T
+Count():int
Stack
+Push(t:T):void
+Pop(): T
If([Link] > 0 ){
T t = [Link]([Link]() – 1;
[Link]([Link]()-1;
}
[Link](t,[Link]())
Public class Cliente
Public void method(Vector v){
[Link](x,2);
En esta solución hay un problema en cliente, ya que el [Link](x,2) funciona para vectores y stacks
(porque son vectores) pero funciona mal, en un stack se debe insertar en la última posición
agregada, si agrego en 2 y no tengo nada en la posición 0 y 1, deja de ser un stack. Lo mismo si ya
hay elementos y se sobreescribe el de la posición 2.
Lo que morimos en este caso es en un rtti. Diferenciando si es stack o sólo vector.
Lo que dice el principio es que el método Add debe funciona para cualquiera de los dos, para
Vector y Stack. Sin hacer RTTI. Tengo que diseñar de cierta forma que no pase eestos casos. Que
los algoritmos que reciben un padre funcione tanto para el padre y para el hijo.
En este caso se soluciona quitando la herencia. Un stack tiene un vector pero es una
implementación aparte.
El principio de Liskov dice que los algoritmos que utilizan una herencia deben servir para el padre y
los hijos. SI esto no sucede, es porque una jerarquía no es lo más adecuado para ese diseño.
A veces Liskov no lo estoy violando al momento, pero en futuro puede pasar.
Lo que dice el principio es:
Los métodos que usan referencias a clases bases (padres) deben poder utilizar sin saberlo, objeto
de las clases derivadas (hijos). Este principio asegura que un método desconozca las subclases de la
clase referenciada. Por lo tanto no se ve afectado por las derivaciones de la clase.
Este principio se apoya en el concepto de tipo y subtipo.
Interface Segregation Principle
O separation.
Principio de segregación de interfaces.
Un cliente de una clase no tiene porque ver cosas que no le interesa.
Servidor
+a1() A
CB
En este ejemplo B conoce los métodos de A, y C también. Y así sucesivamente. Y si cambiamos uno,
influye a todos.
Una interfaz es una forma en la que yo me comunico con algo. Una interfaz de la clase son los
métodos públicos. Segregar interfaces es hacer que por ejemplo A no conozca los métodos de B.
IA IB IC
Servidor
+a1()
+a2()
+a3() A B C
+b1()
+b2()
+b3()
+c1()
+c2()
(Los sombreados en azul son implementación de interfaz) En esta solución las clases A, B y C se
comunican con Servidor a partir de las interfaces correspondientes , por lo cual sólo ven los
métodos que deben ver. Servidor implementa las tres interfaces.
Otra solución sería utilizando el principio de una sóla responsabilidad, donde se crean tres clases
independientes con los métodos que utiliza cada clase A, B y C. Es decir hacer clases Servidor A,
Servidor B y Servidor C.
Este principio dice que los clientes no deben ser forzados a depender de interfaces que no utilizan
Dependency Inversion Principle
→Módulos de Alto nivel: Lógica de negocio, lo más abstracto lo más cerca del negocio. (Dominio).
Services..
→ Módulos de Bajo Nivel: Un ejemplo de bajo nivel es el acceso a datos, no tiene nada que ver con
las reglas de negocio. Dice cómo está implementado el software por debajo. Tests. UI.
Lo que dice este principio es que los módulos que implementan funcionalidades de alto nivel no
deben depender de módulos que implementan funcionalidades de bajo nivel. Ambos deben
depender de interfaces bien definidas.
Métricas
Definiciones:
Atributo: Una propiedad mesurable, física o abstracta, que comparten todas las
entidades de una misma categoría.
Medida:
Es una indicación cuantitativa de extensión, cantidad, dimensiones, capacidad de
algunos atributos de un proceso o producto.
Métrica:
Una medida cuantitativa del grado en que un sistema, componente o proceso posee
un atributo dado. Es una forma de medir y una escala deifinidas para realizar
mediciones de uno a varios atributos.
Métricas de Diseño
Luego de obtener los valores para las métricas se pueden tomar acciones para mejorar
el diseño, o estimar futuras fases del desarrollo.
Se podrían dividir en tres categorías:
- Orientadas al paquete (assembly)
Ejemplo: Afferent Couplings (Ca); la cual indica el numero de clases fuera del
paquete que dependen de clases del paquete analizado.
- Orientadas a la clase. Ejemplo: Numero de subclases de una clase
- Orientadas a los métodos. Ejemplo: Complejidad Ciclomática de McCabe; indica la
complejidad del método.
La calidad del diseño está muy condicionada por las relaciones existentes entre los
paquetes del sistema.
Bajo acoplamiento y alta cohesión entre paquetes son características de calidad
deseadas en cualquier aplicación bien diseñada.
Una medición objetiva del grado de reusabilidad y mantenibilidad de un sistema puede
ser determinante en la evaluación de la calidad del software.
Las métricas no deben ser el único mecanismo para evaluar la calidad del diseño.
Métricas dadas en clase:
Inestabilidad – I = Ce/(Ce + Ca)
Ca = Acoplamiento aferente, o dependencias entrantes. Numero de tipos fuera del
paquete que dependen de tipos dentro del paquete.
Ce = Acoplamiento eferente, o dependencias salientes. Numero de tipos dentro del
paquete que dependen de tipos fuera del paquete.
Abstracción Fórmula: A = Na / Nc
Na = Cantidad de clases abstractas e interfaces en el paquete.
Nc = Cantidad de clases concretas, abstractas e interfaces del paquete.
Distancia Formula D = | A + I – 1 |
A = Abstracción
I = Inestabilidad
Cuando el valor de D es más cercano a 0 más cerca de la secuencia principal se
encuentra el paquete.
Valores cercanos a 0 (zona de dolor) indican que el paquete es concreto y estable
(responsable). Estos paquetes no son buenos porque no son extensibles y si cambian
impactan en otros.
Valores cercanos a 1 (zona de poca utilidad) indican que el paquete es abstracto e
inestable. El paquete es extensible, pero tiene pocos paquetes que dependan de él.
Cohesión Relacional:
Mide la relación entre clases en un paquete
R = número de relaciones entre clases internas al
paquete (relaciones que no conectan con clases
fuera del paquete)
N = número de clases e interfaces dentro del
paquete
H = (R+1)/N
Buena 1.5 a 4.0.
< 1.5 or > 4.0 no muy buena.
Se cuenta como dependencia cuando hay una
relación (de las que siguen) entre una clase o
interfaz C hacia una clase o interfaz D
Si C tiene un atributo del tipo D.
Si C tiene una operación con un parámetro del tipo D
Si C tiene una asociación, agregación, o composición
con navegabilidad hacia D
C es hijo de D
C implementa la interfaz D
Las asociaciones bidireccionales se cuentan dos
veces.