2a edición
Utilización de UML Otros libros de interés:
• Esta segunda versión ha sido completamente actualizada para UML2.0 al tiempo que se han hecho mejoras en el texto para facilitar su comprensión.
Craig Larman: UML y patrones. Madrid, Pearson Prentice Hall, 2003. ISBN 978-84-205-3438-1
• Diseñado para estudiantes y profesionales que están aprendiendo sobre el diseño y desarrollo basado en objetos y componentes, el libro apoya un enfoque pragmático y abierto para la ingeniería del software en la vida real. Sitúa a UML dentro del contexto de la disciplina de ingeniería del software como un todo, aportando a los lectores las mejores prácticas en diseño y desarrollo de software.
a
2
edición
Perdita Stevens Rob Pooley
Stevens
OBJECT TECHNOLOGY SERIES
Pooley ADDISON-WESLEY
Grady Booch, James Rumbaugh e Ivar Jacobson: El Lenguaje Unificado de Modelado, 2.º Edición. Madrid, Pearson Addison Wesley, 2006. ISBN 978-84-782-9076-5
www.pearsoneducacion.com
en Ingeniería del Software con Objetos y Componentes
• Utilización de UML es un libro que surge como texto para estudiantes de cursos superiores de Informática, Ingeniería del Software y otros cursos similares, ante la inexistencia de un libro de texto adecuado; todo el material existente de UML, estaba dirigido principalmente hacia desarrolladores experimentados, pero no hacia estudiantes ni profesionales que estén aprendiendo sobre el diseño y desarrollo basado en objetos y componentes.
Utilización de UML
en Ingeniería del Software con Objetos y Componentes
SERIES EDITORS
Utilización de UML en Ingeniería del Software con Objetos y Componentes
Utilización de UML en Ingeniería del Software con Objetos y Componentes 2.a edición Perdita STEVENS Universidad de Edimburgo
con Rob Pooley Universidad Heriot-Watt
Traducción Marta Fernández Alarcón Rubén González Crespo Facultad de Informática Universidad Pontificia de Salamanca
Coordinación general y Revisión técnica Luis Joyanes Aguilar Facultad de Informática Universidad Pontificia de Salamanca (Campus de Madrid)
Madrid • México • Santafé de Bogotá • Buenos Aires • Caracas • Lima • Montevideo San Juan • San José • Santiago • São Paulo • White Plains
Datos de catalogación bibliográfica Utilización de UML en Ingeniería del Software con Objetos y Componentes Perdita Stevens y Rob Pooley 2.ª Edición PEARSON EDUCACIÓN, S.A., Madrid, 2007 ISBN: 978-84-7829-086-4 Materia: Informática, 004 Formato: 195 250 mm
Páginas: 304
Queda prohibida, salvo excepción prevista en la Ley, cualquier forma de reproducción, distribución, comunicación pública y transformación de esta obra sin contar con autorización de los titulares de propiedad intelectual. La infracción de los derechos mencionados puede ser constitutiva de delito contra la propiedad intelectual (arts. 270 y sgts. Código Penal). DERECHOS RESERVADOS © 2007 por PEARSON EDUCACIÓN, S. A. Ribera del Loira, 28 28042 Madrid (España) Utilización de UML en Ingeniería del Software con Objetos y Componentes Perdita Stevens y Rob Pooley ISBN: 978-84-7829-086-4 Depósito Legal: M. This translation of USING UML, SOFTWARE ENGINEERING WITH OBJECTS AND COMPONENTS 02 Edition is Published by arrangement with Pearson Education Limited, United Kingdom. © Pearson Education Limited 1999, 2006 Equipo editorial: Editor: Miguel Martín-Romo Técnico editorial: Marta Caicoya Equipo de producción: Director: José A. Clares Técnico: José A. Hernán Diseño de cubierta: Equipo de diseño de PEARSON EDUCACIÓN, S. A. Composición: JOSUR TRATAMIENTOS DE TEXTOS, S.L. Impreso por: IMPRESO EN ESPAÑA - PRINTED IN SPAIN Este libro ha sido impreso con papel y tintas ecológico
CONTENIDO
Parte I
Prefacio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
xvii
Agradecimientos de la primera edición . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
xxiii
Conceptos básicos Capítulo 1. 1.1 1.2
1.3
1.4
Ingeniería del software con componentes . . . . . . . . . . . .
3
¿Qué es un buen sistema? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ¿Tenemos buenos sistemas? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
3 4
1.2.1 1.2.2 1.2.3
Problemas... . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ...incluso fallos drásticos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Promesas, promesas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
4 5 7
¿Cómo son los buenos sistemas? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
7
1.3.1 Encapsulación: débil acoplamiento . . . . . . . . . . . . . . . . . . . . . . . . 1.3.2 Abstracción: fuerte cohesión . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.3.3 Arquitectura y componentes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1.3.4 Diseño basado en componentes: conectividad . . . . . . . . . . . . . . .
7 12 13 14
¿Cómo se construye un buen sistema? . . . . . . . . . . . . . . . . . . . . . . . . . . . .
15
Capítulo 2.
Conceptos de objetos . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
17
¿Qué es un objeto? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
17
2.1.1 2.1.2 2.1.3 2.1.4
Ejemplo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Mensajes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Clases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
19 19 20 21
2.2 ¿Cómo relacionar esto con los objetivos del capítulo anterior? . . . . . . . . .
23
2.2.1 ¿Qué tienen que ver los objetos con los componentes? . . . . . . . .
25
2.3 Herencia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2.4 Polimorfismo y ligadura dinámica . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
25 27
2.1
vi
CONTENIDO
Capítulo 3. 3.1
3.2 3.3 3.4 3.5
Estudio de un caso introductorio . . . . . . . . . . . . . . . . . . . .
31
El problema . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
31
3.1.1 3.1.2
Clarificación de los requisitos . . . . . . . . . . . . . . . . . . . . . . . . . . . . Modelo de casos de uso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
31 33
Alcance e iteraciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Identificación de clases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Relaciones entre clases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . El sistema en acción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
35 37 39 42
Panel 3.1
Diseño por contrato 1 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
43
Cambios en el sistema: diagramas de estado . . . . . . . . . . . . . . . . . Trabajo adicional . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
46 46
Panel 3.2
Persistencia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
47
Capítulo 4.
El proceso de desarrollo . . . . . . . . . . . . . . . . . . . . . . . . . . .
49
Definición de términos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
49
4.1.1 4.1.2
Modelos y lenguajes de modelado . . . . . . . . . . . . . . . . . . . . . . . . Proceso y calidad . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
50 52
El proceso de desarrollo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
52
4.2.1 4.2.2
¿Una metodología unificada? . . . . . . . . . . . . . . . . . . . . . . . . . . . . Procesos a utilizar con UML . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
54 55
Sistema, diseño, modelo, diagrama . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
57
4.3.1
58
3.5.1 3.5.2
4.1
4.2
4.3
Parte II
Los usos de los modelos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
El lenguaje unificado de modelado Capítulo 5. 5.1
5.2
5.3
5.4
Fundamentos de los modelos de clases . . . . . . . . . . . . . .
63
Identificar objetos y clases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
63
5.1.1 ¿Qué hace que un modelo de clases sea bueno? . . . . . . . . . . . . . . 5.1.2 Cómo construir un buen modelo de clases . . . . . . . . . . . . . . . . . . 5.1.3 ¿Qué son las clases? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.1.4 Objetos del mundo real frente a su representación en el sistema .
64 64 67 67
Asociaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
68
5.2.1
Multiplicidades . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
70
Atributos y operaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
71
5.3.1 5.3.2
Operaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Atributos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
71 72
Generalización . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
73
CONTENIDO
vii
Panel 5.1 Diseño por contrato 2: posibilidad de sustitución . . . . . . . . . . . . . .
74
5.4.1 Utilización del lenguaje para comprobar si existe una generalización . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.4.2 Implementación de la generalización: herencia . . . . . . . . . . . . . .
76 76
El modelo de clases durante el desarrollo . . . . . . . . . . . . . . . . . . . . . . . . . Tarjetas CRC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
77 77
5.6.1 Creación de tarjetas CRC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.6.2 Utilización de tarjetas CRC en el desarrollo del diseño . . . . . . . . 5.6.3 Ejemplo de tarjeta CRC . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 5.6.4 Refactorización . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
78 78 79 80
5.5 5.6
Capítulo 6. 6.1
Más sobre los modelos de clases . . . . . . . . . . . . . . . . . . .
83
Más sobre asociaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
83
6.1.1 Agregación y composición . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.1.2 Roles . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.1.3 Navegabilidad . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.1.4 Asociaciones calificadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.1.5 Asociaciones derivadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 6.1.6 Restricciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
83 85 86 88 89 90
Panel 6.1
Lenguaje de Restricción de Objetos (Object Constarint Language) .
92
Clases asociación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
93
Más sobre clases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
95
6.1.7 6.2
Panel 6.2
Estereotipos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
95
Interfaces . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Clases abstractas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
96 98
Propiedades y valores etiquetados . . . . . . . . . . . . . . . . . . . . . . . . . .
99
Clases parametrizadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Dependencia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Componentes y paquetes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Visibilidad, protección . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
100 101 102 102
6.2.1 6.2.2 Panel 6.3 6.3 6.4 6.5 6.6
Capítulo 7. 7.1 7.2 7.3 7.4
7.5
Fundamentos de los modelos de casos de uso . . . . . . . .
103
Actores en detalle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Casos de uso en detalle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Límite del sistema . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Utilización de los casos de uso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
105 107 108 109
7.4.1 Casos de uso para la captura de requisitos . . . . . . . . . . . . . . . . . . 7.4.2 Casos de uso a través del desarrollo . . . . . . . . . . . . . . . . . . . . . . .
109 110
Posibles problemas con los casos de uso . . . . . . . . . . . . . . . . . . . . . . . . . .
112
Panel 7.1
¿Desarrollo dirigido por casos de uso? . . . . . . . . . . . . . . . . . . . . . . .
113
viii
CONTENIDO
Capítulo 8.
Más sobre los modelos de casos de uso . . . . . . . . . . . . .
115
Relaciones entre casos de uso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
115
8.1.1 Casos de uso para la reutilización: <
> . . . . . . . . . . . 8.1.2 Componentes y casos de uso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 8.1.3 Separación del comportamiento variante: <<extend>> . . . . . . .
116 118 119
Generalizaciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Actores y clases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
121 122
8.3.1
Notación: actores como clases . . . . . . . . . . . . . . . . . . . . . . . . . . .
122
Fundamentos de los diagramas de interacción . . . . . . . .
125
Colaboraciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Diagramas de comunicación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Diagramas de secuencia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
126 127 129
Panel 9.1 ¿Dónde deben colocarse los mensajes? Ley de Demeter . . . . . . . . .
130
9.4
Características más avanzadas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
132
9.4.1 9.4.2 9.4.3
Mensajes desde un objeto a sí mismo . . . . . . . . . . . . . . . . . . . . . . Valores de retorno . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Creación y borrado de objetos . . . . . . . . . . . . . . . . . . . . . . . . . . . .
132 133 134
9.5 Diagramas de interacción para otros propósitos . . . . . . . . . . . . . . . . . . . . .
136
9.5.1 Muestra de cómo una clase proporciona una operación . . . . . . . . 9.5.2 Descripción de cómo funciona un patrón de diseño . . . . . . . . . . . 9.5.3 Descripción de cómo utilizar un componente . . . . . . . . . . . . . . . .
136 136 136
8.1
8.2 8.3
Capítulo 9. 9.1 9.2 9.3
Capítulo 10.
Más sobre diagramas de interacción . . . . . . . . . . . . . . . .
137
10.1 Más allá de las secuencias simples de mensajes . . . . . . . . . . . . . . . . . . .
137
10.1.1 10.1.2 10.2
Comportamiento condicional . . . . . . . . . . . . . . . . . . . . . . . . . . . Iteración . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
137 139
Concurrencia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
141
10.2.1
Modelización de varios hilos de control . . . . . . . . . . . . . . . . . .
142
Fundamentos de los diagramas de estado y actividad . . .
147
Diagramas de estado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
148
11.1.1 11.1.2 11.1.3 11.1.4 11.1.5
Mensajes inesperados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Nivel de abstracción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Estados, transiciones, eventos . . . . . . . . . . . . . . . . . . . . . . . . . . Acciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Guardas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
149 150 151 151 153
Diseño de clases con diagramas de estado . . . . . . . . . . . . . . . . . . .
155
Diagramas de actividad . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
157
Capítulo 11. 11.1
Panel 11.1 11.2
CONTENIDO
Capítulo 12.
ix
Más sobre los diagramas de estado . . . . . . . . . . . . . . . .
161
12.1 12.2 12.3
Otros tipos de eventos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Otros tipos de acciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Análisis de los estados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
161 162 163
12.4
Concurrencia dentro de los estados . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
165
Capítulo 13. 13.1 13.2
Diagramas de arquitectura e implementación . . . . . . . .
167
Diagramas de estructura de componentes . . . . . . . . . . . . . . . . . . . . . . . . Modelo de despliegue . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
167 169
13.2.1 La capa física . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13.2.2 Despliegue del software sobre el hardware . . . . . . . . . . . . . . . .
169 170
Panel 13.1
El Modelo de Despliegue en el proyecto . . . . . . . . . . . . . . . . . . . .
170
Capítulo 14.
Paquetes y modelos . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
173
Paquetes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
173
14.1.1
Control del espacio de nombres . . . . . . . . . . . . . . . . . . . . . . . . .
174
Modelos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
176
14.1 14.2
Parte III Estudio de casos Capítulo 15. 15.1
15.2
Administración del CS4 . . . . . . . . . . . . . . . . . . . . . . . . . . .
181
El estudio del caso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
181
15.1.1 15.1.2 15.1.3 15.1.4
Modelo de clases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Dinámicas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Diagramas de estado . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Diagramas de actividad . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
185 186 187 187
Discusión . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
188
Capítulo 16. 16.1
16.2 16.3 16.4
Juegos de mesa . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
189
Alcance y análisis preliminar . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
190
16.1.1 16.1.2
Tres en Raya . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Ajedrez . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
190 191
Interacción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . De vuelta al marco de trabajo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Estados . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
195 197 199
Capítulo 17. 17.1 17.2
Simulación de eventos discretos . . . . . . . . . . . . . . . . . . .
203
Requisitos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
203
17.1.1
Descripción más detallada . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
204
Esquema general del modelo de clases . . . . . . . . . . . . . . . . . . . . . . . . . .
206
x
CONTENIDO
17.3
Casos de uso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
208
17.3.1 Resumen de crear modelo . . . . . . . . . . . . . . . . . . . . . . . . . . 17.3.2 Resumen de observar comportamiento . . . . . . . . . . . . . 17.3.3 Resumen de recoger estadísticas . . . . . . . . . . . . . . . . 17.3.4 Resumen de ejecutar un modelo . . . . . . . . . . . . . . . . . . .
209 209 209 209
17.4 Mecanismos estándar para la simulación basada en el proceso . . . . . . . . 17.5 Asociaciones y navegabilidad . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17.6 Clases en detalle . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
210 211 214
Clase Planificador . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Clase EntidadActiva . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Clase EntidadPasiva . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Clase Recurso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
215 215 217 217
17.7 Clase Informe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17.8 Clase Estadística . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
220 220
17.8.1 Clase Promedio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
221
Construcción de un modelo de simulación completo . . . . . . . . . . . . . . . .
222
17.10 La cena de los filósofos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
222
17.6.1 17.6.2 17.6.3 17.6.2
17.9
Parte IV Hacia la práctica Capítulo 18.
Reutilización: componentes y patrones . . . . . . . . . . . . .
227
Viabilidad de la reutilización . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
227
18.1.1
¿Qué se puede reutilizar, y cómo? . . . . . . . . . . . . . . . . . . . . . . .
228
Panel 18.1 ¿Qué es en realidad un componente? ¡Discutible! . . . . . . . . . . . . .
229
18.1.2 ¿Por qué reutilizar? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 18.1.3 ¿Por qué es difícil reutilizar? . . . . . . . . . . . . . . . . . . . . . . . . . . . 18.1.4 ¿Qué componentes son verdaderamente reutilizables? . . . . . . . 18.1.5 ¿Qué pasa con el desarrollo de componentes propios? . . . . . . . 18.1.6 ¿Qué diferencias introduce la orientación a objetos? . . . . . . . .
230 231 232 233 234
Patrones de diseño . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
235
18.2.1 18.2.2
Ejemplo: Fachada . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . UML y patrones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
237 238
Marcos de trabajo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
239
18.1
18.2
18.3
Capítulo 19. 19.1 19.2
Calidad del producto: verificación, validación y prueba .
241
Revisión de la calidad . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ¿Cómo se alcanza la alta calidad? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
241 242
19.2.1 19.2.2 19.2.3
242 242 243
Centrada en el producto . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Centrada en el proceso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Más información . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
CONTENIDO
19.3 19.4 19.5
19.6
Verificación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Validación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
243 244
19.4.1
Usabilidad . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
244
Prueba . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
245
19.5.1 Selección y ejecución de las pruebas . . . . . . . . . . . . . . . . . . . . . 19.5.2 Problemas particulares de la orientación a objetos . . . . . . . . . . 19.5.3 ¿Por qué las pruebas se hacen de forma incorrecta tan a menudo? . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
246 248 250
Revisiones e inspecciones . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
251
19.6.1
Problemas de las RTF . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
252
Calidad del proceso: gestión, equipos, GC . . . . . . . . . . .
255
Gestión . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
255
20.1.1 Gestión de proyectos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20.1.2 Estimación de un proyecto iterativo . . . . . . . . . . . . . . . . . . . . . . 20.1.3 Gestión del desarrollo basado en componentes . . . . . . . . . . . . . 20.1.4 Gestión del personal . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
256 257 258 259
Equipos . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Liderazgo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
260 261
20.3.1
Reforma del proceso de desarrollo . . . . . . . . . . . . . . . . . . . . . .
262
Garantía de calidad . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
262
20.4.1 Garantía de calidad en proyectos iterativos . . . . . . . . . . . . . . . . 20.4.2 Gestión de la Calidad Total . . . . . . . . . . . . . . . . . . . . . . . . . . . .
264 264
Capítulo 20. 20.1
20.2 20.3 20.4
xi
Panel 20.1
Garantía de calidad: el caso contrario . . . . . . . . . . . . . . . . . . . . . . .
265
Más información . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
266
Bibliografía . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
269
Índice analítico . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
273
Esquemas . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
279
20.5
LISTA DE FIGURAS
Dependencias entre capítulo, ¡no está exactamente en UML! . . . . . . . . . . . . . . . . . 3.1 3.2 3.3 3.4 3.5 3.6 3.7
xviii
Diagrama de casos de uso para la biblioteca. . . . . . . . . . . . . . . . . . . . . . . . . . Diagrama de casos de uso para la primera iteración . . . . . . . . . . . . . . . . . . . . Nombres y locuciones nominales en la biblioteca . . . . . . . . . . . . . . . . . . . . . Modelo de clases inicial de la biblioteca . . . . . . . . . . . . . . . . . . . . . . . . . . . . Modelo de clases de la biblioteca revisado . . . . . . . . . . . . . . . . . . . . . . . . . . . Interacción mostrada en un diagrama de secuencia . . . . . . . . . . . . . . . . . . . . Diagrama de estado para la clase Libro . . . . . . . . . . . . . . . . . . . . . . . . . . . .
34 36 37 40 41 43 46
4.1 Un proceso en cascada sencillo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4.2 Un proceso en espiral sencillo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
52 54
5.1 5.2 5.3 5.4 5.5
Un modelo de clases muy sencillo . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Asociación sencilla entre clases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Un modelo de clases sencillo, con atributo y operación . . . . . . . . . . . . . . . . . Una generalización sencilla . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Ejemplo de tarjetas CRC para la biblioteca . . . . . . . . . . . . . . . . . . . . . . . . . .
63 69 72 75 80
6.1 6.2 6.3 6.4 6.5 6.6 6.7 6.8 6.9 6.10 6.11 6.12 6.13 6.14 6.15 6.16
Una agregación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Una composición . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Una asociación con nombres de rol . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Asociación sin navegación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Asociación con navegación en un solo sentido . . . . . . . . . . . . . . . . . . . . . . . . Asociación sencilla entre Cuadrado y Tablero . . . . . . . . . . . . . . . . . . . . . Asociación calificada . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Composición calificada . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Una asociación derivada . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Un diagrama bajo restricción . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Utilizando una restricción xor . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Una clase de asociación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Evitando una clase de asociación . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Una interfaz y su utilización . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Notación más escasa para la dependencia de interfaz . . . . . . . . . . . . . . . . . . . Una clase parametrizada y su utilización . . . . . . . . . . . . . . . . . . . . . . . . . . . .
84 85 86 86 86 88 88 89 90 91 92 94 94 96 98 100
xiv
LISTA DE FIGURAS
7.1 7.2 7.3 7.4
Diagrama de casos de uso para la biblioteca (diagrama 1) . . . . . . . . . . . . . . . Asociación simple entre clases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Comunicación sencilla entre un actor y un caso de uso (diagrama 2) . . . . . . Diagrama de casos de uso para la biblioteca . . . . . . . . . . . . . . . . . . . . . . . . . .
104 104 105 109
8.1 8.2 8.3 8.4 8.5 8.6
Reutilización de casos de uso: <> . . . . . . . . . . . . . . . . . . . . . . . Un diagrama de casos de uso que describe un componente . . . . . . . . . . . . . . <<extend>> . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . <<extend>> con punto de extensión . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Generalización entre actores . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Estos dos símbolos significan lo mismo . . . . . . . . . . . . . . . . . . . . . . . . . . . .
116 119 120 120 121 123
Una colaboración sencilla, sin interacciones . . . . . . . . . . . . . . . . . . . . . . . . . Interacción mostrada en un diagrama de comunicación . . . . . . . . . . . . . . . . . Interacción mostrada en un diagrama de secuencia . . . . . . . . . . . . . . . . . . . . Mal diseño, se rompe la norma de Demeter . . . . . . . . . . . . . . . . . . . . . . . . . . Interacción mostrada en un diagrama de secuencia, con características opcionales . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 9.6 Diagrama de secuencia: creación y borrado de objetos, y utilización del valor de retorno . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
126 128 130 131
9.1 9.2 9.3 9.4 9.5
133 134
10.1 10.2 10.3 10.4 10.5
Comportamiento opcional en un diagrama de secuencia . . . . . . . . . . . . . . . . Comportamientos alternativos en un diagrama de secuencia . . . . . . . . . . . . . Comportamiento iterativo en un diagrama de secuencia . . . . . . . . . . . . . . . . Variantes del envío de un mensaje en diagramas de secuencia . . . . . . . . . . . . Paso de mensajes asíncrono . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
138 139 140 143 145
11.1 11.2 11.3 11.4 11.5 11.6 11.7
Diagrama de estado de la clase Copia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Diagrama de estado de la clase Copia, con acciones . . . . . . . . . . . . . . . . . . Diagrama de estado de la clase Copia, con acciones de entrada . . . . . . . . Diagrama de estado de la clase Copia, con acciones de salida . . . . . . . . Varias acciones en un diagrama . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Diagrama de estado para la clase Libro . . . . . . . . . . . . . . . . . . . . . . . . . . . . Diagrama de actividad de la biblioteca a nivel de negocio . . . . . . . . . . . . . . .
148 151 152 152 153 154 159
12.1 Diagrama de estado para la clase Promedio: ¡no es un buen estilo! . . . . . . 12.2 Diagrama de estado para la clase Cliente . . . . . . . . . . . . . . . . . . . . . . . . . . 12.3 Diagrama de estado anidado detalleActivo para el estado activo de la clase Cliente . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 12.4 Diagrama de estado con concurrencia . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
162 164 165 165
13.1 Un diagrama de componentes que muestra las dependencias . . . . . . . . . . . . . 13.2 Un diagrama de despliegue sin el software . . . . . . . . . . . . . . . . . . . . . . . . . . . 13.3 Un diagrama de despliegue con el software . . . . . . . . . . . . . . . . . . . . . . . . . .
168 169 170
14.1 Ejemplo de paquetes y visibilidad . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 14.2 Una jerarquía de paquetes . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
174 174
15.1 Modelo de casos de uso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15.2 Modelo de clases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
184 185
LISTA DE FIGURAS
15.3 Otro modelo de clases . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 15.4 Las tarjetas CRC necesarias para Producir guía de curso . . . . . . . . . 15.5 Un diagrama de actividad para la preparación de la guía del curso . . . . . . . . 16.1 16.2 16.3 16.4 16.5 16.6
xv
º186 186 187
Tres en Raya . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Ajedrez . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . Diagrama de comunicación para un movimiento de la X en las Tres en Raya . Diagrama de clases para las Tres en Raya . . . . . . . . . . . . . . . . . . . . . . . . . . . Diagrama de clases para el marco de trabajo de los juegos . . . . . . . . . . . . . . Diagrama de estado de PosiciónActual . . . . . . . . . . . . . . . . . . . . . . . . . .
191 192 196 197 198 200
17.1 Diagrama de clases de un sistema de simulación de eventos discretos. . . . . 17.2 Algunas alternativas para las clases utilizadas en la creación del informe del comportamiento . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17.3 Diagrama de casos de uso de un sistema de simulación de eventos discretos. 17.4 Diagrama de clases detallado para un experimento de simulación . . . . . . . . . 17.5 Diagrama de estado de la EntidadActiva genérica . . . . . . . . . . . . . . . . . . 17.6 Diagrama de estado de Recurso . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17.7 Diagrama de estado detalleActivo de la clase Trabajador . . . . . . . . . 17.8 Diagrama de estado de Promedio . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17.9 Diagrama de colaboración de la cena de los filósofos . . . . . . . . . . . . . . . . . . 17.10. Diagrama de estado detalleActivo de la clase Filósofo . . . . . . . . .
206 207 208 212 216 218 219 221 223 23
18.1 El patrón Fachada aplicado a la biblioteca . . . . . . . . . . . . . . . . . . . . . . . . . . .
239
PREFACIO
Sobre la Segunda Edición La primera edición de este libro apareció en 1998, en ese momento el Lenguaje Unificado de Modelado, UML, estaba empezando a ser ampliamente adoptado. Desde entonces se ha convertido en el lenguaje dominante para el modelado de software. Durante los últimos años, el Grupo de Dirección de Objetos (OMO) ha estado trabajando en la primera gran revisión de UML, ahora denominada como UML 2.0. En ella se realizan muchos cambios significativos en el lenguaje. Esta segunda versión de Utilización de UML ha sido completamente actualizada para UML 2.0. Además, se ha tenido la oportunidad de hacer bastantes mejoras en el texto, en respuesta a los comentarios recibidos de la primera edición. Ha sido importante para mí mantener reducido el tamaño de este libro, ya que muchos lectores han valorado este hecho. Por lo tanto, hay muchas características de UML 2.0 —lenguaje mucho más complejo que UML 1.1— que se han omitido en el texto. He aspirado a describir un subconjunto utilizable y fácil de enseñar del lenguaje. Como siempre, los comentarios son bienvenidos. Además de los agradecimientos del prefacio original de la primera edición, quería agradecer a todos aquellos (demasiados para ser citados) que han hecho comentarios o sugerencias; especialmente a mis estudiantes del MSc (Master) en Ingeniería del Software Avanzado de la Universidad de Oxford. También desearía agradecer a Simon Plumtree, mi actual editor, y a toda la gente involucrada de Pearson. Perdita Stevens Profesor de Ingeniería de Software Facultad de Informática Universidad de Edimburgo Mayo de 2005
Sobre la Primera Edición UML, el Lenguaje Unificado de Modelado, se ha adoptado como estándar por OMG y, por lo tanto, los ingenieros de software necesitan familiarizarse con su conocimiento como activo importante de su profesión. Por ello, ha aumentado la necesidad del conocimiento de UML en la comunidad universitaria y por eso nosotros lo hemos introducido como lenguaje de modelado en nuestros cursos de grado para estudiantes de tercero y cuarto año. Desafortunadamente, nos
xviii
PREFACIO
encontramos con la inexistencia de un libro de texto adecuado; todo el material existente de UML estaba dirigido principalmente hacia desarrolladores experimentados, pero no hacia estudiantes. Por esa razón decidimos escribir nuestro propio libro de texto, y éste es el resultado. La filosofía de este libro es ser ecléctico. Algunos de los lectores pueden sentirse ofendidos porque no nos definimos ni por un bando ni por el otro, y tampoco declaramos cómo se debe de realizar el desarrollo orientado a objetos, pero esto lo hemos hecho intencionadamente. No creemos que ninguna postura tenga la verdad absoluta, lo que sí creemos es que, en la práctica, la mayoría de los desarrollos OO con éxito utilizan diferentes técnicas; y creemos que no es justo tratar de vender a los estudiantes la idea de que sólo uno de los enfoques es el bueno. Se ha utilizado la ortografía americana, por consejo de nuestros editores; imploramos indulgencia a nuestro lectores británicos.
Cómo utilizar este libro Este libro está dividido en cuatro partes. Inevitablemente tienen solapamientos y están interrelacionadas. Describamos cada parte, y mostremos los pasos a seguir a través del libro: • La Parte I introduce los conceptos de ingeniería del software desarrollo orientado a objetos. • La Parte II estudia UML como lenguaje. Hay dos capítulos dedicados principalmente a los tipos de diagramas. El primer capítulo contiene el material básico que cualquier usuario de UML necesita conocer. El segundo contiene las características más avanzadas o especializadas que los lectores pueden necesitar antes de abordar desarrollos rigurosos en UML, pero que se estudian mejor una vez entendido el material fundamental. Desde nuestra experiencia, la confusión que se produce por la comprensión a medias de muchas características es un problema mucho más serio que la ignorancia de características menos importantes.
Parte I 3 1
2 4
Parte II
Parte III
5
7
9
11
13
6
8
10
12 14
15
16
17
Parte IV 18
19
20
Dependencias entre capítulos, ¡no está exactamente en UML!
PREFACIO
xix
• La Parte III consta de tres estudios de casos. Estos se han diseñado desde el principio. La página web del libro contiene el código para la funcionalidad analizada en los capítulos, pero los capítulos también incluyen consejos para posibles y futuras ampliaciones. • La Parte IV examina la calidad y la garantía de calidad, la verificación, validación y pruebas, la gestión de proyectos de software, el equipo de trabajo, etc. Nos centraremos en las diferencias que existen entre los modelos de desarrollo iterativo basado en componentes, y el enfoque tradicional en cascada. Se proporcionan varias referencias a otras partes de este amplio campo de estudio. Existe una página Web del libro (actualmente http://homepages.inf.ed.ac.uk/ perdita/Book) que contiene enlaces a información importante en la Web. Se puede acceder también a través de la Web del editor http://www.pearsoned.co.uk/stevens
Estructura del libro El diagrama de las dependencias entre capítulos es un poco engañoso: esto significa que nada de lo que viene a continuación depende de lo anterior. Por supuesto que hemos intentado reducir esta situación al mínimo, pero es imposible eliminar, por ejemplo, las dependencias circulares entre la comprensión de la necesidad del diseño y qué es, la comprensión de un lenguaje de modelado, y los ejemplos. Por consiguiente, aunque le animamos a utilizar el libro del modo que le resulte más útil, le sugerimos el siguiente orden: • Para principiante en OO: 1, 2, 3, 4, 5, 7, 9, 11, 15 • Para personas familiarizadas con OO: 4, 3, seguido de cualquier subsecuencia 5-20. • Para personas con experiencia en OO y especialmente interesadas en aplicar OO y UML en la práctica: leer por encima del 1-4, y a continuación 5, 7, 9, 11, 13, 14, 17, 18, 19, 20.
Secciones especiales Algunas secciones especiales necesitan explicación. • Los paneles describen temas que son importantes, pero que no encajan en su totalidad con el desarrollo del resto del capítulo en el que aparecen. Por esta razón, se incluyen en los capítulos de las Parte II diferentes técnicas de diseño. • Las anotaciones técnicas de UML serán de interés para aquellos lectores que deseen comprender cómo se relaciona el texto con el material básico (fuente) de UML, la guía y la semántica de la anotaciones. • Se pretende que las preguntas indicadas con la letra P sean directas, y que sirvan para comprobar qué es lo que se ha comprendido. • Las preguntas de discusión (debate), que aparecen en las cajas sombreadas, son más interesantes: requieren reflexiones más profundas, o investigación, apoyada en materiales complementarios a este libro, tal como el indicado en la página web del mismo. Utilizaremos estas preguntas como base para debates en pequeños grupos y para volverlas a utilizar en conferencias; deseamos que también sea un material útil para tutoriales y cursos, o simplemente para reflexionar.
xx
PREFACIO
Notas para la enseñanza Este libro se diseñó fundamentalmente para ser un libro de texto destinado a estudiantes de cursos superiores de Informática (por ejemplo, para el segundo y tercer curso británico, tercer y cuarto año escocés), Ingeniería del Software y otros cursos similares. Los primeros capítulos se han escrito como base para cursos introductorios de informática y estudiantes de los primeros años. El curso de cuarto año que impartimos también está disponible para estudiantes del Master en Ciencias británico (MSc), dado que este libro se ha escrito de forma que se adapte a diferentes tipos de estudiantes. El libro tiene una página web pública disponible (como se ha mencionado anteriormente), y un sitio protegido con contraseña, sólo disponible para instructores. Contacte con el editor en http://www.pearsoned.co.uk/stevens para acceder a una información más detallada, donde se pueden encontrar soluciones a ciertos ejercicios, más anotaciones, y quizá información de mayor utilidad sobre el estudio de un caso más amplio no contenido en el libro. (Sin embargo, esto último se basa en un código disponible para el público, que se debería tener en cuenta si se va a utilizar para realizar una evaluación). Deliberadamente no se enseña en el libro un lenguaje de programación. Existen tres razones: 1. La gama de lenguajes de programación utilizada en los cursos importantes es muy amplia, y para cada posible lenguaje hay ya disponibles muchos recursos para enseñar sus especificaciones. Hemos recopilado algunos enlaces a material de enseñanza gratuito de diferentes LPOO en la página web del libro: ¡por favor, envíen más! 2. Nuestra intención era la de escribir un libro breve, manejable y asequible. 3. Creemos que lo más importante es que los estudiantes se den cuentan de que los temas íntimamente relacionados con A/DOO y CBD1 son independientes del lenguaje. En Edimburgo utilizamos el material de este libro en dos cursos. El tercer año de Ingeniería del Software con Objetos y Componentes 1 abarca la mayor parte del material de los Capítulos 1-4, 5, 7 y 9; también se enseña un lenguaje de programación orientado a objetos (en nuestro caso, Java). Al final de este curso se espera que los estudiantes comprendan los conceptos de orientación a objetos, diseño basado en componentes e ingeniería del software, y que sean capaces de llevar a cabo un diseño detallado. El cuarto año de Ingeniería del Software con Objetos y Componentes 2 profundiza en mayores conocimientos de diseño y UML (los restantes capítulos de la Parte II), y analiza los temas más difíciles sobre análisis y arquitectura. En la parte IV se continúa utilizando el material, y animando a seguir un enfoque más pragmático, y abierto hacia la ingeniería del software de la vida real. Para apoyar esta parte, en SEOC2 se utilizan preguntas abiertas y se anima a los estudiantes a que utilicen otros recursos, especialmente la web. (Pensamos que éste es un hábito importante que hay que fomentar entre estudiantes, dado que es muy probable que la ingeniería del software cambie a gran velocidad durante su carrera.) Muchas de las Preguntas de Discusión en la Parte IV del libro requiere de los estudiantes para la utilización otros recursos para investigar esas cuestiones. Y como ayuda la página web pública del libro, incluye una colección de enlaces a artículos notables en el texto. Naturalmente, no podemos garantizar que los propietarios de estos materiales continúen dejándolos disponibles, pero haremos todo lo posible por mantener los enlaces: en caso de tener algún problema les rogamos 1
En el original, CBD (Component-Based Development), N. del T.
PREFACIO
xxi
que nos los hagan saber, y si tienen más enlaces que puedan ser útiles para los lectores, les rogamos nos los envíen.
Herramientas Habíamos planificado un apéndice en donde se comparaban las herramientas CASE (ingeniería del software asistida por computadora) disponibles que soportaban UML; en aquel momento ese objetivo parecía razonable. Sin embargo, el número de herramientas CASE que soportan UML ha crecido enormemente desde entonces, y ahora parece injusto hacer una lista de algunas herramientas sin nombrar todas las que existen. La página web del libro contiene un enlace al sitio web de Cetus, donde se incluye una colección de herramientas que soportan UML que se actualiza frecuentemente. Hemos utilizado Rational Rose como apoyo en nuestro cursos que utilizan UML, y ha resultado ser una experiencia positiva. En comparación con cursos anteriores donde todo el modelado se hacía con lápiz y papel, hemos comprobado que cuando los estudiantes tienen una herramienta disponible alcanzan un mayor nivel de competencia en menos tiempo. La razón parece ser que la herramienta ofrece retroalimentación rápida de errores elementales, como pueden ser diagramas sintácticamente incorrectos. La parte negativa es que se pueden crear modelos innecesariamente grandes y complicados.
Sobre la edición revisada Esta impresión de la edición revisada describe UML 1.4, el cuál ha sido lanzado en febrero de 2001. Anteriores impresiones de la edición revisada describían UML 1.3, la cual fue lanzada en junio de 1999 y formalmente adoptada por la OMG en marzo de 2000. UML 1.3 fue significantemente diferente de UML 1.1, pero los cambios entre UML 1.3 y UML 1.4 son mucho menores. Para ampliar detalles sobre todos los cambios, visítese la página web del libro.
AGRADECIMIENTOS DE LA PRIMERA EDICIÓN
Agradecemos a nuestras familias su apoyo durante la escritura de este libro. Agradecemos a todo el personal de Addison Wesley Longman, especialmente a Sally Mortimore y Keith Mansfield, por habernos estado ayudando y apoyando durante todo el proceso de edición. Este libro se ha beneficiado en gran medida con los comentarios procedentes de revisores anónimos. Muchos de nuestros amigos y colegas también nos han ayudado a través de sus comentarios en los borradores, discutiendo ideas y resolviendo preguntas. Damos las gracias en particular a: Stuart Anderson, Adam Atkinson, Alan Gauld, Martin Hofmann, Bernd Kahlbrandt, Scott Keir, Ben Kleinman, Lindsay Marshall, Benjamin Pierce, Ian Redfern, Peter Thanisch, Pauline Wilcox y Alan Wills. Y, sobre todo, agradecemos a todos nuestros estudiantes de cualquier lugar, y a todos los demás que nos han enviado sugerencias. Marzo, 2001 Los editores desean mostrar sus agradecimientos por permitir la reproducción del siguiente material con derechos de autor (copyright). Pearson Education Limited, UK Texto de p. 427, Jacobson: Software Reuse, 1997, ISBN: 0201 924765 Reimpresión con permiso. John Wiley & Sons Limited, UK Texto de Buschmann: Pattern-Oriented Software Architecture, ISBN: 0471 958697, 1998 Reproducido con permiso. Perdita Stevens Departamento de Informática Universidad de Edimburgo JCMB, King’s Buildings Mayfield Road EDIMBURGH EH9 3JZ Escocia, UK [email protected]
Rob Pooley Departamento de Computación y EE (Ingeniería Electrónica) Universidad de Heriot-Wat University EDIMBURGH EH14 4AS Escocia, UK [email protected]
Parte
I Conceptos básicos
Capítulo 1
Ingeniería del software con componentes
3
Capítulo 2
Conceptos de objetos
17
Capítulo 3
Estudio de un caso introductorio
31
Capítulo 4
El proceso de desarrollo
49
Capítulo
I Ingeniería del software con componentes
Este capítulo comienza a tratar las siguientes preguntas: • ¿Qué es un buen sistema? • ¿Se tienen buenos sistemas? • ¿Cómo son los buenos sistemas? • ¿Cómo se construye un buen sistema? Se debe decir que “empieza” en la totalidad de este libro y, por supuesto, toda la disciplina de la ingeniería del software se puede ver como un intento de contestar a estas cuestiones más detalladamente. Nuestro objetivo en este capítulo es establecer el marco para el resto del libro, y establecer indicadores para encontrar más información. Antes de empezar, hay que decidir sobre qué se va a hablar. Este libro está centrado principalmente en la ingeniería del software. Se pensará en cómo es el despliegue del software en el hardware; pero no se tratarán, por ejemplo, las necesidades específicas de los sistemas integrados. Este es un libro sobre ingeniería del software orientada a objetos, de manera que se piensa principalmente en los tipos de sistemas que se construyen con la tecnología orientada a objetos.
1.1
¿Qué es un buen sistema? Fundamentalmente, un buen sistema (o de alta calidad) es aquel que cumple las necesidades del usuario. Es decir, tiene que ser: • útil y aprovechable: un buen software hace la vida de los usuarios más fácil o mejor. • fiable: un buen software tiene pocos errores. • flexible: las necesidades de los usuarios cambian a lo largo del tiempo, incluso mientras el software se está desarrollando, de manera que es importante poder realizar cambios en
4
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
el mismo más tarde. ¡Además, tiene que ser posible no cometer más errores! Todos los cambios que se hacen en el software después de ser entregado, tradicionalmente se les llama mantenimiento. • accesible: tanto para la compra como para el mantenimiento. Los costes de mano de obra son el elemento más significativo dentro de los costes del software, de manera que si éste se reduce quiere decir que es relativamente sencillo y fácil de desarrollar y mantener. • disponible: por otra parte, ¡no importa lo bueno que sea! Consideraremos dos aspectos de disponibilidad. — El software tiene que poder ejecutarse en un hardware disponible, con un sistema operativo disponible, etc. Esto implica, por ejemplo, que un sistema debe ser suficientemente portable, y también nos lleva de nuevo a la facilidad de mantenimiento, ya que debe ser posible realizar cualquier cambio provocado por cambios en el entorno del software. — ¡El software debe ser lo primero que exista! De forma que un proyecto de software debe completarse con éxito, y entregar el software prometido.
1.2
¿Tenemos buenos sistemas? Antes de empezar con todos los problemas de los sistemas de software modernos, hay que dedicar un momento a analizar cuánto software, para nosotros, funciona satisfactoriamente. Los avances en el software han revolucionado la preparación de un libro, la banca, y la búsqueda de información (piénsese en los catálogos de una biblioteca, o en la Web), por nombrar algunos ejemplos. Algunas cosas las estamos haciendo bien.
1.2.1
Problemas...
Por desgracia, usted, sin duda alguna, también encontrará fallos. Conocerá muchos sistemas que no cumplen con los requisitos de sus usuarios y/o tienen fallos técnicos. Generalmente, los sistemas están anticuados incluso cuando están siendo diseñados. A menudo las necesidades de los usuarios se pierden durante la captura de requisitos. Incluso, si sus necesidades se capturan correctamente al comienzo de un proyecto, éstas pueden cambiar durante el desarrollo del sistema, por lo tanto, el sistema entregado no cumple las necesidades de los usuarios en el momento de la entrega. Normalmente, las compañías todavía ponen como excusa del pobre servicio a sus clientes, el “error de computadora”. La mayoría de los usuarios de PC de gestión esperan que las aplicaciones, e incluso los sistemas operativos, fallen, se cuelguen, o por otra parte, funcionen mal con cierta regularidad. El software no es siempre aprovechable, útil, fiable, ni está disponible.
P: Piense en un software que le guste utilizar. ¿Desde qué punto de vista es un sistema de alta calidad? ¿Hay aspectos del mismo que impidan que sea un sistema de alta calidad en el sentido aquí presentado? ¿Qué características son más influyentes en la valoración del mismo? ¿Hay aspectos importantes que no hayan sido considerados aquí? P: Piense en un software que tenga mucho éxito pero que a usted, personalmente, no le guste utilizar. Conteste a las mismas cuestiones.
INGENIERÍA DEL SOFTWARE CON COMPONENTES
5
Pregunta de Discusión 1 ¿Cuáles son las mejores y las peores experiencias que usted, o alguien que conozca, ha tenido recientemente y que haya afectado al software?
Pregunta de Discusión 2 ¿Cómo podría la entrega de un nuevo sistema por sí mismo afectar a los requisitos de los usuarios?
También hace falta flexibilidad. ¡Recuerde el problema del milenio! Como probablemente sabrá, los primeros desarrolladores de software no estaban dispuestos a utilizar más espacio del necesario para almacenar una fecha, de manera que su formato de fecha no especificaba el siglo. En su lugar se utilizó una fecha de tan solo dos dígitos, entendiendo que se encontraban en el siglo XX. Esto, en algunos casos, era razonable y comprensible; por ejemplo, el almacenamiento secundario, como son los discos, solía ser bastante más caro de lo que es ahora, por lo que no podía desperdiciarse. En el momento de escribir la primera edición de este libro (finales de los noventa) había una gran preocupación por mantener estos sistemas actualizados para poder mantener las fechas del siglo XXI.¡Muchos sistemas se están abandonando completamente porque sería demasiado caro hacer este cambio! Aunque, en el acontecimiento, el extendido caos que se había anunciado no ocurrió, los costes del cambio fueros inmensos. La verdadera razón de porqué esto es interesante desde el punto de vista que aquí nos interesa, es que, conceptualmente, es difícil imaginar un cambio más simple que el de los formatos de las fechas. Cuando se considera el hecho de que los usuarios de software realmente quieren cambios más difíciles de alcanzar, para permitir que sus procesos de negocio sean soportados de manera apropiada por el software, se ve por qué se tiene un problema con los sistemas heredados. Se necesitan técnicas para construir software más flexible, aunque no todas las técnicas para los sistemas inflexibles están anticuadas. Todavía se están construyendo. Por último, consideremos la accesibilidad. Esto resulta estar íntimamente relacionado con la fiabilidad y la flexibilidad, ya que el coste de ajuste de errores y de mantenimiento es el mayor coste en el que se incurre al buscar sistemas de alta calidad. La complejidad, la interdependencia de componentes y la introducción de nuevos errores influyen en todo esto. Si el software se utiliza a lo largo de un amplio período de tiempo, la necesidad de una nueva plantilla para comprender un sistema antiguo en su totalidad para después encargarse de él, complica más el problema. A menudo, a la mejor plantilla de trabajadores no les atrae tal trabajo por lo que se le tiene que asignar a personas menos capacitadas. Aunque el mantenimiento puede requerir tanta capacidad e ingenio como otro desarrollo, la mayoría de la gente encuentra mucho más gratificante producir algo nuevo que mejorar algo hecho.
1.2.2
... incluso fallos drásticos
Muchas veces los fallos de estos atributos tan ansiados tienen efectos mucho más dramáticos de lo que uno puede imaginar. Debemos ser conscientes de estos ejemplos infames: Ariane 5 cuyo vuelo inaugural del 4 de junio de 1996 terminó en explosión en la pista debido a una serie de fallos de software.
6
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
Taurus Un sistema de ajuste de transacciones automatizado planificado para la Bolsa de Londres. El proyecto se canceló en 1993 después de más de cinco años. El coste del proyecto rondaba los 75 millones de libras; la estimación de las pérdidas de los clientes rondaba los 450 millones de libras; y el daño a la reputación de la Bolsa de Londres fue incalculable. El sistema de tramitación de equipaje de Denver, donde un complejo sistema planificado, que englobaba cerca de 300 ordenadores, se excedió impidiendo abrir el aeropuerto a tiempo, y después resultó contener excesivos errores y costó casi otro 50 por ciento más del presupuesto inicial de cerca de 200 millones de libras hacerlo funcionar. El sistema de ambulancias de Londres, donde, debido a una sucesión de fallos de ingeniería del software, principalmente defectos en la gestión del proyecto, se presentó un sistema que falló dos veces en otoño de 1992. Aunque el coste monetario, de tan “sólo” unos 9 millones de libras, fue bajo en comparación con los otros ejemplos, se piensa que hubo gente que murió y que no habría muerto si las ambulancias hubiesen llegado tan rápido como lo hubieran hecho sin este fallo de software. Therac-25, donde, entre 1985 y 1987, seis personas (al menos) sufrieron serias sobredosis de radiaciones debido a un mal funcionamiento relacionado con el software de la máquina de terapia de radiación Therac-25. Tres de ellos se cree que murieron por la sobredosis. La principal causa fue la falta de garantía de calidad, que lleva el desarrollo de un sistema demasiado complejo, probado inadecuadamente y con poca documentación, y posteriormente al fallo, al que hay que aplicar una adecuada acción correctiva. Pregunta de Discusión 3 ¿Cuál de estos atributos deseados falló, en cada caso? ¿Hay alguna causa de fallo que no esté cubierta?
Pregunta de Discusión 4 Tanto en el caso de Ariane 5 como el de Therac-25, un factor influyente fue el hecho de que el software que se utilizaba previamente sin problemas aparentes fue reutilizado, a veces con modificaciones, en un nuevo contexto donde alcanzó serios problemas. Analice con más detalle qué pasó (hay algunos enlaces web en la página de inicio del libro), y piense en las implicaciones de estos casos de reutilización.
Estos ejemplos no son los únicos. Volviendo a la perspectiva económica, considere las siguientes estadísticas citadas en un artículo de W. Wayt Gibbs [24]: • de media, los grandes proyectos duran un 50 por ciento más de lo planificado. • tres cuartos de los fallos de un gran proyecto son operativos. • un cuarto de los grandes proyectos se cancela. (El artículo no es siempre creíble, pero incluso si estas son sobreestimaciones significativas, la escala del problema es enorme.) Los riesgos de las listas de correo y de asimilación de datos (véase la página de inicio del libro) es una buena fuente los problemas contemporáneos con los proyectos de software,
INGENIERÍA DEL SOFTWARE CON COMPONENTES
7
particularmente cuando los fallos causan peligro a las vidas, los negocios, o la integridad y seguridad de la información. El Grupo Standish (véase la página de inicio del libro) también recoge estadísticas útiles sobre proyectos de software.
1.2.3
Promesas, promesas
Todas las nuevas tecnologías prometen reducir los tiempos de desarrollo y aumentar la tasa de éxito de los proyectos, pero los ingenieros del software experimentados tienden a ser justificadamente escépticos al respecto. Uno de los problemas fundamentales que ha de afrontar una técnica para tratar con grandes proyectos es “el mítico hombre-mes (mythical man-month)” [9] de Fred Brooks. Cuanto mayor es el proyecto, mayor es la proporción de los costes del mismo y del tiempo que se pierde en la comunicación entre la gente del proyecto, debido a que cada persona tiene más gente con quien comunicarse. Un efecto de esto es que la reacción natural ante un proyecto que empieza con errores en la planificación, es la de aumentar el número de personas que trabajan en el proyecto, lo que a menudo falla. Si se dobla el número de personas en un proyecto, no se puede esperar reducir a la mitad el tiempo del mismo. ¡Se puede incluso aumentar el tiempo de duración! A un gran grupo de personas que trabaja conjuntamente en un sistema, le lleva bastante tiempo y esfuerzo comunicar toda la información que se necesita para mantener el trabajo consistente, y si esto falla, el resultado puede ser que partes del sistema no estén conectadas entre sí. Los efectos son especialmente devastadores si un fallo como tal se descubre tarde en el proyecto. Por esta razón, los grandes proyectos son más caros y más arriesgados que los pequeños. El Departamento de Defensa Americano encargó el diseño del lenguaje Ada, que fue estandarizado en 1983, como intento de resolver la crisis del software. Ada fue pensado para implementar, de la mejor manera posible, los conceptos del análisis, diseño y programación estructurada. La modularidad y la encapsulación fueron conceptos clave en el diseño del lenguaje. Otros esfuerzos recientes se han concentrado en la mejora de metodologías para la ingeniería del software y en mejorar la educación en ingeniería del software. Véase la página de inicio del libro para enlaces a la Base del Conocimiento de la Ingeniería del Software (SWEBOK), por ejemplo. Estos esfuerzos han producido una diferencia substancial: aunque los proyectos de ingeniería del software siguen teniendo problemas, es discutible que el término “crisis del software” esté ya anticuado.
1.3
¿Cómo son los buenos sistemas? A lo largo de las últimas décadas se ha alcanzado un conocimiento cada vez más profundo de qué sistemas son los más propensos a ser correctos. Todavía queda mucho por aprender. El problema fundamental es que: Hay un límite de cuánto puede entender de una sola vez un ser humano.
Los sistemas pequeños (en esta categoría se incluyen casi todos los desarrollos que normalmente realizan los estudiantes universitarios) pueden construirse mediante “programación heroica”, donde una única persona intenta tener in mente todos los aspectos relevantes del sistema; pero en general es imposible, para un desarrollador o encargado de mantenimiento,
8
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
comprender todo lo relacionado con el sistema de una sola vez. Esto quiere decir que es fundamental poder emprender una tarea de desarrollo o mantenimiento sin entender todo el sistema. Para ver cómo evoluciona la comprensión del problema, piense en un encargado de mantenimiento que quiere realizar un pequeño cambio en un gran sistema, quizá cambiar tres líneas de código. ¿Qué más tiene que comprender el encargado de mantenimiento, para ver si puede verse afectado por el cambio? Naturalmente, la primera conjetura es que se tiene que comprender el código cercano a esas tres líneas, pero no el código que está más alejado. Desde los inicios de la historia de la programación, la gente se dio cuenta de que la falsedad de esta suposición era la principal causa de errores en los programas; por ejemplo, una sentencia go to en una parte del código lejana que dirige el flujo de control cerca del cambio podría significar que partes remotas del programa estén afectadas por dicho cambio. La identificación de qué partes de código remotas podrían estar afectadas no siempre era fácil. El término “código spaguetti” fue acuñado 1 para describir los sistemas en los que el flujo de control y la dependencia eran demasiado complejos de entender. La breve anotación de Edsger Dijkstra “la sentencia Goto considerada perjudicial” [14], de 1968, fue un famoso paso destinado a controlar la complejidad del sistema de manera que los humanos puedan comprenderlo. El siguiente paso es pensar en un sistema como conjunto de módulos e identificar dependencias entre módulos. En el más amplio sentido de la palabra, un módulo podría ser cualquier “bit” identificable de un sistema y tiene sentido por sí mismo. Por ejemplo, serían módulos: • ficheros. • sub-rutinas. • funciones de biblioteca. • clases, en un lenguaje orientado a objetos. • otras estructuras conocidas como módulos o similares. • programas o subsistemas independientes o semi-independientes. Por supuesto, no todos los módulos son iguales: coger un programa monolítico y separarlo de forma aleatoria en archivos no es óptimo. El resto de esta sección considera qué módulos característicos se deberían tener para llevar a cabo el desarrollo y el mantenimiento del sistema de la manera más sencilla, barata y fiable posible. Se debe tener en cuenta los conceptos asociados de dependencia, acoplamiento, cohesión, interfaz, encapsulación y abstracción. (Los distintos autores utilizan definiciones ligeramente diferentes de estos términos, de manera que las que se utilizan aquí no son las únicas posibles.) Una vez identificado lo que es un buen módulo, se puede contemplar la reutilización de un buen módulo como componente. El Módulo A depende del Módulo B si cualquier cambio en el Módulo B implica que el Módulo A también tenga que ser modificado. A veces se dice que el Módulo A es un cliente del Módulo B, o que el Módulo B actúa como servidor del Módulo A. (Sin embargo, los términos cliente y servidor se utilizan también de manera más específica: en una “arquitectura cliente-servidor”, por ejemplo, el cliente y el servidor son generalmente procesos separados en máquinas diferentes.) En general, es normal que un mismo módulo sea tanto cliente como servidor. Esto significa, que depende de algunos módulos, mientras que otros módulos dependen de él. Incluso es posible que un par de módulos se tengan 1
¡Parece ser que por W. G. E. Stevens, padre de Perdita, a principios de los años sesenta!
INGENIERÍA DEL SOFTWARE CON COMPONENTES
9
el uno al otro de cliente; sin embargo, éste es un ejemplo de dependencia circular, que debe evitarse siempre que sea posible debido a que impide la reutilización. La dependencia a veces se conoce como acoplamiento. Un sistema con muchas dependencias tiene fuerte acoplamiento. Los buenos sistemas tienen débil acoplamiento porque, en ese caso, los cambios en una parte del sistema son menos probables de propagarse a través del mismo. Pregunta de Discusión 5 ¿Cómo se identifica el acoplamiento? ¿Cómo se mide? ¿Y se reduce? Piense: si se tiene un sistema con dos módulos acoplados, y se mezclan ambos en uno sólo, ¿se reduce el acoplamiento en el sistema?
1.3.1
Encapsulación: débil acoplamiento
Entre otras cosas, se pretende minimizar el número de casos en los que un cambio en un módulo necesite un cambio en otro módulo. Esto significa que hay que saber qué cambios dentro de un módulo pueden afectar al resto del sistema. Sin embargo, para poder aprovecharse del débil acoplamiento de un sistema es muy importante poder identificar qué módulos están acoplados; por otra parte, puede que haya que invertir cierto esfuerzo comprobando si se necesitan los cambios en un módulo, lo cual es caro incluso si la conclusión es que no se necesitan cambios. Lo ideal es saber con certeza qué módulos de un sistema podrían verse afectados por un cambio en un módulo dado. En resumen, una vez que se han establecido los límites entre los módulos del sistema, hay dos tipos de información que puede ser útil. 1. ¿Qué suposiciones pueden hacer los clientes de un determinado módulo sobre él? Por ejemplo, ¿qué servicios se supone que se van a proporcionar? Responder a esto nos permite saber qué tipo de cambios en un módulo pueden ser peligrosos. 2. ¿Qué módulos son clientes de uno dado? Contestar a esto nos dice qué módulos se tienen que cambiar, si se realiza un cambio peligroso en un módulo. Analizaremos estas preguntas de una en una.
Interfaces Una interfaz a un módulo define algunas características de dicho módulo en las que sus clientes pueden confiar. El resto del sistema sólo puede utilizar el módulo de las maneras permitidas por la(s) interfaz(es); esto es, una interfaz encapsula conocimiento sobre el módulo. Parnas escribe “Las conexiones entre módulos son las suposiciones que los módulos hacen unos de otros”. Cualquier suposición que un cliente hace sobre un servidor corre el riesgo de ser incorrecta; por lo que se debería documentar tales suposiciones en las interfaces y comprobar su corrección. Si se documentan todas las suposiciones en la interfaz con éxito se podrá decir: Si un módulo cambia internamente sin cambiar su interfaz, este cambio no necesitará que se realice ningún otro cambio 2 en ninguna otra parte del sistema. 2
El código cambia, de cualquier manera: desafortunadamente, dependiendo del entorno, incluso los cambios de interfaces protegidas en los componentes del código fuente pueden hacer necesaria la recompilación.
10
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
Hasta aquí una interfaz puede ser, por ejemplo, una sección de comentarios al inicio de un fichero que, por convención, todo aquél que cambie o utilice el archivo, mantiene al día. El problema con esto es que no hay manera de garantizar que todo el mundo cumpla con las convenciones. Perfectamente, podría haber comprobaciones automáticas de que ningún otro módulo hace ninguna suposición sobre este módulo que no esté documentada en su interfaz, y también que el módulo siempre justifica las suposiciones que se encuentran documentadas. Un lenguaje de programación, cuanto más permita la automatización de las comprobaciones, se dice que soporta más la modularidad y la encapsulación. La información que se puede almacenar en una interfaz y comprobar automáticamente depende, por supuesto, del lenguaje de programación. Normalmente habrá algunas funciones que podrán ser invocadas, posiblemente algunos nombres de valores de datos u objetos que pueden utilizarse; algunos lenguajes permiten que se nombren en una interfaz tipos o excepciones. Los módulos cliente no permitirán aludir a nada definido en el módulo servidor que no esté en la interfaz. En muchos lenguajes se harán comprobaciones de cómo los módulos cliente utilizan los nombres de los elementos en la interfaz, por ejemplo, comprobar sus suposiciones sobre qué tipos tienen los elementos. No utilizar un nombre en una interfaz o utilizarlo suponiendo que tiene un tipo diferente al documentado provoca un error, en tiempo de compilación, de enlazado o de ejecución. Esto está bien relacionado con los aspectos sintácticos de dependencia. Pregunta de Discusión 6 Piense en los ejemplos de módulos nombrados anteriormente, y en cualquier otro que conozca, en cualquier lenguaje. ¿Qué puede documentarse en las interfaces a los módulos, y cuántas comprobaciones son automáticas? ¿Qué comprobaciones se dan en cada momento?
Más allá: lo ideal sería poder comprobar la dependencia semántica. Esto es, que si una interfaz documenta fielmente las suposiciones que se pueden hacer sobre los módulos, sería una verdadera especificación del módulo, que explica lo que los clientes pueden asumir sobre el comportamiento del mismo, no sólo la sintaxis de cómo interactuar con él. Desafortunadamente, hoy por hoy, los lenguajes de programación no proporcionan esta información (con unas pocas excepciones). La principal razón para esto es que la informática teórica no ha avanzado lo suficiente para proporcionar estas características. Este es un área activa de investigación; se pueden esperar lenguajes de programación en el futuro que tengan conceptos de interfaz más convincentes.
Dependencias de contexto Hay varias razones para querer saber no sólo las dependencias que podrían existir —esto es, qué características están documentadas en las interfaces de los módulos del sistema— sino qué dependencias existen realmente. Se ha mencionado la situación en la que se realiza un cambio a un módulo que podría afectar a sus clientes; sus clientes son (por definición) los módulos que podrían cambiar, por lo que es importante poder decir cuáles son. Después, supóngase que se considera reutilizar un módulo. Se necesita saber no sólo los servicios que van a proporcionar ––cuál es su interfaz— sino qué servicios requiere para funcionar. Los servicios que requiere un módulo a veces se denominan dependencias de su contexto. Se pueden expresar en función de
INGENIERÍA DEL SOFTWARE CON COMPONENTES
11
interfaces; el módulo podría garantizar que, si tiene interfaces seguras, entonces, por turnos se proporcionará su propia interfaz. Entre ellos, las dependencias de contexto de un módulo y la interfaz propia del módulo constituyen un contrato que describe las responsabilidades del mismo. Si el contexto proporciona lo que el módulo necesita, entonces éste garantiza proporcionar los servicios descritos en su interfaz.
Beneficios de la modularidad con interfaces definidas Incluso una paupérrima interfaz a un módulo mal elegida puede hacer que el sistema se comprenda y se modifique más fácilmente. ¿Por qué sucede esto? La razón fundamental es que cualquier cosa que permita reducir lo que se necesita saber del mismo, es beneficiosa de varias formas diferentes. • En un desarrollo en equipo, los desarrolladores de código que utilizan un módulo sólo deberían comprender la interfaz del módulo, no cómo funciona, de manera que puedan ser más productivos. • Debido a que los desarrolladores pueden ignorar de forma segura algunos aspectos del sistema, tienden a conocer más a fondo los aspectos que necesitan de verdad, por lo que se introducen menos errores. • Los errores deberían ser más fáciles de encontrar (tanto en el desarrollo como en el mantenimiento), debido a que sería posible evitar examinar módulos irrelevantes. • Una vez que existe un módulo, con documentación de lo que proporciona y de lo que requiere, es posible, al menos, considerar su reutilización. El verdadero desafío, sin embargo, es definir buenos módulos con los elementos correctos en sus interfaces. Sólo en este caso se pueden alcanzar todos los beneficios. Pregunta de Discusión 7 ¿Cuáles son los resultados de hacer una mala selección de los módulos o de utilizar interfaces pobres? ¿Qué hace que una interfaz sea “demasiado grande” o “demasiado pequeña”? ¿Ha encontrado ejemplos?
Un módulo puede tener varias interfaces Si se sabe que el Módulo A puede verse afectado a veces por cambios del Módulo B, se quiere también identificar de la manera más sencilla posible los cambios requeridos, si hay alguno en nuestro caso particular. A veces es conveniente documentar los servicios que proporciona un módulo a través de varias interfaces diferentes, de forma que se pueda ser más preciso sobre qué servicios necesita un determinado cliente. Una vez más, es útil tanto para el mantenimiento como para la reutilización. Ahora se tiene una respuesta parcial a la pregunta “¿cómo son los buenos sistemas?”. Un buen sistema está formado por módulos encapsulados.
12
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
1.3.2
Abstracción: fuerte cohesión
Los módulos correctos, a menudo, tienen la propiedad de que sus interfaces proporcionan una abstracción de algún elemento conocido de manera intuitiva que puede, no obstante, ser difícil de implementar. Este tipo de módulos se dice que tienen una fuerte cohesión. La interfaz se abstrae de las cosas que el desarrollador no tiene que conocer para utilizar el módulo, dejando una fotografía ordenada y coherente de lo que el usuario del módulo quiere conocer. El módulo realiza un conjunto coherente de cosas, pero en la medida de lo posible, el desarrollador del cliente está protegido de la información irrelevante relativa a cómo el módulo hace lo que hace. Este asunto de permitir al desarrollador concentrarse en lo esencial es sutilmente diferente al asunto de encapsulación para alcanzar el débil acoplamiento, que está implicado en la prevención del desarrollador de utilizar información oculta. Como ejemplo sencillo y concreto, supóngase que un módulo proporciona una interfaz a un punto en el espacio 2D, y que la interfaz permite a los clientes obtener y establecer la posición del punto mediante sus coordenadas Cartesianas (x,y) o sus coordenadas Polares (r,θ), según sea lo más conveniente para el cliente. ¿Almacena el módulo ambos conjuntos de coordenadas y los mantiene consistentes, o almacena un conjunto y lo utiliza para calcular el otro cuando se solicite? Esta información no tiene interés para los programadores del cliente; la interfaz del módulo debería abstraerse de ella, y encapsular la estructura de los datos dentro del módulo. A este tipo de combinación de abstracción y encapsulación a menudo se le conoce como información oculta, pero repetimos la advertencia de que no hay ningún consenso sobre cuál de los tres términos cubre. Preferimos resumir: Abstracción es cuando un cliente de un módulo no necesita saber más de lo que hay en la interfaz. Encapsulación es cuando un cliente de un módulo no es capaz de saber más de lo que hay en la interfaz.
La situación en la que la interfaz proporciona medios para interactuar con algunos datos, pero no revela nada sobre el formato interno de los mismos, es normal tanto en el desarrollo orientado a objetos como en el estilo de tipo de datos abstracto. Algunos autores restringen el uso del término encapsulación a este tipo de módulo encapsulado. Si un módulo, de cualquier tamaño y complejidad, es una buena abstracción —tiene fuerte cohesión y débil acoplamiento— puede ser factible reutilizarlo en sistemas posteriores, o sustituirlo en el sistema existente. Esto es, puede ser posible considerarlo como un componente conectable. Sin embargo, aunque esto sea posible, también depende de la arquitectura en la que se desarrolla el componente y en la que vaya a ser utilizada. Esto lleva a pensar en el desarrollo basado en componentes centrado en la arquitectura (CBD)3. Los significados de estos términos, que son palabras de moda más recientes que aquellas a las que se ha hecho alusión hasta este momento, ¡son incluso más discutibles! En la siguiente subdivisión se explicará lo que se entiende en este libro por estos términos. En el Panel 18.1, se volverá a esta controversia una vez visto el resto del libro.
3
Consúltese también el término programación orientada al componente (COP) —es un sinónimo de CBD.
INGENIERÍA DEL SOFTWARE CON COMPONENTES
1.3.3
13
Arquitectura y componentes
En los años ochenta y a principios de los noventa, la orientación a objetos (que se tratará en detalle en el Capítulo 2) era la tecnología de moda que iba a resolver la crisis del software. Si su proyecto iba a utilizar la orientación a objetos, entregaría automáticamente un producto de alta calidad, en tiempo y dentro del presupuesto, y su organización experimentaría niveles de reutilización fenomenales y en continuo crecimiento, debido a que las clases desarrolladas para un proyecto podrían utilizarse en el siguiente, y en el siguiente, y en el siguiente... Esto era una exageración. Tal y como se puede deducir de la letanía de proyectos fallidos de esta misma era que se nombraron anteriormente, la realidad es ligeramente diferente. La mejor definición de la palabra componente que se puede encontrar es “cosa que se puede reutilizar o sustituir” —la visión escéptica es que el desarrollo basado en componentes alcanza, por definición, altos niveles de reutilización, ya que si no lo hace, ¡no es realmente CBD! Por ahora se considera un componente como una unidad de reutilización y sustitución. Un componente puede ser un módulo con propiedades que lo hacen reutilizable y sustituible. Hay muchas otras maneras de reutilización, que se tratarán con mayor profundidad en el Capítulo18; también se discutirá la pregunta sobre cuál debería ser realmente la definición de componente. Mucha gente considera la composición tardía como una característica importante del desarrollo basado en componentes: el asunto es que es más fácil reemplazar un módulo si se puede hacer sin necesidad de recompilar el sistema. Sin embargo, hay diferentes opiniones, especialmente sobre qué es tarde, y se prefiere no imponer esta restricción. ¿Qué hace que un módulo sea reutilizable? No debería sorprender que un módulo reutilizable sea bueno, de acuerdo con lo discutido anteriormente: tiene fuerte cohesión, débil acoplamiento con el resto del sistema, una interfaz bien definida y es una abstracción encapsulada de un elemento bien comprendido. Lo que puede ser más sorprendente es que un módulo es reutilizable dependiendo del contexto en el que sea desarrollado y en el que se vaya a utilizar. El contexto incluye una amplia variedad de factores técnicos y no técnicos, a los que se volverá en el Capítulo 18. Como ejemplo ilustrativo de un factor no técnico, en una organización que recompensa a los programadores de acuerdo al número de líneas de código que escriben, no se reutiliza ningún módulo, ¡ya que ningún programador está incentivado para reutilizar nada! Este es un ejemplo extremo, pero tales factores sociales y de organización son discutiblemente más importantes que los factores técnicos. Para ver la importancia del contexto técnico, considérese una organización que produce un conjunto de sistemas que tienen mucho en común; a menudo esto se conoce como desarrollo de producto en línea, por razones obvias. Si la estructura de alto nivel de dos sistemas consecutivos es la misma, es bastante más probable poder utilizar un módulo desarrollado para un sistema y conectarlo al otro sin alterarlo —reutilización de caja negra— que si los dos sistemas tienen distinta arquitectura. La arquitectura del sistema incluye decisiones de alto nivel sobre cuál debería ser la estructura general del sistema, que se aplican a más de un sistema y se toman para alcanzar reiteradamente beneficios, tales como reutilización de componentes. Puede incluir también otras decisiones que cubren cómo se debe llevar a cabo la construcción del sistema. Un ejemplo de bajo nivel es que una decisión para manejar siempre errores inesperados de una cierta manera es una decisión de arquitectura; un ejemplo de alto nivel es una decisión para utilizar una base de datos de un fabricante determinado o un cierto lenguaje de programación. Parte de la motivación que lleva a tomar estas decisiones es la misma que la que lleva a identificar
14
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
módulos apropiados: reducir la carga que tiene el desarrollador o el encargado de mantenimiento. Si ciertas cosas están hechas de forma fiable y de la misma manera en todas las partes de un sistema, y mejor todavía de sistema a sistema, el desarrollador o encargado de mantenimiento puede esperar que las cosas sean de esta manera y se ahorra el problema de resolver cómo son, en esta instancia concreta. Un componente desarrollado en el contexto de arquitectura es más probable que se reutilice en un nuevo contexto si ambos comparten las decisiones de arquitectura (en este caso es la arquitectura en sí lo que se reutiliza: pero también aumenta la posibilidad de reutilizar componentes. Se considerará brevemente la reutilización de la arquitectura en el Capítulo 18.) Así, cualquier interfaz es mejor que ninguna, y una decisión de arquitectura no óptima puede ser mejor que no tomar ninguna. Sólo con interfaces, el verdadero reto es identificar las decisiones de arquitectura correctas.
1.3.4
Diseño basado en componentes: conectividad
La manera ideal de construir un nuevo sistema es coger algunos componentes existentes y conectarlos unos con otros. Por supuesto, la conectividad es la propiedad que permite hacer esto. La metáfora sugiere correctamente que esto es sólo, en parte, una propiedad de los elementos conectados. Los componentes tienen que ser elementos que completan las necesidades del sistema en su totalidad. Más que esto, tienen que ser compatibles unos con otros, y esto depende de la presencia de una arquitectura adecuada. Piense en un dispositivo eléctrico que se conecta a una red mediante un enchufe estándar. No se ha dicho cuál es la funcionalidad de este dispositivo, que es que se active mediante electricidad de la red y utilice un conector estándar, si bien, son decisiones de arquitectura con ventajas obvias. Una posible decisión de arquitectura alternativa podría haber sido hacer que la batería del dispositivo se activara fácilmente mediante baterías disponibles. Cuál de estas dos arquitecturas es más apropiada es una decisión importante que depende de elementos del proyecto que no han sido detallados. Una decisión como ésta probablemente debería tomarse muy pronto en el proyecto, ya que tendrá una influencia importante en el diseño del dispositivo. Esto es característico de las decisiones de arquitectura. Desde luego, una decisión de arquitectura pobre nos habría llevado al diseño del dispositivo con un conector no estándar. Más interesante, si el dispositivo sólo se vende en UK, sería una decisión incorrecta diseñar el dispositivo con un conector estándar de los Estados Unidos, aunque podría ser una decisión coherente si el dispositivo se vendiera sólo en los Estados Unidos. Las decisiones de arquitectura: • se tienen que tomar pronto en el proyecto. • se ven afectadas por la naturaleza de los componentes en la arquitectura. • pueden estar influenciadas por el entorno del proyecto. En resumen, el desarrollo está centrado en la arquitectura y basado en componentes si se le da máxima prioridad al seguimiento (y toma) de decisiones de arquitectura correctas, y a la utilización (y desarrollo) de componentes correctos. La orientación a objetos, como se verá, es un paradigma adecuado (pero no el único) en el cual hacer CBD centrado en la arquitectura.
INGENIERÍA DEL SOFTWARE CON COMPONENTES
1.4
15
¿Cómo se construye un buen sistema? Se volverá al proceso de desarrollo en el Capítulo 4. Establezcamos ahora los antecedentes considerando el término ingeniería del software, cuya adopción sugiere que los sistemas podrían ser construidos por analogía con artefactos de ingeniería, por ejemplo, motores o puentes. Una aproximación técnica: • utiliza un proceso definido con fases claras, cada una de las cuales tiene un producto final (quizá un documento, quizá algo construido). • está relacionado con encontrar un claro conjunto de requisitos, cuidadosamente definido tan pronto como sea posible. • considera los formularios de verificación y validación, tales como pruebas, nada más esencial para la construcción del producto en sí. • utiliza un almacenamiento de conocimiento, arquitecturas y componentes relevantes. • hace un uso coherente de las herramientas. Se ha identificado ya que los sistemas de software utilizan una arquitectura modular reutilizando componentes, tal y como hacen los artefactos de ingeniería. Pregunta de Discusión 8 ¿Cuáles son las diferencias, y las similitudes, entre la ingeniería del software y otras formas de ingeniería?
RESUMEN
Este capítulo es un recorrido rápido para motivarnos e introducirnos en el tipo de ingeniería del software de que trata este libro. Se ha planteado lo que es un sistema de software de alta calidad, y lo que alcanzan los sistemas de alta calidad de hoy día. Se ha discutido la necesidad de modularidad, y se han cubierto las características de los módulos correctos. Se ha considerado cómo ir más allá de los módulos de ingeniería correctos aislados, hacia los sistemas de ingeniería basados en arquitecturas de correcta reutilización con reutilización de componentes endémicos. Finalmente, se ha mencionado el proceso.
Capítulo
2 Conceptos de objetos
En este capítulo se sigue analizando la pregunta “¿cómo son los buenos sistemas?” mediante la descripción del paradigma orientado a objetos, cuya popularidad se debe, en parte, al hecho de que soporta el desarrollo de buenos sistemas. ¡Sin embargo, la utilización de la orientación a objetos no es ni necesaria ni suficiente para construir buenos sistemas! Se contestará a las siguientes preguntas: • • • •
¿Qué es un objeto? ¿Cómo se comunican los objetos? ¿Cómo se define la interfaz de un objeto? ¿Qué tienen que ver los objetos con los componentes?
Finalmente, se considerarán la herencia, el polimorfismo y la ligadura dinámica. Este capítulo se dirige a los lenguajes estándar y modernos orientados a objetos basados en clases, tales como Java y C. La mayoría del mismo también está dirigido a Smalltalk, Eiffel, CLOS, Perl5 y otros lenguajes orientados a objetos; pero la comparación detallada de los lenguajes de programación orientados a objetos (OOPL) queda fuera del alcance de este libro. Este capítulo incluye preguntas sobre cómo el lenguaje de programación que se utiliza proporciona las características de orientación a objetos que se trata. Si se conoce ya el lenguaje de programación, se debe poder contestar las preguntas según se plantean. Si no, puede ser mejor leer este capítulo al final, entonces estudiar lo básico del lenguaje elegido, y después volver a las preguntas para comprobar que se comprende cómo dicho lenguaje proporciona orientación a objetos. (Se hará referencia a cualquier lenguaje que el lector determine utilizar como “su lenguaje” por ser más corto).
2.1
¿Qué es un objeto? Conceptualmente, un objeto es una cosa con la que se puede interactuar: se le puede enviar varios mensajes y éste reacciona ante ellos. Cómo se comporte depende del estado interno actual del objeto, el cual puede cambiar, por ejemplo, como parte de la reacción del objeto al recibir un
18
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
mensaje. Importa con qué objeto se interactúa y, normalmente, se dirige al mismo por su nombre; es decir, un objeto tiene una identidad que lo distingue del resto de objetos. Por lo que un objeto es una cosa que tiene comportamiento, estado e identidad: esta caracterización se le debe a Grady Booch [7]. Consideremos estos aspectos con más detalle.
Cosa Es más interesante de lo que puede parecer a primera vista. Un objeto no es sólo una cosa en el sistema, sino que también es una representación de un concepto en el sistema. Por ejemplo, un objeto puede representar una cosa física, como es un cliente, un barco o un reloj. En realidad, la primera vez que los objetos aparecieron de forma explícita fue en Simula, donde representaban objetos del mundo real, los objetos simulados. Sin embargo, si el propósito general de los sistemas de software es que se construyan utilizando objetos, un objeto no necesita representar una cosa física. Se volverá de nuevo a la cuestión de cómo identificar objetos en el Capítulo 5.
P: ¿Cuáles de estos elementos podrían ser objetos: martillo, esfera, proceso químico, amor, niebla, río, cólera, gato, tonalidad de gris, gato gris? Estado El estado de un objeto lo constituyen todos los datos que encapsula en un momento determinado. Un objeto normalmente tiene un número de lo que se conoce como atributos (o variables de instancia o miembros datos) cada uno de los cuales tiene un valor. Algunos atributos pueden ser mutables; esto es que sus valores pueden cambiar. Por ejemplo, un objeto que representa a un cliente podría tener un atributo dirección cuyo valor debe cambiar si el cliente se muda de casa. Otros atributos pueden ser inmutables o constantes; por ejemplo, nuestro objeto cliente podría tener un atributo como número único identificativo del cliente, que podría ser el mismo a lo largo de toda la vida del objeto. En la mayoría de los lenguajes orientados a objetos de hoy día, el conjunto de atributos de un objeto no puede cambiar durante la vida del objeto, aunque los valores de esos atributos pueden cambiar.
Comportamiento Es la manera en que actúa y reacciona un objeto, en función de los cambios de su estado y el paso de mensajes. Un objeto comprende ciertos mensajes, lo que quiere decir que puede recibir mensajes y actuar sobre ellos. El conjunto de mensajes que entiende un objeto, al igual que el conjunto de atributos que tiene, normalmente está fijado. La manera en que un objeto reacciona ante un mensaje puede depender de los valores de sus atributos en un momento determinado: de esta manera, incluso cuando (como es normal) el mundo exterior no permite acceso directo a los atributos, sus valores le pueden estar afectando indirectamente. Este es el sentido en que los valores de los atributos son el estado del objeto.
Identidad Este concepto es un poco más escurridizo. La idea es que los objetos no se definen por los valores de sus atributos en un momento determinado. Un objeto tiene una existencia continuada. Por ejemplo, los valores de los atributos de este objeto podrían cambiar, quizá como respuesta a un mensaje, pero seguiría siendo el mismo objeto. A un objeto normalmente se le hace
CONCEPTOS DE OBJETOS
19
referencia por un nombre (el valor de una variable en un programa cliente, quizá aCliente) pero el nombre del objeto no es lo mismo que el objeto, porque un mismo objeto puede tener varios nombres diferentes. (Esto sólo sorprende si se es un programador funcional puro; y hay algunos autores distinguidos que defienden que se puede hacer OO de una forma puramente funcional, pero no se tratará esta cuestión en el presente libro).
2.1.1
Ejemplo
Piense en un objeto al que llamaremos miReloj, que comprende los mensajes informeTiempo e iniciarTiempoA(07:43), iniciarTiempoA(12:30), que de forma más general sería iniciarTiempoA(nuevaHora) para cualquier valor coherente de nuevaHora. El objeto hace público esto a través de una interfaz que indica que acepta mensajes de la forma informeTiempo() e iniciarTiempoA(nuevaHora: Hora) donde Hora es un tipo1 cuyos elementos son los valores coherentes de nuevaHora antes mencionados. ¿Cómo se implementa esta funcionalidad? El mundo exterior no necesita saber nada —la información debería estar oculta— pero quizá tiene un atributo Tiempo, cuyo valor lo devuelve el objeto como respuesta al mensaje informeTiempo, y que se reinicia a nuevaHora cuando el objeto recibe el mensaje iniciarTiempoA (nuevaHora). O quizá pase estos mensajes a cualquier otro objeto que conoce, y ese otro objeto tiene que tratar dichos mensajes.
2.1.2
Mensajes
De este ejemplo se ven algunas cosas sobre los mensajes. Un mensaje incluye una palabra clave llamada selector; en el ejemplo se han visto los selectores informeTiempo e iniciarTiempoA. Un mensaje puede, pero no es necesario, incluir uno o más argumentos, cuyos valores se le pasan al objeto tal y como se pasan los valores a una función en una llamada normal. En el ejemplo, 07:43 es un argumento que forma parte del mensaje iniciarTiempoA(07:43). Normalmente, para un selector determinado hay un número “correcto” de argumentos que deben pasarse en un mensaje que empieza por ese selector; no se puede esperar que miReloj entienda iniciarTiempoA sin ningún argumento, o iniciarTiempoA(4,12:30), etc. Los valores aceptables de los argumentos se establecen2 en la interfaz del objeto.
P: ¿Cuál es la sintaxis para el envío de mensajes en el lenguaje que está utilizando? Destacar que los valores —por ejemplo, los valores de atributos de un objeto, o los valores de los argumentos enviados como parte de un mensaje— no tienen que pertenecer a un tipo básico (caracteres, enteros, etc.); cuáles son los tipos básicos, depende del lenguaje. Pueden incluso ser objetos.
P: ¿Cuáles son los tipos básicos en su lenguaje? Una de las cosas que un objeto podría hacer como respuesta a un mensaje es enviar un mensaje a otro objeto. Para hacer esto es necesario que tenga alguna manera de saber un nombre para 1
O una clase: las clases son tratadas en el punto 2.1.4 más adelante. Indicando la clase (véase 2.1.4) o a veces tan sólo la interfaz, que debería tener el objeto pasado como argumento, o su tipo si perteneciese a un tipo básico como entero o lógico —estos detalles dependen del lenguaje—. 2
20
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
el objeto. Quizá, por ejemplo, tiene el objeto como uno de sus atributos, en cuyo caso puede enviar el mensaje utilizando el nombre del atributo.
P: Supóngase que O es un objeto. Aparte de cualquiera de los objetos que O puede tener como valor de sus atributos, ¿a qué objetos podría enviar O mensajes?
Destacar que cuando se envía un mensaje a un objeto, en general no se sabe el código que se va a ejecutar como respuesta, porque dicha información está encapsulada. Esto será importante al final de este capítulo, cuando se trate la ligadura dinámica.
2.1.3
Interfaces
La interfaz pública de un objeto define qué mensajes se aceptan sin importar de dónde vienen. Normalmente la interfaz almacena los selectores de estos mensajes junto con alguna información sobre qué argumentos se requieren y si se devuelve algo o no. Tal y como se destacó en el Capítulo 1, probablemente se preferiría que la interfaz incluyese algún tipo de especificación sobre las consecuencias de enviar mensaje a un objeto, pero estas especificaciones normalmente sólo vienen en comentarios y documentación anexa. En la mayoría de los lenguajes puede haber atributos también en la interfaz pública del objeto; poner un atributo X en la interfaz equivale a declarar que ese objeto tiene un dato X, que el mundo exterior puede inspeccionar y alterar. Pregunta de Discusión 9 Si se utiliza un lenguaje que no admite atributos en la interfaz pública, ¿cómo se puede obtener los mismos efectos utilizando mensajes para acceder a los datos? ¿Hay alguna ventaja al utilizar esta forma incluso en un lenguaje que admite atributos en las interfaces? ¿Y desventajas?
A menudo, no es apropiado permitir que todo el sistema envíe cada uno de los mensajes que entiende un objeto a dicho objeto. Por lo que, normalmente, un objeto es capaz de entender algunos mensajes que no están en la interfaz pública. Por ejemplo, en el Capítulo 1 se explicó que las estructuras de datos utilizadas por un módulo deberían ser encapsuladas; en este caso se corresponde con la idea de que la interfaz pública de un objeto no debería incluir atributos. Un objeto siempre puede enviarse a sí mismo un mensaje que pueda comprender. Puede parecer extraño o demasiado complicado pensar en un objeto enviándose mensajes a sí mismo: la razón para pensar esto se verá más clara cuando se explique la ligadura dinámica, más tarde en el capítulo. De manera que un objeto normalmente tiene, al menos, dos interfaces: la interfaz pública, que cualquier parte del sistema puede utilizar, y la interfaz privada que puede ser utilizada por el propio objeto y algunas partes privilegiadas del sistema. A veces es útil otra interfaz intermedia que proporcione más prestaciones que la interfaz pública, pero menos que la interfaz privada. Hay veces que, en un contexto determinado, sólo se necesita una parte de la interfaz pública de un objeto; puede ser útil almacenar en una interfaz de menor tamaño que la interfaz pública del objeto, las características que son realmente necesarias. Debido a esto, un objeto puede tener (o realizar) más de una interfaz. A la inversa, varios objetos diferentes pueden implementar la misma interfaz. Si no se especifica lo contrario, “interfaz” será interfaz pública.
CONCEPTOS DE OBJETOS
2.1.4
21
Clases
Hasta aquí, se ha hablado de objetos como si cada uno estuviese definido por separado, con su propia interfaz, su propia manera de controlar qué otros objetos podrían enviarle qué mensajes. Por supuesto, esta no es la manera más coherente de construir un sistema típico, porque los objetos tienen mucho en común unos con otros. Si una empresa tiene 10.000 clientes y se quiere construir un sistema con un objeto que represente a cada una de estas personas, ¡los objetos que representan a los clientes tienen mucho en común! Se querrá que se comporten de manera consistente para que los desarrolladores y los encargados de mantenimiento del sistema puedan entenderlo. Se tendrá una clase de objetos que representen a los clientes. Una clase describe un conjunto de objetos que tienen un rol o roles equivalentes en el sistema. En los lenguajes orientados a objetos basados en clases, cada objeto pertenece a una clase, y la clase de un objeto es quien determina su interfaz 3. Por ejemplo, a lo mejor miReloj pertenece a una clase Reloj cuya interfaz pública especifica que cada objeto de la clase Reloj proporcionará la operación iniciarTiempoA(nuevaHora: Hora); Esto es, entenderá mensajes que tengan el selector iniciarTiempoA y un único argumento que es un objeto de la clase (o tipo) Hora. En realidad, la clase del objeto, junto con los valores de los atributos del objeto, es quien determina incluso la manera en que reacciona un objeto. Un método es una parte de código específica que implementa la operación. Sólo es visible en la interfaz del objeto el hecho de que dicho objeto proporciona la operación; el método que implementa la prestación se encuentra oculto. De forma similar, el conjunto de atributos que tiene un objeto viene determinado por su clase, aunque, por supuesto, los valores que toman dichos atributos no están determinados por la clase, pero pueden variar. Por ejemplo, a lo mejor los objetos que pertenecen a la clase Reloj tienen un atributo privado llamado hora. Si se envía el mismo mensaje a dos objetos que pertenecen a una misma clase, se ejecuta el mismo método en ambos casos, aunque el efecto de la ejecución del método puede ser muy diferente si los objetos tienen distintos valores en sus atributos. En resumen, los objetos de una misma clase tienen las mismas interfaces. Se podría describir las interfaces pública y privada para la clase Reloj de la siguiente manera: - hora: Hora + informeTiempo(): Hora + iniciarTiempoA(nuevaHora: Hora)
La interfaz pública está formada por las líneas marcadas con ; la interfaz privada incluye las líneas marcadas con .
P: En su lenguaje, ¿cómo define el programador una clase como Reloj? ¿Se definen por separado la interfaz y la implementación? ¿Cómo se llaman las interfaces pública y privada? (¡Probablemente public y private!) ¿Hay otras posibilidades? ¿Qué significan dichas posibilidades? Al proceso de creación de un nuevo objeto que pertenece a una clase Reloj se le llama instanciación de la clase Reloj, y al objeto resultante se le denomina instancia de la clase Reloj, 3
Realmente un objeto puede pertenecer a más de una clase, y la clase más específica a la que pertenece es la que determina su interfaz.
22
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
por esto, por supuesto, es por lo que las variables cuyos valores pertenecen al objeto y pueden cambiar a lo largo de su tiempo de vida se denominan variables instancia. La creación de un objeto consiste en la creación de un nuevo elemento con su propio estado y su propia identidad, que se comportará de forma consistente con todos los demás objetos de la clase. El proceso de creación normalmente incluye el establecimiento de los valores de los atributos; los valores se pueden especificar en la petición de creación, o los puede especificar la clase del objeto como valores iniciales por defecto. En los lenguajes tales como C y Java la clase tiene un rol directo en la creación de nuevos objetos —realmente hace todo el trabajo, en vez de actuar como una plantilla—. Esto es, una clase puede verse con una factoría de objetos.
P: En su lenguaje, ¿cómo se crea un nuevo objeto de una clase determinada? La mayoría de los lenguajes permiten que las clases se comporten como si fuesen objetos por derecho, conteniendo atributos y entendiendo mensajes por sí mismas. Por ejemplo, la clase Cliente podría tener un atributo númeroDeInstancias que podría ser un entero que se incrementa cada vez que se crea un nuevo objeto Cliente. Smalltalk tiene esta visión particularmente consistente, pero C y Java también tienen indicios de ella. En Smalltalk, por ejemplo, se crearía un nuevo objeto Cliente mediante el envío de un mensaje (por ejemplo new) a la clase Cliente. La reacción de una clase sería crear un nuevo objeto y entregárselo al llamador. Pregunta de Discusión 10 Considere qué pasaría si se trata de aplicar esta idea de forma consistente. Se ha dicho que cada objeto tiene una clase; así que cuando se considera una clase C como un objeto, ¿cuál es la clase de C? ¿Y la clase de la clase de C?
P: ¿Tiene su lenguaje características como éstas? ¿Exactamente cuáles? Digresión: ¿por qué tener clases? ¿Por qué no tener sólo objetos, que tienen estado, comportamiento e identidad, que es lo que se pide? Las clases en los lenguajes orientados a objetos sirven para dos propósitos. Primero, son una manera cómoda de describir una colección (una clase) de objetos que tienen las mismas propiedades. Los lenguajes de programación orientados a objetos pueden utilizar las clases esencialmente como una ventaja. Por ejemplo, no es necesario almacenar una copia del código que representa el comportamiento de un objeto en cada objeto, incluso aunque se piense que cada objeto encapsule su propio código. En vez de esto, los desarrolladores escriben la definición de una clase una sola vez, y los compiladores crean una única representación de una clase, y permite a los objetos de esa clase acceder a la representación de clase para obtener el código que necesitan. Recuérdese que la comodidad no es una trivialidad. Aquí, el principal objetivo es crear sistemas más fáciles de comprender, de manera que el desarrollo y el mantenimiento sea lo más sencillo, barato y fiable posible. Hacer que objetos parecidos compartan el mismo diseño e implementación es un paso muy importante para conseguir esto.
CONCEPTOS DE OBJETOS
23
Este es un caso fundamental de un principio4 que es muy importante en la ingeniería del software, y no sólo en el código, que se gritará: ¡ESCRIBA UNA SOLA VEZ! Quiere decir que, si dos copias de un mismo artefacto de ingeniería del software —parte de cualquier tipo de código, parte de un diagrama, parte de texto de un documento— tienen que ser siempre consistentes, entonces no debería haber dos copias. Cada vez que se almacena la misma información en más de un lugar, no sólo se gasta esfuerzo en la repetición, que podría utilizarse mejor en hacer algo nuevo, sino que además se crean problemas de mantenimiento, ya que las dos versiones no se mantendrán sincronizadas sin esfuerzo. La capacidad de aplicar el principio está en comprobar si se tiene o no dos copias de la misma información, o dos partes de información potencialmente diferentes que resultan, de momento, ser la misma. En el último caso, lo apropiado es copiar y pegar. En el primer caso, la actitud de resentimiento por duplicar la información en más de un lugar es, en realidad, una ventaja: sólo se debe aceptar hacer esto por una buena razón. Pregunta de Discusión 11 ¿Este principio quiere decir que es una mala idea pensar en la duplicación de datos en una base de datos distribuida? ¿Por qué?
Se puede ver que hay otras maneras de crear arbitrariamente muchos objetos similares mientras se escribe su código una sola vez; por ejemplo, se podría definir un objeto prototipo, y después definir otros objetos que son “como ese otro, pero con estas diferencias”. Otra vez, se necesitaría mantener una sola copia de código. Esto es aproximadamente lo que hace el lenguaje basado en prototipos Self. Sin embargo, los lenguajes basados en clases dominan, hoy por hoy, la orientación a objetos, y probablemente, continuarán haciéndolo. En segundo lugar, en los lenguajes OO más modernos, se utilizan las clases de la misma manera en que se utilizan los tipos en otros muchos lenguajes: para especificar qué valores se aceptan en un contexto determinado, y así permitir al compilador —y, con la misma importancia, al lector— comprender la intención del programador y comprobar que no se den en el programa ciertos tipos de errores. A menudo la gente cree que las clases y los tipos son lo mismo —es cómodo de veras, y a menudo no es erróneo, hacerlo. Sin embargo, está mal. Recuérdese que una clase no sólo define qué mensajes entiende un objeto— que realmente es todo lo que necesitarías saber para comprobar si es aceptable en algún contexto; sino que también define lo que hace el objeto como respuesta a un mensaje.
2.2
¿Cómo relacionar esto con los objetivos del capítulo anterior? Se están buscando módulos, idealmente módulos sustituibles y reutilizables que formarían buenos componentes. ¿Dónde se encuentran en un sistema orientado a objetos? 4
Enfatizado, en particular, por Kent Beck.
24
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
Los objetos individuales podrían considerarse módulos, pero no serían buenos módulos, porque normalmente en un sistema hay varios objetos diferentes que conceptualmente están estrechamente relacionados. Si cada objeto fuese considerado como un módulo separado, entonces, o habría inconsistencias confusas entre los módulos conceptualmente relacionados, o la necesidad de mantener la consistencia crearía fuerte acoplamiento. Se pretende que las clases sean módulos débilmente acoplados, y fuertemente cohesivos. Por lo tanto se puede esperar (quizá en vano, tal y como se tratará en la siguiente subdivisión) obtener los beneficios anunciados en el Capítulo 1, a saber, de coste y tiempo de desarrollo reducidos, facilidad de mantenimiento y alta fiabilidad. Estos beneficios no son específicos de OO, aunque OO actualmente es el camino mejor conocido hacia ellos. Hay otro beneficio muy reclamado en la orientación a objetos que no está incluido en esta categoría. Este beneficio es que es intrínsecamente natural examinar el mundo en función de objetos. Recordemos que un requisito principal en un buen sistema es que debería hacer lo que los usuarios necesitan, y se ha citado la dificultad de capturar esto fielmente y del seguimiento de cambios en él como unas de las cosas más complicadas del sistema. Es importante que el modelo del sistema (del dominio del problema y los procesos a llevar a cabo) sea compatible con el modelo de usuario. Parece que los objetos de dominio cambian menos frecuentemente y menos drásticamente que la funcionalidad exacta que el usuario requiere. Así que si se estructura un sistema en función de estos objetos, es más probable que los cambios en el sistema provoquen menos trastornos que si el sistema estuviese basado en los aspectos funcionales más volátiles. En resumen, se espera que el sistema coincida con el modelo del mundo del usuario, de manera que se pueda: • capturar los requisitos más fácilmente y fielmente. • seguir mejor los cambios en los requisitos de usuario. • tener en cuenta sistemas que interactúan de manera más natural. Por ejemplo, es más fácil implementar una interfaz de usuario que permita interactuar con un fichero, un reloj o una impresora en conjunto —esto es coherente desde el punto de vista del usuario porque estos elementos son como objetos del mundo real— si son objetos coherentes también desde el punto de vista del sistema. Por esta razón, las interfaces de usuario eran unas de las primeras áreas donde la orientación a objetos se hizo popular. Destacar, sin embargo, que hay que ser prudentes en reclamar estos beneficios. Hay casos (como los sistemas de procesamiento de peticiones) donde el enfoque orientado a objetos parece muy natural, y otros (como los compiladores) donde los argumentos parecen mucho menos convincentes. En cualquier caso, cuando la OO alcanza sus objetivos, normalmente no se debe a ninguno de los elementos específicos de la OO. Esto es porque • el enfoque orientado a objetos toma como fundamental la modularidad, encapsulación y abstracción • y los LPOO (Lenguajes de Programación Orientados a Objetos) lo hacen (¡relativamente!) fácil, de manera que es una probabilidad razonable que la manera obvia de hacer algo es también la manera correcta de hacerlo. La OO es una religión: la gente es quien resulta estar orientada hacia los objetos. Pregunta de Discusión 12 ¿Qué efectos, buenos y malos, podrían existir sugeridos por la afirmación “OO es una religión”?
CONCEPTOS DE OBJETOS
2.2.1
25
¿Qué tienen que ver los objetos con los componentes?
La exageración que rodea la orientación a objetos a veces sugiere que cualquier clase es automáticamente un componente reutilizable. Esto, por supuesto, no es verdad. En el Capítulo 1 se discutió por qué la reutilización de un componente no es simplemente un hecho del componente en sí, sino que depende del contexto de desarrollo y de la reutilización propuesta. Otro factor importante es que la estructura de una clase, a menudo, resulta estar demasiado detallada para la reutilización efectiva. Por ejemplo, para reutilizar una única clase de manera efectiva y rápida tienes que escribir en el mismo lenguaje de programación y utilizar una arquitectura compatible. Esto no es siempre impracticable; existen bibliotecas de clases muy utilizadas, con éxito y sus clases pueden ser consideradas sensatamente como componentes reutilizables. A menudo, sin embargo, es más apropiado un componente formado por un grupo de clases relacionadas. Cuanto mayor es el esfuerzo para conectar un componente en un contexto, mayor es el beneficio que tiene que proporcionar el componente antes de que el esfuerzo merezca la pena. Deliberadamente se ha oscurecido la pregunta de si los componentes se construyen con objetos o con clases: todavía no se ha hecho una distinción clara entre tipo e instancia. La razón es que, dada nuestra amplia definición de componentes, cualquiera de los dos puede ser el caso. Una clase o colección de clases puede ser un componente a nivel de código fuente; el contexto en el que se utiliza un componente hace uso de las facilidades del lenguaje para crear los objetos de las clases. En otros casos, un componente podría proporcionar acceso a un objeto concreto de alguna clase ya creado, con o sin proporcionar también acceso a la posibilidad de crear más objetos de la clase. Recuérdese que un componente tiene una interfaz bien definida. Si el componente está formado por objetos y/o clases, la interfaz del componente normalmente no permite el acceso a todo lo que se encuentra en las interfaces de los objetos y las clases contenidos en él. El componente en sí pretende ser una abstracción encapsulada, y probablemente es la manera apropiada de ocultar parte o toda su estructura interna; por ejemplo, si está compuesto por clases u objetos y cómo. Pregunta de Discusión 13 Utilice la Web y cualquier otra fuente a su alcance para investigar una arquitectura de componentes conocida como JavaBeans, CORBA o DCOM. ¿Cuál es la arquitectura? ¿Qué estructura interna puede tener un componente, y cómo está definido su interfaz?
2.3
Herencia Hasta aquí, en realidad sólo se han visto las bases conceptuales de qué es lo que a veces se llama diseño basado en objetos. En esta sección, se van a considerar la herencia, que es la guinda del pastel, y el resto de elementos conceptuales que están por debajo del diseño orientado a objetos. La metáfora indica que está bien, y que, a menudo, la gente piensa que es importante, pero que en realidad ¡es menos sustancioso que lo que se ha visto hasta ahora! En esta sección se considerará la herencia como una característica técnica de los lenguajes orientados a objetos, útiles (de una forma limitada) para la reutilización de bajo nivel. El Capítulo 5 tratará sobre cómo se utiliza esto en el modelado.
26
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
El ejemplo utilizado aquí se ha obtenido del caso estudiado en el Capítulo 15. Se considera un sistema donde hay una clase cuyos objetos representan “Profesores adjuntos” y una clase cuyos objetos representan “Directores de Estudios”. Ahora un director de estudios es un tipo de profesor adjunto: expresamente, un director de estudios es un profesor adjunto que, además de las tareas normales de un profesor adjunto, es responsable de realizar un seguimiento del progreso de determinados estudiantes. En nuestro sistema, se supone que el objeto DirectorDeEstudios tiene que entender los mismos mensajes que el ProfesorAdjunto, y además tiene que ser capaz de responder al mensaje Dirigidos devolviendo una colección de objetos Estudiante. Suponga, que se tiene implementada la clase ProfesorAdjunto y que ahora hay que desarrollar el DirectorDeEstudios. Suponga (para concretar), que se está utilizando un lenguaje como C, donde las interfaces de una clase y su implementación se encuentran almacenados en archivos diferentes. ¿Qué hay que hacer para implementar la nueva clase de la manera más sencilla posible? Se podría cortar y pegar el código que define la interfaz de ProfesorAdjunto en un nuevo archivo de interfaz, y escribir la descripción de los nuevos mensajes al principio. De forma similar, se podría copiar el código que implementa la reacción de los ProfesoresAdjuntos ante los mensajes en el fichero de implementación de la nueva clase, y hacer entonces cualquier cambio y adiciones. Sin embargo, tal y como se ha dicho ya, tal reutilización de código mediante copiar y pegar tiene desventajas. La más importante es que si hay que hacer cualquier cambio en un código duplicado —por ejemplo, si se encuentra un error— se tiene que realizar el cambio en dos lugares en vez de en uno solo. Esto es absurdo, y es probable que esto nos lleve a errores. En vez de esto, los lenguajes de programación orientados a objetos permiten definir la nueva clase DirectorDeEstudios en función de la antigua clase ProfesorAdjunto. Simplemente se especifica que la clase DirectorDeEstudios es una subclase de ProfesorAdjunto, y se escribe sólo lo que pertenece a los atributos u operaciones extra del DirectorDeEstudios5. Más interesante, debido a que en el conocimiento de orientación a objetos cuyo código ejecutado por un objeto cuando recibe un mensaje está encapsulado, es posible que la clase DirectorDeEstudios implemente diferente comportamiento, en la recepción de algunos mensajes, del que implementa ProfesorAdjunto. Esto es, se puede dejar a un lado en el DirectorDeEstudios, algunos métodos del ProfesorAdjunto: además de heredar los métodos de ProfesorAdjunto, DirectorDeEstudios puede incluir un nuevo método que implemente la misma operación para la que ProfesorAdjunto ya tiene un método. Cuando un objeto de la clase DirectorDeEstudios recibe un mensaje solicitándole que ejecute la operación el método invocado, será la versión especializada proporcionada por la clase DirectorDeEstudios. Una subclase es una versión extendida y especializada de su superclase. Incluye las operaciones y los atributos de la superclase, y posiblemente algunos más.
P: ¿Cómo se define una subclase en su lenguaje? ¿Puede o no una clase ser una subclase directa de más de una clase (herencia múltiple)? 5
Algunos lenguajes, destaca Eiffel, permiten a las subclases borrar atributos u operaciones de una superclase, pero ahora esto se considera una mala idea.
CONCEPTOS DE OBJETOS
27
Terminología Se dice que: • • • • • •
DirectorDeEstudios hereda de ProfesorAdjunto. DirectorDeEstudios es una subclase (o clase derivada) de ProfesorAdjunto. DirectorDeEstudios es una especialización de ProfesorAdjunto. DirectorDeEstudios está más especializado que ProfesorAdjunto. ProfesorAdjunto es una superclase (o clase base) de DirectorDeEstudios. ProfesorAdjunto es una generalización de DirectorDeEstudios.
Todo esto significa casi lo mismo. A menudo la gente utiliza subclase/superclase como una descripción de una relación concreta entre clases, y especificación/generalización como una descripción de la relación entre los conceptos representados por las clases. Normalmente, ambos coinciden. Si bien, la relación conceptual no necesita mostrarse en la estructura de la clase. A la inversa, a veces la gente utiliza la herencia en una estructura de clase en casos donde la relación conceptual de generalización entre clases no existe. (No se recomienda esto: puede ser cómodo a corto plazo, pero casi siempre se paga el desorden a largo plazo). ¿Se puede decir que un objeto de la clase DirectorDeEstudios pertenece a la clase ProfesorAdjunto? Debería, si se piensa en clases como conjuntos de objetos. Cuando se habla de “un objeto de la clase ProfesorAdjunto” naturalmente se quiere incluir los objetos que también pertenecen a la clase DirectorDeEstudios —es demasiado pesado escribir todo el tiempo “la clase ProfesorAdjunto o cualquier subclase”—, además, es fundamental la idea de que un objeto de una subclase debería ser sustituible por un objeto de la superclase. Por lo tanto cada vez que se hable sobre los objetos que pertenecen a una clase se incluirán los objetos que pertenecen a las subclases. Por otro lado, es extremadamente útil poder hablar de “la clase de este objeto” como si el objeto perteneciese sólo a una clase. Por “la clase” de un objeto se entiende la clase más especializada a la que pertenece el objeto. ¡Esto quiere decir que no es menos cierto que el comportamiento de un objeto viene determinado por su clase!
2.4
Polimorfismo y ligadura dinámica Estos términos se utilizan con frecuencia indistintamente por la comunidad orientada a objetos. En realidad, son conceptos bastante diferentes —en general los lenguajes pueden tener cualquiera sin tener el otro—, pero en OO están relacionados, ambos tienen que ver con la herencia. Antes de nada, destacar que el simple hecho de que se puede definir una clase en función de otra, obteniendo reutilización de código, no tiene que tener implicaciones en la manera en que el código cliente maneja los objetos de aquellas clases. Podría ser bastante posible tener un lenguaje en el que la herencia fuese simplemente un dispositivo para la reutilización de código. Podrías definir DirectorDeEstudios en función de ProfesorAdjunto, pero todo el código cliente (y el compilador) tendrían que estar seguros de la clase de cualquier objeto con el que interactuase. Técnicamente, esto quiere decir que se podría tener un lenguaje con un tipo de herencia que sólo tuviese tipos monomórficos. Los lenguajes orientados a objetos, sin embargo, lo hacen mejor.
28
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
Polimorfismo Este término, derivado del griego, significa tener varias formas. En los lenguajes de programación, se refiere a la situación en la que una entidad podría tener uno de entre varios tipos. En una situación orientada a objetos, una variable polimórfica podría hacer referencia (en diferentes momentos) a objetos de varias clases distintas. Una función polimórfica puede tomar argumentos de diferentes tipos. Ahora bien, se ha dicho que se supone que un objeto de una subclase se puede utilizar en cualquier sitio donde se puede utilizar un objeto de la superclase. En particular, esto querría decir que un objeto subclase debería aceptarse como valor de una variable, si lo es un objeto de la clase base. En otras palabras, en un lenguaje orientado a objetos cualquier variable es polimórfica, ¡al menos en este sentido limitado! De forma similar, si alguna función (por ejemplo, un método) puede obtener un argumento de la clase B debería poder también obtener un argumento de cualquier clase C que es una subclase de B; de manera que cualquier función es polimórfica en este sentido limitado. Por ejemplo, una función que espera como argumento de un objeto ProfesorAdjunto no debería interrumpirse si se le pasa en su lugar un objeto DirectorDeEstudios. Pregunta de Discusión 14 Piense en cualquier lenguaje de programación que conozca (quizá C, o un lenguaje de programación funcional). ¿Tiene polimorfismo? ¿Es más o menos restrictivo que el polimorfismo OO aquí descrito? ¿(Cómo) Es útil?
De esta manera el polimorfismo libera una gran cantidad de duplicación de código. Sin embargo, realmente se asocia/hereda consigo mismo cuando se combina con la ligadura dinámica.
Ligadura dinámica El término “ligadura” en ligadura dinámica (o ligadura tardía —ambos se utilizan indistintamente—) hace referencia al proceso de identificar el código que debería ejecutarse como respuesta a algún mensaje. Para comprender esto, pongamos un par de ejemplos. Supongamos que la interfaz de ProfesorAdjunto incluye la operación puedoHacer(tarea: Tarea), que toma como argumento una tarea administrativa, y el Profesor-Adjunto retorna un valor lógico, que es verdadero si el profesor adjunto es capaz de realizar la tarea. Supongamos algo más (¡ligeramente irreal!), que el DirectorDeEstudios es capaz de realizar cualquier cosa, por lo que debería devolver siempre verdadero. Esto es, la subclase DirectorDeEstudios ha vuelto a implementar —teniendo prioridad— la operación puedoHacer, probablemente sustituyendo parte del código complejo que examina la información sobre los intereses o experiencia del profesor adjunto por código sencillo que simplemente devuelve verdadero. Ahora supongamos que profesoresAdjuntos es una variable que hace referencia a una colección de objetos ProfesorAdjunto. Esta colección puede, por supuesto, contener algunos objetos DirectorDeEstudios, ya que si se puede añadir un ProfesorAdjunto a profesoresAdjuntos se puede añadir un DirectorDeEstudios. Supongamos algunos clientes —una tarea que trata de encontrar a alguien que organice el seminario— que ejecutan algo parecido a:
CONCEPTOS DE OBJETOS
29
Por cada o en profesoresAdjuntos o.puedoHacer(organizaciónSeminario)
(Preste atención a la notación del punto para el envío de mensajes, que es común a muchos lenguajes. La expresión o.puedoHacer(organizaciónSeminario) indica que el mensaje puedoHacer(organizaciónSeminario) se envía al objeto o. Se puede utilizar también para representar el valor que devuelve o al emisor después de ejecutar el mensaje). Lo máximo que se puede asumir, observando el código, es que o es un ProfesorAdjunto. Recuerde la idea original de que un objeto encapsula su propio comportamiento, y que las clases se han introducido tan sólo como abreviatura. Si o está solo en la clase ProfesorAdjunto, el código de puedoHacer del ProfesorAdjunto debería ejecutarse: es decir, su comportamiento al recibir este mensaje. ¿Qué pasaría cuando el bucle para alcanzase un objeto o que pertenece en realidad a la clase DirectorDeEstudios? Parece claro, que en este caso se ejecutaría el código del DirectorDeEstudios. (Si el objeto o realmente tuviese consigo su propio código, esto pasaría automáticamente; y el hecho de que no sea se considera una posible optimización). Esto quiere decir, la misma sintaxis debería producir dos partes de código que se ejecutarían en dos momentos diferentes. El envío del mensaje se enlaza dinámicamente al código apropiado 6. Por último, consideremos otro ejemplo muy sencillo de ligadura dinámica, que también ilustra por qué se puede querer que un objeto se envíe un mensaje a sí mismo. Supongamos que la interfaz del ProfesorAdjunto incluye el mensaje habilidades, al que se supone que el objeto ProfesorAdjunto va a devolver como respuesta una colección de tareas que el ProfesorAdjunto es capaz de realizar. A lo mejor, el código de ProfesorAdjunto implementa esto de una forma parecida al siguiente pseudocódigo (donde self.puedoHacer(tarea) representa el resultado devuelto después de que se envíe a ese mismo objeto un mensaje con selector puedoHacer y argumento tarea): CosasQuePuedoHacer:= ColecciónVacía Por cada tarea en listaGlobalTareas Si (self.puedoHacer(tarea)) entonces añadir tarea a CosasQuePuedoHacer Si no hacer nada Devolver CosasQuePuedoHacer
Se puede dar prioridad a esta operación también en la nueva clase DirectorDeEstudios. Pero, ¿por qué debería hacerse? (Es menos probable que las prestaciones sean importantes). Supongamos que se deja tal cual, de manera que este código lo heredan sin cambios los objetos DirectorDeEstudios. Recordemos, sin embargo, que el DirectorDeEstudios priorizó la operación puedoHacer. ¿Qué pasaría si el objeto DirectorDeEstudios recibe el mensaje habilidades? Ejecutaría el código especificado arriba —no hay nada más específico. Esto hace que se envíe el mensaje puedoHacer a sí mismo. Es un objeto DirectorDeEstudios, de forma que cuando recibe el mensaje puedoHacer ejecutaría la versión especializada de código que está definida en la clase DirectorDeEstudios. Si se utiliza un lenguaje orientado a objetos puro, este comportamiento será secundario. Si se utiliza C hay que tener cuidado: sólo los métodos virtuales tienen este comportamiento. Al6
Esto, la mayoría de las veces, puede hacerse comprobando en tiempo de ejecución el tipo de objeto, aunque la investigación actual está progresando para permitir al compilador hacer la mayor parte del trabajo, lo cual es más eficiente.
30
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
gunas personas (incluyéndome a mí 7) están convencidas de que cuando se empieza la programación orientada a objetos en C uno se tiene que habituar a crear todos los métodos como virtuales 8, y aprender más tarde sobre los métodos no virtuales.
RESUMEN
Este capítulo es un recorrido rápido para motivarnos e introducirnos en la orientación a objetos. Se ha considerado qué es un objeto y cómo es el envío de mensajes entre objetos. Se ha mostrado que los objetos en un sistema pueden ser divididos útilmente en clases, en vez de considerarlos de forma individual, y se ha empezado a tratar la conexión entre objetos y componentes. Se ha discutido cómo puede definirse una clase utilizando la definición de otra clase mediante la herencia. Finalmente se han tratado las otras características de la herencia en los lenguajes orientados a objetos, su capacidad para proporcionar polimorfismo y ligadura dinámica. En el siguiente capítulo, se presentará el tema principal del libro, el Lenguaje Unificado del Modelado, que puede utilizarse para expresar la especificación y el diseño de los sistemas que se aprovechan de la orientación a objetos.
7 8
Stevens. Incluyendo (de manera especial) los destructores, pero nunca los constructores. ¿Por qué?
Capítulo
3 Estudio de un caso introductorio
En este capítulo se presenta UML, el Lenguaje Unificado del Modelado. Se pretende demostrar lo suficiente de UML, y la manera en que se utiliza, para especificar y diseñar sistemas, y establecer el contexto de los próximos capítulos. En el siguiente capítulo se discutirán los orígenes de UML y su papel en el proceso de desarrollo. En la Parte II se presentará UML con mayor profundidad, para lo que nos apoyaremos, a veces, en el estudio del caso sencillo aquí utilizado.
3.1
El problema La parte más difícil de cualquier proyecto de diseño es comprender la tarea que se está tratando. En este ejemplo se asume la siguiente situación: Ha sido contratado para desarrollar un sistema informático para la biblioteca de una universidad. La biblioteca actualmente utiliza un programa de 1960, escrito en un lenguaje obsoleto, lleva a cabo algunas tareas sencillas de gestión de los libros, y un índice de carnés, para la búsqueda del usuario. Se le solicita construir un sistema interactivo que mantenga estos dos aspectos en línea.
3.1.1
Clarificación de los requisitos
La especificación del problema dado antes es muy incierta, pero es la típica solicitud que se recibe al principio, cuando se plantea por primera vez un proyecto. Antes incluso de ponerse de acuerdo en abordar el diseño, es necesario un análisis detallado de los requisitos de los usuarios. Esta tarea de ingeniería de requisitos es compleja por gran variedad de razones: • usuarios diferentes tendrán distintas prioridades, a veces en conflicto. • los usuarios no suelen tener una visión clara y fácilmente expresable de lo que quieren: por ejemplo, les resulta difícil distinguir entre lo que hace un sistema existente y lo que tiene que hacer un sistema adecuado.
32
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
• es difícil imaginar un sistema del cual sólo se ha visto su descripción, por lo que los usuarios pueden pensar que la descripción es correcta cuando en realidad falta algo vital. • los responsables que se encargan de hablar con la mayoría de los desarrolladores pueden no tener experiencia directa de hacer el trabajo que hacen los usuarios del sistema. Pregunta de Discusión 15 ¿Cómo podría usted, como desarrollador, ayudar a superar cualquiera de estos problemas?
El tratamiento completo de la ingeniería de requisitos está fuera del alcance de este libro: un buen tratamiento es [44]. Después de algunas investigaciones esmeradas, surgen los siguientes hechos sobre los requisitos que debería satisfacer un buen sistema: • Libros y revistas La biblioteca tiene libros y revistas. Puede haber varias copias de un libro determinado. Algunos libros son sólo para préstamos a corto plazo. Todos los demás libros pueden ser prestados a cualquier socio de la biblioteca durante tres semanas. Sólo los socios del personal pueden tomar prestadas las revistas. Los socios de la biblioteca normalmente pueden tomar prestados hasta seis artículos de una sola vez, mientras que los socios del personal pueden tomar prestados hasta doce artículos de una sola vez. Regularmente llegan nuevos libros y revistas, y a veces se tienen que deshacer de los antiguos. Las revistas del año actual se envían para ser encuadernadas en volúmenes al finalizar el año. • Préstamos Es esencial que el sistema lleve un control de cuándo se prestan y devuelven los libros y las revistas, ya que el sistema actual lo hace. El nuevo sistema debería avisar cuando un libro ha sido prestado, pero no devuelto. En un futuro, se puede requerir que los usuarios puedan ampliar el préstamo de un libro si este no está reservado. • Hojeada de los libros El sistema debería permitir a los usuarios buscar un libro por tema, por autor, etc., para comprobar si hay una copia del libro disponible para ser prestado, y si no, lo reserva. Cualquiera puede hojear los libros en la biblioteca. Esto está mejor, pero todavía no queda claro cuáles son las diferentes tareas o quién necesita qué. Pregunta de Discusión 16 ¿Qué más preguntas serían necesarias?
Pregunta de Discusión 17 ¿Cuáles son las desventajas de diseñar un sistema que cumpla los requisitos anteriores, sin un mayor análisis?
ESTUDIO DE UN CASO INTRODUCTORIO
3.1.2
33
Modelo de casos de uso
Para que un sistema sea de alta calidad, tiene que cumplir las necesidades de sus usuarios1. De manera que vamos a hacer una aproximación al análisis de los sistemas orientada al usuario. Hay que identificar los usuarios del sistema y las tareas que deben acometer con el sistema. También hay que buscar información sobre qué tareas son las más importantes, de manera que se pueda planificar el desarrollo de acuerdo con esto. ¿Qué se quiere decir con “usuarios” y “tareas”? UML en realidad utiliza como términos técnicos actores y casos de uso. Un actor es un usuario del sistema que tiene un rol particular. (En realidad, un actor puede ser un sistema externo, que es como un usuario desde el punto de vista de nuestro sistema: el punto crucial es que se está diseñando alguien o algo externo al sistema, que interactúa con nuestro sistema y que puede hacerle peticiones). Por ejemplo, el sistema que aquí interesa tendrá un actor PrestatarioLibro representando a la persona que interactúa con el sistema para tomar prestado un libro. No queda claro si es un socio de la biblioteca o un bibliotecario que se comporta como un socio de la biblioteca, pero para el propósito actual no es necesario saberlo. Un caso de uso es una tarea que un actor necesita ejecutar con la ayuda del sistema, como son Tomar prestada copia de libro. Dese cuenta que el nombre sencillo puede ocultar un comportamiento bastante complejo con variedad de resultados: por ejemplo, tomar prestada una copia de un libro requerirá comprobar que el prestatario sea socio de la biblioteca y que él o ella no tenga ya el máximo número de libros permitido. Es posible que el caso de uso termine sin que el usuario haya conseguido tomar prestado un libro, pero se nombra el caso de uso según lo que pasaría en el caso normal, en el que todo es correcto. El detalle de cada caso de uso debería estar documentado, normalmente, en tercera persona y voz activa2. Por ejemplo: • Tomar prestada copia de libro Un PrestatarioLibro presenta un libro. El sistema comprueba que el prestatario potencial es socio de la biblioteca y que él o ella no tiene ya el máximo número de libros permitido. El máximo es seis, a menos que el socio sea un socio de la plantilla, en cuyo caso es 12. Si ambas comprobaciones son correctas, el sistema almacena que este socio de la biblioteca tiene esta copia del libro en préstamo. En caso contrario, rechaza el préstamo. Pregunta de Discusión 18 El sistema de biblioteca considerado en este capítulo está simplificado. Considerando las bibliotecas que conoce, sugiera qué más tendría que hacer o comprobar un sistema completo para implementar este caso de uso. Corrija la descripción del caso de uso, recordando que sólo debería incluir requisitos, no diseño.
1 Algunos autores prefieren hablar de necesidades de los clientes, debido a que el que paga tiene derecho a escoger; pero es probable que el cliente es realimente de los usuarios, ¡y es el cliente de carácter resuelto quien mantiene la idea de que un sistema es de alta calidad cuando los usuarios lo maldicen! 2 “El sistema comprueba” en vez de “comprobar” o “... es comprobado por el sistema”
34
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
Observación sobre las interfaces de usuario El diseño de la interfaz de usuario es un campo muy amplio e interesante, y queda fuera del alcance de este libro. Para el diseñador de la interfaz de usuario, tiene gran importancia si el libro lo toma prestado un socio de la biblioteca o un bibliotecario. Se asume que la tarea que aquí importa es construir el sistema subyacente, proporcionando la funcionalidad que invocará la interfaz de usuario, construida por otra persona. Esta no es una suposición irracional: las interfaces de usuario son más propensas a cambiar que el resto del sistema, y la separación entre la interfaz de usuario y el sistema subyacente permiten que las modificaciones o sustituciones del IU sean más factibles. Además, tiene sentido que los diseñadores de interfaces de usuario expertos sean los que realicen el diseño de la interfaz de usuario. Para más información sobre el diseño de interfaz de usuario véase, por ejemplo, [47] o [15]. Llegados a este punto, se puede almacenar la información gráficamente, en un diagrama de casos de uso del sistema, como se muestra en la Figura 3.1. La notación es autoexplicativa: los muñecos representan a los actores, los óvalos casos de uso, y hay una línea entre un actor y un caso de uso si el actor puede participar en el caso de uso.
Reservar libro
Hojear
PrestatarioLibro
Hojeador
Tomar prestada copia de libro
Devolver copia de libro
Ampliar préstamo Actualizar catálogo Tomar prestada revista
PrestatarioRevista
Devolver revista
Figura 3.1 Diagrama de casos de uso para la biblioteca.
Bibliotecario
ESTUDIO DE UN CASO INTRODUCTORIO
35
La gestión de los diagramas de UML —dibujarlos, compartirlos, mantenerlos consistentes— puede ser más fácil utilizando una herramienta CASE (Ingeniería del Software Asistida por Computadoras) que soporte UML3. Sin embargo, para los sistemas pequeños es suficiente un trozo de papel o la parte de atrás de un sobre enorme. Tenga cuidado con hacer diagramas muy complejos, cosa fácil con herramientas potentes. Si un diagrama es demasiado complejo para dibujarlo a mano, probablemente será bastante difícil pensar en él claramente. En este caso, probablemente debería ser dividido, o dibujado en un nivel de abstracción mayor, o ambos. En este caso, se han agrupado todas las agregaciones y eliminaciones de libros de la Biblioteca, todos los envíos de las revistas, etc., en un único caso de uso Actualiza catálogo. Cuando se considere esta funcionalidad en detalle, puede que se decida separar esto en casos de uso diferentes. En esta fase, cada caso de uso debería documentarse, al menos en términos generales. Se destaca que sólo hay que decidir lo que el sistema debería hacer, no cómo hacerlo. Por ejemplo, en Tomar prestada copia de libro no se menciona cómo el sistema debe almacenar la información sobre el préstamo; sólo se dice lo que tenía que almacenarse.
P: Escribe las descripciones de algunos de los casos de uso de la Figura 3.1. No se invente requisitos
Esto puede parecer obvio —pero cuando se analizan los casos de uso, probablemente, se pensará en muchas cosas útiles que podría hacer el sistema. Es fundamental evitar confundir las cosas que el sistema tiene que hacer porque lo dice el cliente, con las cosas que se piensa que podría o debería hacer. Puede ser útil hacer una lista de preguntas y posibilidades con cada caso de uso, para discutirlo con el cliente: pero no se deben añadir características dudosas en los propios casos de uso.
3.2
Alcance e iteraciones Ahora se tiene una idea razonablemente clara de lo que haría un sistema ideal. Sin embargo, la experiencia ha demostrado que la construcción de sistemas mediante el enfoque “big bang” de una versión, donde los desarrolladores pretenden entregar el sistema ideal en su primera y única entrega, es extremadamente arriesgado. Para controlar los riesgos, es mejor proponerse obtener el sistema ideal en varios pasos o iteraciones. La primera iteración termina con la entrega de un sistema sólo con las funcionalidades más básicas y esenciales; las iteraciones posteriores mejoran el sistema. Se discutirá este tema con mayor detalle en el siguiente capítulo, pero puede que usted quiera discutirlo ahora. Pregunta de Discusión 19 ¿Qué ventajas e inconvenientes observa en la adopción de un enfoque iterativo para el desarrollo de este sistema?
3
Aquí se utiliza Rational Rose, pero hay otros muchos. Hay algunos enlaces en la página de inicio de este libro.
36
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
Uno de los principales propósitos de los casos de uso es ayudar a identificar las líneas divisorias adecuadas entre las iteraciones: una iteración puede entregar parte del sistema permitiendo que se cumplan ciertos casos de uso, pero no otros. En este caso, supongamos que después de discutir con el cliente las prioridades, se decide que la primera iteración debería proporcionar los siguientes casos de uso: • • • •
Tomar prestada copia de libro Devolver copia de libro Tomar prestada revista Devolver revista
El diagrama de casos de uso, limitado para la primera iteración del sistema aquí tratado, se muestra en la Figura 3.2. Lo que viene a continuación es, de nuevo, un breve planteamiento de los requisitos de la primera iteración, descartando las irrelevancias. En un proyecto real se podría preparar un documento como este que describe todos los casos de uso que se proporcionan en la primera iteración (por ejemplo, como parte de un documento de contrato) o se podrían utilizar las descripciones de los casos de uso y listar los nombres de los casos de uso a proporcionar. • Libros y revistas La biblioteca tiene libros y revistas. Puede haber varias copias de un libro determinado. Algunos libros son sólo para préstamos a corto plazo. Todos los demás libros pueden ser prestados a cualquier socio de la biblioteca durante tres semanas. Los socios de la biblioteca normalmente pueden tomar prestados hasta seis artículos al tiempo, mientras que los socios del personal pueden tomar prestados hasta 12 artículos de una sola vez. Sólo los socios del personal pueden tomar prestadas revistas. • Préstamo El sistema debe llevar un control de cuándo se prestan y se devuelven los libros y las revistas, respetando las normas descritas anteriormente. En un proyecto real, en algunos casos, se mirarían cuestiones de recursos, incluyendo la selección de lenguajes, herramientas y aplicaciones adecuados. Para los propósitos de este libro, se asumirá que el resultado de esta revisión es que el sistema se implementará en un lenguaje orientado a objetos, y el diseño se desarrollará en UML. Se ignorarán las cuestiones de bases de datos: véase el Panel 3.2 sobre Persistencia, hacia el final de este capítulo.
Tomar prestada revista
Tomar prestada copia de libro
PrestatarioLibro
PrestatarioRevista Devolver copia de libro
Figura 3.2 Diagrama de casos de uso para la primera iteración.
Devolver revista
ESTUDIO DE UN CASO INTRODUCTORIO
37
Pregunta de Discusión 20 La elección para este sistema, en la primera iteración, ha sido la de introducir técnicas interesantes de OO sin perder mucho tiempo en cosas que quedan fuera del alcance del mismo. Ignorando estas restricciones, ¿qué otras opciones se ven para esta primera iteración del sistema? ¿Cree que alguna de ellas es mejor?
3.3
Identificación de clases En la jerga estándar del análisis a menudo se habla de las abstracciones clave del dominio. El término dominio cubre el área de aplicación que aquí se cubre, es decir, la biblioteca. Se podría hablar de clases clave del dominio; se utiliza el término abstracción en vez de clase para enfatizar que se describen sólo los aspectos del dominio que son importantes para la aplicación. (Sin embargo, recuerde que se argumenta en el Capítulo 2 que todos los buenos módulos, incluyendo las clases, son abstracciones). Por lo tanto, escrito de manera más sencilla, se están buscando las características y los hechos de la biblioteca, que son importantes en el sistema que se está construyendo. La identificación de las clases correctas es una de las habilidades principales del desarrollo OO. Es crucial para la construcción de software originalmente extensible con reutilización. Se comienza el proceso de identificación de las abstracciones clave del dominio utilizando el siguiente camino, que es conocido como la técnica de identificación de nombres (bastante largo de nombrar). Se toma una declaración de los requisitos del sistema concisa y coherente y se subrayan sus nombres y locuciones nominales; es decir, se identifican las palabras y frases que denotan cosas. Esto proporciona una lista de clases candidatas, que se puede reducir poco a poco y modificar para obtener una lista inicial de clases del sistema. Esta es sólo una técnica entre muchas que se utilizan para identificar clases. En el Capítulo 5 se introducirán varias técnicas más que se pueden utilizar para seleccionar clases y validar las elegidas. En esta etapa se trata de obtener simplemente una lista aproximada de posibles candidatos. Se toman las frases dadas en la subdivisión 3.1.1 y se subrayan los nombres y las locuciones nominales, obteniendo el resultado de la Figura 3.3 (por supuesto, también se pueden utilizar las descripciones de los casos de uso). Libros y revistas La biblioteca tiene libros y revistas. Puede haber varias copias de un libro determinado. Algunos libros son sólo para préstamos a corto plazo. Todos los demás libros pueden ser prestados a cualquier socio de la biblioteca durante tres semanas. Los socios de la biblioteca normalmente pueden tomar prestados hasta seis artículos al tiempo, mientras que los socios de la plantilla pueden tomar prestados hasta doce artículos de una sola vez. Sólo los socios del personal pueden tomar prestadas revistas. Préstamo El sistema debe llevar un control de cuándo de prestan y se devuelven los libros y las revistas, respetando las normas descritas anteriormente. Figura 3.3 Nombres y locuciones nominales en la biblioteca.
38
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
A continuación, se descartan aquellas que “obviamente” no son clases candidatas por cualquier razón. (Por supuesto, cuando se familiarice con la técnica no subrayará aquellas cosas que no sean adecuadas). Se considera cada una en singular. Se hará esto de manera más general y sistemática en el Capítulo 5. En este caso particular, se descartan: • Biblioteca, porque está fuera del alcance de nuestro sistema 4. • Préstamo a corto plazo, porque un préstamo (a corto plazo o cualquiera) realmente es un evento, el préstamo de un libro a un usuario, que con lo que se sabe no es un objeto útil en este sistema. • Miembro de la biblioteca, lo cual es redundante: significa lo mismo que socio de la biblioteca, el cuál lo guardamos. • Semana, porque es una medida de tiempo, no una cosa. • Artículo, porque es impreciso: cuando se clarifica se ve que se trata de libro o revista; • Tiempo, porque queda fuera del alcance del sistema. • Sistema, porque es parte del meta-lenguaje de la descripción de requisitos, no parte del dominio. • Norma, por la misma razón. Esto deja: • • • • •
Libro Revista Copia (de un libro) Socio de la biblioteca Socio de la plantilla
como primera lista aproximada de clases. En este momento se utilizarían las tarjetas CRC para almacenar las responsabilidades provisionales de las clases e identificar mejoras en la lista, pero se pospone su discusión al Capítulo 5. Hay que destacar que los socios de la biblioteca y los socios del personal son gente que utiliza el sistema. Por supuesto, un usuario del sistema no siempre tiene que estar representado en el sistema (y de hecho en este caso, no están representados los bibliotecarios), por lo que cada uno tiene que considerar en cada caso si es deseable hacer esto. En este caso, hay un límite en el número de libros que puede tomar prestado un socio de la biblioteca o un socio de la plantilla, por lo que está claro que el sistema tiene que mantener información sobre estos usuarios. Es menos obvio el comportamiento que debería tener el objeto que representa a un usuario. Aquí se utilizará una técnica que a menudo es útil, aunque no es apropiada para cualquier sistema. Se hace que los objetos del sistema que representan actores sean responsables de ejecutar acciones en nombre de estos actores. Por ejemplo, cuando el socio de la biblioteca Jo Bloggs toma prestada una copia de un libro, se enviará el mensaje tomarPrestado(laCopia) al objeto SocioBiblioteca que representa a Jo Bloggs. (La interfaz de usuario, que no se modela en este capítulo, es quien envía el mensaje). Este objeto SocioBiblioteca será entonces responsable de ejecutar todo lo que sea necesario para almacenar (o denegar) el préstamo, mediante el envío de mensajes a otros objetos del sistema. 4
La cuestión de si hay un objeto sistema “principal” se discutirá en el Capítulo 5.
ESTUDIO DE UN CASO INTRODUCTORIO
39
Por supuesto, este proceso de identificación de objetos y clases no es una ciencia exacta. Se podrían haber descartado los mismos candidatos por otras razones. De hecho, en esta etapa no se tiene que tomar como algo absolutamente correcto y no se trata de llevar a cabo el diseño del sistema: se está intentando identificar los objetos importantes del mundo real dentro del dominio del sistema. Pregunta de Discusión 21 ¿Está en desacuerdo con alguna de las decisiones que se han tomado? ¿Por qué?
Es importante ser claro con lo que quiere decir cada término (nombre de clase, etc). Algunas metodologías dictan que en esta etapa se debe redactar una entrada en el diccionario de datos para definir cada término. Desde nuestra experiencia, la mayoría de los proyectos ni soportan ni necesitan este paso burocrático. Se asume que cualquier documento que se utilice para llevar un seguimiento del diseño (quizá una pizarra o parte de atrás de un sobre, quizá un documento “real” o un documento de diseño a ordenador) incluye cualquier anotación o información detallada que pueda ser necesaria para que cualquiera en el proyecto tenga el mismo conocimiento de los términos comunes. En las primeras etapas de un proyecto puede que haya que quedar con todo el equipo de trabajo para ponerse de acuerdo en los términos que todavía no hayan sido definidos completamente. Esto está bien. Lo que hay que evitar es que haya malentendidos, y que cada uno tenga una idea diferente de lo que significan.
3.4
Relaciones entre clases A continuación se identifican y nombran las relaciones del mundo real importantes o asociaciones entre nuestras clases. Se hace esto por dos razones: 1. Para clarificar el entendimiento del dominio, mediante la descripción de los objetos en función de cómo trabajan juntos. 2. Para comprobar el acoplamiento en el sistema final, es decir, para asegurar que se siguen los principios correctos en la modularización de nuestro diseño, tal y como se describió en el Capítulo 1. La segunda de estas razones requiere alguna explicación. Si se cree que un objeto está muy relacionado con otro, probablemente es bastante menos dañino para la clase que lo implementa, depender de la clase que implementa al otro objeto, es decir, que estén muy acoplados. Hay, al menos, dos justificaciones para esto: 1.
Si los objetos del dominio están relacionados conceptualmente, entonces la persona encargada del mantenimiento que reconoce esta relación, es probable que espere una dependencia entre las clases correspondientes; por lo que él/ella tendrá en cuenta la posibilidad de una dependencia cuando se modifiquen las clases. La probabilidad de aparición de un problema inesperado debido a la dependencia es, así, relativamente baja.
2. Si los objetos de dominio están relacionados conceptualmente, es probable que una aplicación que pueda reutilizar una de las clases pueda reutilizar también la otra; por lo que,
40
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
aunque cualquier acoplamiento en un sistema complica la reutilización, el efecto será relativamente benigno. Recuerde que se dijo que un argumento fundamental para la OO es que la estructura de un sistema OO refleje la estructura de la realidad: este es un ejemplo concreto del beneficio. En este caso se puede ver que: • • • •
una copia es una copia de un libro. un socio de la biblioteca toma prestada/devuelve una copia. un socio de la plantilla toma prestada/devuelve una copia. un socio de la plantilla toma prestada/devuelve una revista.
Se puede almacenar la información gráficamente en el modelo de clases de UML en la Figura 3.4. Este modelo de clases, en realidad, almacena un poco más de la información que se ha mencionado: también muestra las multiplicidades de las asociaciones. Por ejemplo, cada copia es una copia de sólo un libro, definido unívocamente, por lo que el dígito 1 aparece en el extremo de Libro en la asociación entre Libro y Copia. Por otro lado, puede haber una o más copias de un determinado libro (según cabe presumir, si no hay copias el libro no pertenece al sistema, pero esta es una presunción que tiene que ser revisada), por lo que aparece en el otro extremo de dicha asociación 1..*. Esto se tratará en detalle en el Capítulo 5.
Libro
1 es una copia de 1..*
SocioBiblioteca
toma prestado/devuelve 0..1
Copia
0..* 0..*
toma prestado/devuelve 0..1
SocioPlantilla
toma prestado/devuelve 0..1
Revista
0..*
Figura 3.4 Modelo de clases inicial de la biblioteca.
ESTUDIO DE UN CASO INTRODUCTORIO
41
Destacar que el diagrama no dice si un objeto Copia conoce (depende) del correspondiente Libro, o viceversa, o ambos: es decir, no dice nada sobre la navegabilidad de las asociaciones. Pregunta de Discusión 22 ¿En qué dirección(es) piensa que deben navegarse las asociaciones, teniendo en cuenta que no se debe introducir acoplamiento innecesario? Considere esto de nuevo al final del capítulo.
Finalmente, se puede destacar que SocioDePlantilla tiene las mismas asociaciones que SocioBiblioteca, y esto coincide con nuestra idea de que un socio de plantilla es un tipo de socio de biblioteca. El almacenamiento de todo esto en el diagrama de clases clarificará nuestro conocimiento de la situación, hay una relación de generalización entre SocioBiblioteca y SocioDePlantilla. Se puede, o no elegir implementar esta relación de generalización utilizando la herencia (es decir, haciendo que SocioDePlantilla sea una subclase de SocioBiblioteca) —esta decisión de diseño dependerá de la profundidad de conocimiento del sistema que actualmente se tiene—. Si se almacena la generalización en la notación de UML, se obtiene un modelo de clases bastante más organizado, tal y como se muestra en la Figura 3.5.
Libro
1 es una copia de
1..*
SocioBiblioteca
toma prestado/devuelve 0..1
SocioPlantilla
0..*
toma prestado/devuelve 0..1
Copia
Revista
0..*
Figura 3.5 Modelo de clases de la biblioteca revisado.
42
3.5
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
El sistema en acción Hasta aquí se ha hecho un boceto de la estructura estática del sistema, pero todavía no se ha descrito el comportamiento dinámico: por ejemplo, se tiene todavía que almacenar cómo los objetos del sistema trabajan juntos para permitir al usuario tomar prestada una copia de un libro. No hay conexión activa entre los casos de uso con los que se empieza y los objetos que forman el sistema según se haya decidido. En UML se pueden utilizar los diagramas de interacción para mostrar cómo pasan los mensajes de un objeto a otro para ejecutar algunas tareas —por ejemplo, para realizar un determinado caso de uso—. No se tiene que hacer esto para todos los casos de uso, y hay proyectos que no necesitan que se haga para ninguno: se debería utilizar un diagrama de interacción cuando se piense que se obtiene un beneficio con mayor peso que los costes. Por ejemplo, si un caso de uso es particularmente complicado, o si no hay duda de cuál de las dos realizaciones es mejor, entonces los diagramas de interacción pueden ayudar a aclarar las ideas. Como ejemplo, se considerará con más detalle qué pasa cuando un socio de la biblioteca toma prestada una copia de un libro. En el mundo físico, un socio de una biblioteca probablemente llegará a la mesa de trabajo con una copia física de un libro, y por medio de la interfaz del usuario del sistema habrá alguna forma de identificar al socio de la biblioteca y a la copia concreta. (Esto puede ser por medio de un lector de tarjetas y un lector de código de barras, o por una persona que introduce los detalles de identificación en un GUI: lo que sea). Se puede partir de la situación en que se tiene un cierto objeto SocioBiblioteca (llamado elSocioBiblioteca) y un cierto objeto Copia (llamado laCopia). Recuerde que se dijo anteriormente que se permitiría a los objetos SocioBiblioteca actuar en nombre de los socios de la biblioteca, por lo que esta interacción empieza cuando un actor SocioBiblioteca envíe un mensaje a elSocioBiblioteca. El mensaje especificará el servicio que se requiere: en este caso, pedir prestada laCopia. Llamemos al mensaje pedirPrestado(laCopia). El sistema tiene que comprobar que el SocioBiblioteca puede pedir prestado otro libro: una de las tareas de el SocioBiblioteca será llevar a cabo esta comprobación. (Esto probablemente se implementará comparando el número de artículos que el usuario actualmente tiene prestados, que debe ser almacenado como un atributo de la clase SocioBiblioteca, con el número de artículos máximo permitido: pero de forma deliberada no se toma esta decisión en detalle en esta etapa). Probablemente, esto se debería representar haciendo que elSocioBiblioteca se envíe a sí mismo un mensaje, que dice okTomarPrestado. A continuación se quiere actualizar la información del sistema sobre cuántas copias de ese libro hay en la biblioteca, ya que ahora hay una menos. Esto afecta a cualquier objeto Libro que esté asociado con laCopia por medio de la asociación “es una copia de”: llamemos a este objeto elLibro. Supongamos que elSocioBiblioteca informa a laCopia que ha sido prestado enviándole el mensaje pedirPrestado, y que laCopia le envía entonces a elLibro un mensaje diciendo que se ha prestado una copia del libro, llamado prestado(). No hay que hacer nada más: todos estos mensajes reciben una respuesta indicando que no ha habido error. La interfaz de usuario, de alguna manera, indicará al prestatario que todo es correcto, y el prestatario se llevará el libro físicamente.
P: ¿De qué otras maneras se podría implementar este comportamiento? ¿Piensa que alguna de las alternativas es mejor? ¿Por qué?
Una manera —mucho más legible que la anterior— para describir esto en UML es mediante el diagrama de secuencia de la Figura 3.6. Un diagrama de secuencia describe una parte del
ESTUDIO DE UN CASO INTRODUCTORIO
elSocioBiblioteca:
laCopia: Copia
43
elLibro: Libro
SocioBiblioteca
unSocio: PrestatarioLibro tomarPrestado(laCopia) 1: okTomarPrestado
2: okTomarPrestado prestado
Figura 3.6 Interacción mostrada en un diagrama de secuencia.
comportamiento del sistema —normalmente un caso de uso, o parte de uno— indicando qué mensajes se pasan entre los objetos y en qué orden deben darse (lea los mensajes de arriba a abajo de la página). En un proyecto real probablemente no se necesitará desarrollar un diagrama de secuencia para una interacción tan sencilla como esta. Los diagramas de secuencia pueden ser mucho más expresivos de lo que se ha mostrado; los detalles se encuentran en los Capítulos 9 y 10. Por ejemplo, en este caso los mensajes se tienen que dar en un orden determinado: hay un único hilo de actividad y las actividades no se pueden dar en paralelo. Además, sólo interesa el orden en el que tienen lugar las cosas. Mediante el ajuste de tiempos a las diferentes actividades es posible también tratar las cuestiones que surgen en los problemas de tiempo real. Hay otras maneras de ver este tipo de interacción. En los Capítulos 9 y 10 se ve cómo almacenar la misma información en un diagrama de colaboración: cada manera tiene sus propias ventajas.
P: Desarrolle un diagrama de secuencia para el escenario alternativo en el que okTomarPrestado devuelve falso, debido a que el socio de la biblioteca tiene ya el número máximo de libros.
PANEL 3.1
Diseño por contrato 1
En este panel se considera cómo se pueden guardar decisiones sobre cuál debería ser el comportamiento de una operación, con más detalle que dar simplemente su nombre y los tipos de sus argumentos y valores de retorno.
Pre- y post-condiciones Un problema fundamental es que los tipos en un lenguaje de programación son sólo una manera aproximada de describir las propiedades que deberían tener los atributos y las
44
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
operaciones de un objeto. Por ejemplo, el mensaje okTomarPrestado no tiene argumentos y devuelve un valor falso. Esto está implementado para que siempre devuelva el valor falso; sin embargo, el sistema de la biblioteca no va a funcionar correctamente con tal implementación, ¡porque nunca permitirá a un socio de la biblioteca tomar prestado un libro! El problema es que, aunque se quiera por todos los medios que el valor devuelto por el método sea lógico, esto no es todo lo que se quiere: también se quiere que tenga alguna conexión particular con el estado del objeto. Se pueden expresar condiciones extra, o restricciones, en las operaciones utilizando las precondiciones y las post-condiciones. Una precondición describe algo que tiene que ser verdadero cuando se invoca una operación; es un error si se invoca la operación en caso contrario. La precondición podría incluir el estado actual del objeto (los valores de sus atributos) y/o los argumentos de la operación. Es decir, la precondición describe lo que solicita la operación. Una post-condición describe algo que tiene que ser verdadero cuando retorna la operación; la implementación de la operación es errónea si su post-condición alguna vez es falsa después de que se haya invocado con la precondición verdadera. La post-condición podría incluir el valor devuelto por la operación, los argumentos de la operación, y el estado del objeto tanto antes como después de la operación. Es decir, la post-condición describe lo que promete la operación. Considere otra vez la operación okTomarPrestado en SocioBiblioteca. El valor de retorno debería ser verdad si el socio tiene menos libros prestados de los permitidos, en otro caso, debería ser falso. Se puede incluso decir que la implementación de la operación da por hecho, desde el principio, que el socio no tiene más libros en préstamo de los permitidos. Suponga que SocioBiblioteca tiene los atributos númeroDeLibrosPrestados: entero y maxLibrosPrestados: entero. Se podría documentar el comportamiento de la operación okTomarPrestado utilizando una precondición y una post-condición que en español sería de la siguiente manera: OkTomarPrestado Pre: númeroDeLibrosPrestados es menor o igual que maxLibrosPrestados Post: el valor de retorno es verdadero si númeroDeLibrosPrestados es menor que maxLibrosPrestados, si no falso. El estado del objeto no cambia.
Por supuesto, si tiene planeado escribir muchas pre- y post-condiciones, ¡naturalmente querrá una notación más concisa que esta! Las opciones incluyen expresiones lógicas de un lenguaje de programación, o instrucciones en un lenguaje de propósito especial; el Capítulo 6, que también trata el almacenamiento de restricciones en UML, tiene un panel sobre el Lenguaje de Restricción de Objetos, OCL (Object Constraint Language, Lenguaje de Restricción de Objetos, LRO), que está diseñado para trabajar correctamente con UML. Pregunta de Discusión 23 Si los argumentos de una operación o el valor de retorno del mismo son (como es normal) objetos en sí, en vez de tener los valores de un tipo base como entero o lógico, ¿qué se podría decir sobre estos objetos en las pre- y post-condiciones?
ESTUDIO DE UN CASO INTRODUCTORIO
Invariantes de clase En el ejemplo, la precondición no era realmente específica de esta operación. Siempre es un error que un socioBiblioteca tenga en préstamo más libros de los permitidos. En vez de indicar esto como precondición de cada operación, se puede simplemente dejar como un invariante de clase. Esto es, se pone como documentación de la clase socioBiblioteca, donde un objeto válido de la misma siempre tiene el valor del atributo númeroDeLibrosPrestados menor o igual que el valor del atributo maxLibrosPrestados. Posiblemente, esta condición podría ser violada por el objeto cuando está en medio del procesamiento de un mensaje, pero tiene que ser reestablecido cuando termina el tiempo de procesamiento, de manera que siempre mantiene cualquier mensaje que se le envía al objeto en un momento determinado.
P: ¿Cómo comprobaría que el invariante de la clase se mantiene siempre? ¿Tipo o restricción? No hay un abismo entre los significados de tipo de algo y una restricción que debería cumplirse. Ambos, el tipo y la restricción describen lo que el elemento puede ser. El tipo de un objeto describe qué atributos y operaciones tiene y sus tipos. (Debido a que la definición de una clase proporciona también esta información, a menudo se confundirá el tipo de un objeto con su clase —pero recuerde que la clase de un objeto especifica también su implementación). Una restricción —en este caso, un invariante de clase— dice algo más; por ejemplo, puede decir algo sobre la conexión entre los diferentes atributos. De forma similar, el tipo de una operación dice cuáles son los tipos de los argumentos y cuál es el tipo del resultado. Una restricción sobre una operación toma la forma de una precondición y una post-condición. Se espera que una operación sólo se ejecute si se le pasan argumentos del tipo correcto, es decir, se espera que se ejecute sólo si se cumple su precondición. Se espera nada más que devuelva un valor del tipo correcto, por tanto, se espera que devuelva un valor que cumpla la postcondición. De manera que, cuando se intenta describir las cosas permitidas en un determinado contexto —como objetos de una clase, o como implementaciones de una operación— primero se fijan por encima, utilizando el sistema de tipos de tu lenguaje de programación. Si esto no es lo suficientemente preciso para sus necesidades, simplemente añada una restricción para expresar lo que quiere decir. En el extremo —un lenguaje como Smalltalk, que no tiene escritura estática— todo lo que se quiera decir sobre lo que son los valores adecuados en una expresión tiene que estar en una restricción. Los lenguajes como Java y C, que tienen tipificación estática, permiten que la mayoría de comprobaciones de rutinas las realice el compilador. Sólo se escriben restricciones para los bits más importantes. La desventaja de las restricciones, comparadas con los tipos, es que normalmente no hay comprobación automática para ver que se satisfacen las restricciones, aunque una excepción de esto es Eiffel. En muchos lenguajes se puede utilizar una aserción: expresiones lógicas de tu lenguaje de programación que puede comprobarse en tiempo de ejecución, quizá sólo cuando está activada la depuración.
45
46
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
3.5.1
Cambios en el sistema: diagramas de estado
Probablemente habrá notado que el estado del objeto libro puede cambiar cuando se presta con éxito una copia: el libro puede pasar de estar disponible para prestar (hay una copia en la biblioteca) a no estar disponible para prestar (todas las copias están prestadas o reservadas). (En realidad esto no es muy importante en esta iteración, pero será importante cuando se implemente hojeada o reservas en futuras iteraciones del desarrollo). Se puede almacenar esto utilizando un diagrama de estado como el que se muestra en la Figura 3.7.
devuelto()
devuelto() no está disponible para prestar
está disponible para prestar prestado()[última copia]
prestado()[no última copia]
Figura 3.7 Diagrama de estados para la clase Libro.
Esta notación muestra que si el prestar una copia provoca un cambio de estado en el libro o no, depende de si todavía hay otras copias en la biblioteca. Se ha utilizado español informal para expresar en este caso la condición. Por supuesto, en cierto sentido, cualquier cosa que cambie algún dato de un objeto provoca un cambio de estado. Lo que se está haciendo en el diagrama de estados es almacenar los cambios de estado signifiacativos: aquellos que cambien cualitativamente el comportamiento del objeto. Se volverá a la cuestión de decidir que cambios son significativos en el Capítulo 11. Pregunta de Discusión 24 ¿Se ha almacenado suficiente información? ¿Cree que se podría querer saber cuántas copias de un libro hay disponibles para prestar? ¿Qué consecuencias habría si se intentara modelar esto?
3.5.2
Trabajo adicional
Al desarrollar los diagramas de estado y de secuencia, se han identificado ciertos mensajes que tienen que comprender nuestros objetos; esto es, ciertas operaciones de nuestras clases. Por supuesto, hay que llegar más lejos considerando las otras iteraciones del sistema. Se puede incluso identificar los datos que se encapsulan en el objeto, incluyendo los datos que implementan las asociaciones que se han mostrado, tales como referencias a otros objetos. Toda esta información
ESTUDIO DE UN CASO INTRODUCTORIO
47
puede añadirse al modelo de clases: se pueden listar los atributos y operaciones de una clase en el icono de la clase. Sin embargo, se pospone la discusión de cómo hacer esto en UML hasta el Capítulo 5. Una vez que se ha identificado cómo se llevan a cabo todos los casos de uso, a nivel de los mensajes que se pasan y el orden en que lo hacen, es bastante directo implementar las clases. Esto proporciona la primera iteración de nuestro sistema, que puede ser probado por los desarrolladores y los usuarios para identificar cualquier malentendido u otros fallos. Más iteraciones del desarrollo producirían un sistema que se acerca al ideal. PANEL 3.2
Persistencia
Hasta aquí no se ha tenido en cuenta el importante asunto de la persistencia de los objetos. Por supuesto, debe ser posible apagar el sistema y reiniciarlo sin perder la información sobre los socios de la biblioteca, libros y revistas, etc. Los objetos que deben durar más que el tiempo de vida de una instancia del programa en ejecución se denominan persistentes. Los objetos persistentes potencialmente pueden ser utilizados también por programas diferentes, pero este no es el tema a tratar aquí. En casos muy sencillos, los objetos pueden almacenarse simplemente en filas: un objeto puede escribirse a sí mismo en un archivo, y puede reconstruirse un objeto leyendo el archivo. Los detalles de cómo hacer esto dependen del lenguaje: muchos, pero no todos los lenguajes orientados a objetos, proporcionan mecanismos estándar, en cuyo caso el desarrollador no tiene que preocuparse de asuntos tales como los formatos de los archivos. Sin embargo, este mecanismo no es adecuado para sistemas que necesitan persistencia de objetos no trivial; es inflexible e ineficiente.
P: ¿Cómo se puede hacer este almacenamiento en su lenguaje? (Consejo: en Java, mire la interfaz Serializable; en Smalltalk80, mire el método storeOn:aStream; en C mire en sus bibliotecas de clase). Escriba un programa sencillo que permita crear un objeto y cambiar su estado; después escríbalo en un archivo y reconstruya el objeto desde el fichero.
P: ¿Qué desventajas tiene este método? Considere, por ejemplo, cómo se puede tratar con objetos que tienen enlaces a otros objetos.
Una opción más seria es utilizar una base de datos, que proporciona mucho más que sólo persistencia. Por ejemplo, una base de datos proporcionará soporte para transacciones —conjunto de actualizaciones que deben tener éxito o fracasar juntas—. Lo más común es utilizar una base de datos relacional. De ningún modo esto es directo, ya que hay un error de impedancia entre SQL y los lenguajes de programación orientados a objetos. Los datos se almacenan en tablas que deben ser traducidos a objetos, lo que es útil en el sistema orientado a objetos: el desarrollador no puede clasificar ciertos objetos como persistentes y esperar que los cambios en ellos sean permanentes. Sin embargo, cada vez hay más soporte de herramientas, y los desarrollos recientes en SGBDR, en inglés RDBMS (en particular SQL3) pretenden facilitar estos problemas. Una alternativa es utilizar una base de datos orientada a objetos. BDOO soportan de forma explícita la orientación a objetos, incluyendo la herencia, y permiten también una interacción con los datos más flexible que la que permiten los SGBDR. Sin embargo, todavía tienen una pequeña cuota del mercado. El estudio de las bases de datos orientadas a objetos quedan fuera del alcance de este libro; un buen libro para leer sobre el tema es [30].
48
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
P: Si ha accedido a cualquier base de datos, considere cómo deberían utilizarse para apoyar la aplicación aquí descrita. Pregunta de Discusión 25 ¿Qué factores influirían si se deseara integrar el sistema de la biblioteca con cualquier otro sistema de la universidad, como es el sistema de registro de estudiantes?
RESUMEN
Este capítulo ha descrito el estudio de un caso sencillo. Ha introducido las funciones principales de UML de una manera superficial; la Parte II del libro cubre cada tipo de diagrama en detalle; pero, ya que se han diseñado los diagramas para que sean utilizados juntos en un desarrollo iterativo, es importante tener una visión general de lo que es UML antes de continuar con los detalles. Se ha descrito una única iteración del desarrollo del sistema, sin justificar las técnicas utilizadas. En el capítulo siguiente se trata el proceso de desarrollo. PREGUNTAS DE DISCUSIÓN
1. En este capítulo nos hemos centrado en un solo caso de uso —préstamos—. ¿Podría desarrollar diagramas similares para el resto de casos de uso? 2. ¿Piensa que merece la pena el esfuerzo para crear este modelo? Trate de recordar cómo habría abordado el mismo problema antes de leer este capítulo y analice si le ha beneficiado el conocimiento adquirido. 3. Los bibliotecarios se considera que existen simplemente para servir a los casos de uso de los socios de la biblioteca. ¿Cómo se podrían modelar sus propios casos de uso, que incluyen la necesidad de añadir y eliminar nuevos libros y nuevas copias de libros existentes? ¿Hasta qué punto su adición a nuestro diseño requiere un cambio en nuestro modelo existente? 4. ¿Qué piensa sobre la manera en que se tratan las revistas? 5. Se han modelado los actores socios del personal como tipos de socios de la biblioteca, pero los socios del personal pueden tomar prestados más libros que los socios de la biblioteca normales. ¿Esto es un problema? 6. ¿Cómo hará frente, o debería hacer frente, el sistema a los libros que constan de varios volúmenes, a los que tienen CD o cinta, etc.? Enumere algunas opciones, y considere los pro y los contra que le gustaría discutir con su cliente?
Capítulo
4 El proceso de desarrollo
Este capítulo trata la pregunta “¿Cómo se construye un buen sistema?”. En el estudio del caso simple, era posible imaginar que uno se sentaba y hacía el trabajo, empezando desde el principio, continuando hasta el final, y paraba. Sin embargo, para sistemas más complejos es necesario dirigir el proceso de desarrollo. Es posible planificar el desarrollo y decir hasta dónde se ha llegado y si se va con retraso, y se debe tener un juego de herramientas de técnicas apropiadas para desarrollar un sistema. Se debe tener una manera comprensible de documentar y controlar lo que se hace, por ejemplo, para que la gente pueda abandonar o unirse a un proyecto, según convenga, de manera segura. Se han utilizado muchos procesos o metodologías de desarrollo. Este libro no se centra en ninguno concreto; al final del capítulo se volverá a la pregunta de por qué no. En este capítulo se discutirán brevemente algunos aspectos del proceso, y se darán algunos puntos a los que acudir para obtener más información.
4.1
Definición de términos Los términos “proceso”, “metodología” y “método” (con o sin los prefijos “desarrollo” o “diseño”) se utilizan de diferentes maneras por distintas personas, a menudo sin definición. Mediante proceso de desarrollo, se hace referencia a un conjunto de reglas que definen cómo debería llevarse a cabo el desarrollo de un proyecto. Esto puede incluir una descripción de qué documentos, modelos de diseño y otros artefactos deberían producirse y en qué orden. Los términos metodología y método se utilizan también de manera similar; hay una tendencia de utilizar estos términos para cosas que especifican técnicas para desarrollar artefactos de diseño particulares, tales como el modelo de clases que se vio en el Capítulo 3. A veces esto se enfatiza hablando de metodología de diseño. Si hay una diferencia entre “método” y “metodología”, sería, tal y como sugiere la longitud de las palabras, que una metodología es algo más grande con un alcance más amplio. Sin embargo, la diferencia entre estas palabras no es absoluta, y no siempre se puede ser definitivo a la hora de decir si algo es un proceso o “simplemente” una metodología. ¡Ni siquiera la pregunta es particularmente interesante!
50
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
Un/a método[logía][de diseño] normalmente especifica qué lenguaje de modelado debería utilizarse en la descripción del trabajo de análisis y de diseño. También indica cómo se deberían producir estas cosas, por ejemplo, dando un conjunto de pasos a seguir para capturar los requisitos de los usuarios. En este libro se discutirán muchas de estas técnicas que pueden formar parte de una metodología, pero se deja abierta la elección de la metodología. A veces, a esta colección de técnicas se le llama, en sí, proceso, pero se intentará evitar este uso. Ya que hemos mencionado el lenguaje de modelado, discutámoslo antes de volver a otros aspectos del proceso de desarrollo.
4.1.1
Modelos y lenguajes de modelado
Un modelo es una representación abstracta de una especificación, un diseño o un sistema, desde un punto de vista particular. A menudo se representa gráficamente mediante uno o más diagramas. Tiene como objetivo expresar la esencia de algunos aspectos de lo que se está haciendo, sin especificar detalles innecesarios. Su propósito es permitir a la gente implicada en el desarrollo pensar y discutir sobre problemas y soluciones sin desviarse de los objetivos. Si esto es útil, un modelo tiene que tener un significado preciso y bien entendido: ¡abstracto no significa confuso! Un lenguaje de modelado es una manera de expresar los distintos modelos que se producen en el proceso de desarrollo. Un lenguaje de modelado define una colección de elementos del modelo, que son aproximadamente análogos a la pronunciación (palabras, sentencias, guiones de comedias de televisión) en el lenguaje hablado; un modelo está formado por elementos de modelo, tal y como una sentencia está formada por palabras. UML, el Lenguaje Unificado del Modelado, es, por supuesto, un ejemplo de lenguaje de modelado. Un lenguaje de modelado normalmente se centra en los diagramas, pero puede utilizar texto. Tiene: • sintaxis —en un lenguaje de modelado basado en diagramas, las reglas que determinan qué diagramas son legales. • semántica —las normas que determinan qué significa un diagrama correcto. Tanto la sintaxis como la semántica pueden ser más o menos formales. La mayoría de lenguajes de modelado expresan tanto la sintaxis como la semántica de manera informal con el español de todos los días. Las explicaciones de UML en este libro pueden verse como una manera informal de dar la sintaxis y la semántica de UML —esto es, cuando haya leído el libro debería tener un buen conocimiento de si un diagrama es correcto en UML, y si es así, qué significa, aunque no sea capaz de expresarlo matemáticamente. La Guía oficial de Especificación de UML [48], describe de forma informal y escueta, tanto la sintaxis como la semántica de UML, de forma estructurada a través del español y las matemáticas. En el lado opuesto al español informal, es posible dar una descripción matemática y completamente formal de la sintaxis y la semántica de un lenguaje (pero es un trabajo muy duro). Por citar algún ejemplo completo, [39] define la sintaxis y la semántica del lenguaje de programación ML Estándar. Se desarrolla una definición formal de un lenguaje si es necesario eliminar cualquier posibilidad de ambigüedad. No es probable que se haga para todo UML, ya que el beneficio no suele coincidir con el coste. Además de utilizarlo para apoyar el desarrollo de un sistema particular, un lenguaje de modelado puede utilizarse para documentar un artefacto reutilizable, como es un componente o un marco de trabajo.
EL PROCESO DE DESARROLLO
51
Es algo insólito que UML sea un lenguaje de modelado que no esté asociado a un proceso particular. En el pasado, cada metodología de diseño tenía su propia notación, esto es, su propio lenguaje de modelado. Esta distinción es la raíz de gran cantidad de confusión. A menudo la gente piensa que está utilizando una metodología si dibuja diagramas en la notación de dicha metodología, utilicen o no cualquiera de las técnicas recomendadas por la metodología o sigan sus normas. Es probable que oiga a la gente hablar sobre “la metodología de UML” y comparar sus méritos y deméritos frente a su metodología favorita. Esto es falso: es comparar manzanas con naranjas. Una cuestión más sensata podría ser “¿Cómo es de fácil y de sensato seguir mi metodología favorita utilizando como notación UML?”.
¿Por qué un lenguaje de modelado unificado? Dado que los desarrolladores necesitan un lenguaje de modelado para ayudarles a discutir los problemas y soluciones implicados en la construcción del sistema, ¿qué debería determinar el lenguaje que utilizan? El lenguaje elegido debería ser: 1.
Suficientemente expresivo, de manera que sea posible expresar los aspectos del diseño que será necesario tratar, y que reflejen de forma que tengan sentido, los cambios en el diseño que se lleven a cabo durante el desarrollo como cambios en el modelo.
2. Suficientemente fácil de utilizar, de forma que el lenguaje de modelado ayude a tener un conocimiento claro en vez de proporcionar el camino para tener dicho conocimiento claro. 3. Inequívoco, para que el lenguaje de modelado ayude a resolver malos entendidos en vez de presentar más. 4. Soportado por herramientas adecuadas, de manera que el esfuerzo de los desarrolladores pueda utilizarse en un trabajo que requiera su habilidad, no en un trabajo rutinario como crear un diagrama con una herramienta de dibujo. 5. Generalmente utilizado, por gran variedad de razones. Por supuesto, cuanto más general sea la utilización de un lenguaje es más probable que se cumplan los cuatro puntos anteriores. También, • cuando se incorpora gente nueva en el proyecto, es una ventaja si ya conocen el lenguaje de modelado en vez de tener que aprenderlo; • para hacer diseño basado en componentes hay que ser capaz de leer las descripciones de los componentes, y cuanto más rápido y fácil se pueda hacer, será más barato tener en cuenta un componente. Cuanto más genéricamente utilice su lenguaje de modelado, mayor es la posibilidad de que sea el mismo que el escritor del componente decidió utilizar. Pregunta de Discusión 26 ¿Qué otras ventajas ve respecto a tener un lenguaje unificado de modelado? ¿y desventajas?
Se tratará la historia de UML y la esencia global de sus modelos hacia el final de este capítulo.
52
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
4.1.2
Proceso y calidad
Tal y como se verá en el Capítulo 20, hay un solapamiento significativo entre los asuntos del proceso de desarrollo y los de un sistema de gestión de calidad. El objetivo último de ambos es asegurar que el proceso (y desde aquí, se espera, el producto) tenga alta calidad. En general, el proceso de desarrollo especificará aspectos más técnicos del proceso y del sistema de gestión de calidad, los más administrativos; pero los términos se solapan. Es importante recordar que ningún proceso o metodología, aunque sean buenos, puede asegurar el éxito de un proyecto. Lo más que puede hacer es apoyar a la gente implicada en el proyecto en la producción de buenos resultados. El excelente libro de DeMarco y Lister, Peopleware [13], destaca de forma acertada este hecho.
4.2
El proceso de desarrollo En esta sección se considera el proceso de desarrollo de un sistema general y de alto nivel. Casi seguro que conoce el famoso y ahora, al menos en parte, desacreditado, proceso en cascada1, mostrado en la Figura 4.1. El proceso tiene un número relativamente pequeño de partes identificables, en este caso las cinco fases del ciclo de vida. Algún tipo de división en partes es una parte fundamental de cualquier proceso, debido a que el proceso pretende ayudar a las personas a entender algo, ¡y a las personas les gusta identificar las partes de las cosas! En este caso el proceso se divide en partes en función de las actividades que se están ejecutando; sin embargo, hay también una consecuencia en la que las actividades se realizan una después de otra, transcurriendo el tiempo hacia abajo en la página.
Análisis
Diseño
Implementación
Pruebas
Mantenimiento
Figura 4.1 Un proceso en cascada sencillo.
1
Más conocido como modelo en cascada (del ciclo de vida), pero la sobrecarga de “modelo” parece innecesariamente confusa en este contexto.
EL PROCESO DE DESARROLLO
53
La Figura 4.1 muestra una versión sencilla del proceso en cascada, a veces conocido como proceso “tíralo por la ventana”, ya que incorpora una suposición (o restricción) en la que una vez que se supone que ha terminado una fase, nunca se vuelve a ella. Si fuera posible tomar decisiones perfectas todo el tiempo, sería la manera en que funcionarían las cosas. Desafortunadamente, es imposible, por ejemplo, debido a que constantemente hay disponible nueva información. En la práctica, es necesario revisar las primeras decisiones en función de la experiencia: por ejemplo, revisar la especificación de los requisitos cuando se descubre que al cliente no le gusta el sistema implementado. El rechazo a revisar las decisiones puede terminar fácilmente en un fallo completo del proyecto. El proceso en cascada muchas veces se dibuja con flechas adicionales hacia atrás, para reflejar esta realidad. Sin embargo, los criterios para decidir volver a una fase anterior a menudo se dejan implícitos. La consecuencia es que la situación normal, correcta, es que se procede de una fase a la siguiente: primero se hace todo el análisis de requisitos, después se empieza con el diseño del software y del sistema, y demás. Pregunta de Discusión 27 Dadas sus desventajas bastante obvias, ¿por qué es tan difícil de detener el proceso de “lánzalo por la ventana”? ¿Qué ventajas tiene, y para quién?¿Bajo qué circunstancias se podría decidir correctamente vivir con las desventajas?
Ahora, sin embargo, se reconoce que para casi todos los sistemas es correcto y necesario tener algún tipo de proceso iterativo. Los procesos de desarrollo modernos tienen como fundamental las iteraciones, y tratan de proporcionar maneras de gestionar, en vez de ignorar, los riesgos. La gestión de riesgos es un tema amplio y extremadamente importante. Consideremos sólo dos aspectos: 1. Cada vez que se toma una decisión, se corre el riesgo de que sea incorrecta. Más importante, cuanto más se tarde en descubrir un error, es probable que sea más difícil arreglarlo. Por lo tanto, ser puede tratar de controlar los riesgos descubriendo los errores lo más pronto posible. Una manera es tener evaluaciones frecuentes y definidas explícitamente por el proceso. 2. Un riesgo importante es que los desarrolladores pueden entender mal los requisitos. Cualquier cosa que aumente la confianza de que los requisitos establecidos sean correctos, reduce el riesgo. A menudo, es más fácil criticar un sistema que describirlo, por lo que prototipar un sistema puede ser una buena manera de afirmar los requisitos. Pregunta de Discusión 28 Encontrará la visión (puede que no declarada) de que para gestionar el riesgo de tomar una mala decisión es mejor posponer la decisión todo lo que se pueda. ¿Es una buena idea? ¿Depende del tipo de decisión? ¿Cómo y por qué?
El proceso en espiral de Boehm [4] incorpora estas dos ideas, y desde entonces han aparecido muchas variantes del proceso en espiral. Una variante sencilla es la que se ilustra en la Figura 4.2. Empezando por el centro de la espiral, un proyecto que siga el proceso atraviesa sucesivos análisis y planificaciones de riesgos, análisis de requisitos, fases de ingeniería y evaluación. La fase de ingeniería del proceso en espiral implica diseño, implementación y pruebas. El número de iteraciones es arbitrario.
54
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
Evaluar
Ingeniería diseño, implementar pruebas
Analizar riesgos y planificar
Analizar requisitos para estar iteración
Figura 4.2 Un proceso en espiral sencillo.
Destacar lo que el proceso en espiral aquí mostrado no dice: no indica cómo hacer cada fase. Claro está, ni siquiera especifica cuál es el resultado de una fase. ¿Es un sistema en ejecución con cada vez mayor funcionalidad, o es un conjunto de modelos de un sistema más detallado, que se convierte en un sistema en ejecución sólo en la última iteración? Esta ambigüedad sobre lo que significa una iteración es una de las principales diferencias entre las metodologías modernas, que tienen sus propias versiones de la espiral básica.
4.2.1
¿Una metodología unificada?
Como la orientación a objetos se ha puesto de moda, y ahora ha pasado de estar de moda a ser la línea central del desarrollo del software, se ha propuesto una plétora de métodos de desarrollo orientados a objetos, cada uno con ideas y notaciones que se solapan, pero que no son idénticos. Tres de los más populares fueron —aunque había y hay muchos otros: 1. El método de Grady Booch, a veces llamado OOD [7]. 2. OMT, de James Rumbaugh [40]. 3. OOSE y Objectory, de Ivar Jacobson [28]. (Cada uno tiene un libro principal que apareció a principios de los noventa). Como se puede ver, cada método es defendido por un único inventor, el cual, en cada caso, es un experto altamente experimentado y respetado en el campo del desarrollo OO. Como la mayoría de la gente, los autores esperan que en un futuro previsible se llegue, más o menos, a esta situación: la guerra de
EL PROCESO DE DESARROLLO
55
los métodos, en la que cada método tenga sus defensores. (Después de todo, ¡todavía no se ha decidido un lenguaje de programación!)
La guerra de los métodos ha terminado: hemos ganado Sin embargo, no se está produciendo completamente de esa manera. Booch estaba y está a cargo del Software de Rational (los desarrolladores de Rose). En 1994, Rumbaugh se unió también a Rational, y ambos declararon su intención de mezclar sus métodos. En 1995, Rational compró la compañía Objectory, de Ivar Jacobson, y eran muy conocidos. Booch, Rumbaugh y Jacobson se convirtieron en los Tres Amigos 2. Y se produjo pronto un borrador de documentación para el tan nombrado Método Unificado, y parecía probable que acaparase a todo el mundo. (No es necesario decir que no todo el mundo estaba de acuerdo con esto, y el desarrollo de OML, el Lenguaje Abierto de Modelado [8], por el consorcio OPEN, fue una reacción. Sin embargo UML, tal y como es ahora, parece establecerse para dominar, y ahora ha sido adoptado por el principal cuerpo de estandarización para los problemas orientados a objetos, el Grupo de Gestión de Objetos (OMG)). Más tarde, el centro cambió desde el desarrollo de un método unificado hacia el desarrollo de un lenguaje de modelado unificado, y por lo tanto se tiene UML. Los autores creen, sin duda pragmáticos, que esta decisión fue correcta. Se han dado ya algunas razones para querer un lenguaje de modelado unificado; sin embargo no se cree que haya razones tan convincentes para tener una metodología unificada. Se piensa que siempre se dará el caso en que diferentes metodologías se adecúan a diferentes organizaciones con distintos tipos de proyectos. Claro está, se piensa que la variedad de metodologías que han utilizado las organizaciones, ha estado hasta ahora oculta tras las diferentes notaciones. Desde nuestra experiencia, cuando alguien declara estar utilizando “OMT” o “el método de Booch”, a menudo quiere decir, simplemente, que está utilizando la notación de ese método. Las diferentes organizaciones que “utilizan OMT” podrían, en realidad, estar siguiendo procesos muy distintos. Puede ser que decidir una única notación, haga, en un futuro, más fácil identificar las verdaderas diferencias entre procesos. Por supuesto, las metodologías incluyen técnicas que a menudo pueden trasplantarse entre metodologías. Se tratarán varias de estas técnicas que se podrían importar a una metodología.
4.2.2
Procesos a utilizar con UML
Se tomará, tal y como se acordó, que un proceso de desarrollo debería. • tomar la gestión de riesgos como un concepto central, y en particular. • permitir que las iteraciones sean un medio para controlar el riesgo. • ser centrado en la arquitectura y basado en componentes: esto es, tal y como se trató en el Capítulo 1, podría tener como actividades prioritarias el seguimiento y la toma de decisiones de arquitectura correctas, y el uso y desarrollo de buenos componentes. La característica final que todo desarrollador de UML piensa que cualquier proceso de desarrollo debe tener es que sea dirigido a casos de uso. Se pospone su discusión hasta el Capítulo 7: sin un tratamiento detallado de los casos de uso, no es posible definir lo que esto significa sin estar peligrosamente equivocado. 2
¿Después de la película del mismo nombre de John Landisen, en 1986?
56
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
Proceso Unificado En esta sección se tratará brevemente el tan nombrado proceso unificado, que incorpora ideas de cada uno de los “Tres Amigos” y de otras muchas fuentes. Se ha empezado tratando los métodos iterativos. Sin embargo, este no es el final de la historia. Una aproximación iterativa ad hoc funciona bien para desarrollos rápidos, pequeños: pero, incluso en ellos, habrá una etapa antes de que se haya optado definitivamente por el proyecto, y una etapa después de que haya sido “finalmente” entregado, lo que es lógico diferenciar. También es fundamental, por razones políticas y de gestión, tener un plan con lo que serán las iteraciones y lo que cubrirá cada una, en vez de, simplemente hacer tantas iteraciones como se requieran. El Proceso Unificado sitúa su espiral principal en su fase de Construcción. • Incepción, finaliza con el compromiso del fiador del proyecto de seguir adelante: caso de negocio del proyecto y su viabilidad y alcance básicos conocidos. • Elaboración, finaliza con — La arquitectura básica del sistema en cuestión. — Un plan convenido para la construcción. — Todos los riesgos significativos identificados. — Los principales riesgos comprendidos lo suficiente para no estar demasiado preocupados. • Construcción (claramente iterativo), finaliza con una versión beta del sistema. • Transición, es el proceso de presentación del sistema a sus usuarios. Estas fases pueden diferir ampliamente entre proyectos en cuanto a su duración y a sus períodos de entrega. Por ejemplo, para un proyecto pequeño o sencillo (¡o uno con un patrocinador que lo respalde!) la fase de incepción podría ser incluso un gestor de desarrollo que tiene una pequeña charla con el patrocinador del proyecto. En el otro extremo, se podría requerir una serie de entrevistas de alto nivel y un caso de negocio formalmente aceptado.
Otros procesos En realidad, UML es tan expresivo que es improbable no poder utilizarlo con cualquier metodología o proceso de desarrollo orientado a objetos. Por ejemplo, cualquiera de los métodos originales de los “Tres Amigos” puede utilizarse con UML. Catalysis [16] es una metodología de desarrollo particularmente interesante, que ha adoptado UML como su lenguaje de modelado. Está principalmente implicada en los aspectos técnicos del desarrollo de los sistemas, y de forma explícita abarca la idea de que no hay ningún proceso correcto que funcione con todos los proyectos de software. Destaca el rigor con que se desarrolla un sistema, y especialmente el rol de los marcos de trabajo; un marco de trabajo puede verse como una parte de la arquitectura reutilizable, que define cómo interactúa un grupo de objetos o componentes. Se cubrirán brevemente los marcos de trabajo en el Capítulo18, después de desarrollar un ejemplo sencillo en el Capítulo 16. Metodologías ágiles, como la Programación Extrema iniciada por Kent Beck, se concentran en el manejo de los riesgos asociados al cambio o a la poca claridad en los requisitos. Usan muy
EL PROCESO DE DESARROLLO
57
pequeñas y rápidas iteraciones y una pronta retroalimentación por parte del cliente para verificar que lo que esta siendo construido es lo que se necesita. Usan largas pruebas automatizadas y continuos pequeños ajustes para asegurar alta calidad, y evitar la construcción de características que, seguramente, no se necesitan. Pregunta de Discusión 29 Utilice cualquier medio que tenga a su disposición (lnternet le será útil, y hay algunos puntos de partida en la página de inicio del libro) para averiguar algo sobre los siguientes procesos y métodos de desarrollo: • • • • • • • • •
El Proceso Unificado de Rational Catalysis OPEN Programación Extrema The Bazaar, más conocido como el proceso utilizado para desarrollar el sistema operativo libre Linux SCRUM DSDM, el Método de Desarrollo de Sistemas Dinámico SSADM, para contrastar ...
Considere cuál es su alcance, y si bien impone o bien es compatible con una aproximación centrada en la arquitectura, basada en componentes. Compare y contraste.
4.3
Sistema, diseño, modelo, diagrama Cualquier proceso de desarrollo pretende producir, probablemente después de varias iteraciones, la implementación de un sistema. Esto es, un programa o conjunto de programas que funcionan en un entorno apropiado para cumplir las necesidades de los usuarios, incluyendo necesidades implícitas, tales como el mantenimiento. El diseño y (especialmente) la arquitectura del sistema incorporan las decisiones importantes sobre cómo construir el sistema, abstrayéndose de muchos detalles. Un lenguaje para describir un diseño debería, naturalmente, estar basado en diagramas, ya que la experiencia sugiere que así es como se piensa, de forma natural, sobre los sistemas. Es inconcebible que un único diagrama pueda capturar todo sobre nuestro diseño, y está claro que no debería ser deseable, ya que en cada momento interesan diferentes aspectos del diseño. Se construirán diferentes modelos del diseño, reflejando estos aspectos distintos, y se expresará cada modelo utilizando diagramas en un lenguaje de modelado. Se quiere distinguir modelos en varios ejes: • El modelo de casos de uso describe el sistema requerido desde el punto de vista del usuario. • Un modelo estático describe los elementos del sistema y sus relaciones. • Un modelo dinámico describe el comportamiento del sistema a lo largo del tiempo.
58
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
Se puede tomar una: • Vista lógica: ¿Qué partes están teóricamente juntas? Por ejemplo, ¿cuáles son las clases y cómo están relacionadas? Esto se modela principalmente para comprobar que se cumplen los requisitos funcionales. • Vista de proceso: ¿Qué hilos de control hay? Por ejemplo, ¿qué cosas pueden darse de forma concurrente, y qué sincronización tiene que haber? Modelar esto ayuda a asegurar que se cumplen los requisitos no funcionales, tales como la disponibilidad y las prestaciones. • Vista de desarrollo: ¿Qué partes las puede desarrollar, sensatamente, el mismo equipo de gente, y qué puede reutilizarse? Modelar esto ayuda a gestionar el proyecto. • Vista física: ¿Qué partes se ejecutarán en la misma computadora? Modelar esto ayuda a asegurar que se cumplen los requisitos no funcionales; es una vista más concreta que la vista de proceso. Estos son los cuatro modelos en los “41 modelos de vistas de la arquitectura” [31]. El diagrama no es el diseño: el diagrama es una representación de (parte de) un modelo de diseño, que captura un aspecto del diseño de una manera que puede ser discutido.
Puede haber muchos diagramas de un modelo que, por supuesto, tienen que ser consistentes. Esto es de sentido común: el conjunto entero de diagramas, que describen varias partes y aspectos del diseño, se supone que es una descripción de aspectos de un único sistema, de manera que no deben contradecirse unos a otros. Por ejemplo, si su diagrama de estructura estática muestra que la clase Foo no tiene ninguna relación con la clase Bar, su diagrama dinámico no puede mostrar un objeto Foo enviando un mensaje a un objeto de la clase Bar. Parte de esta comprobación de consistencia puede automatizarse con una herramienta adecuada.
4.3.1
Los usos de los modelos
Los modelos son usados más frecuentemente para la comunicación entre desarrolladores: ellos registran las decisiones de diseño. Se ha observado que desarrollar modelos es caro, y a veces el coste no parece estar justificado. Hay dos estrategias para rectificar esto: 1. Reducir el coste. En metodologías rápidas, los diagramas de UML son normalmente dibujados rápidamente en pizarras blancas y utilizados para mejorar la discusión. No se dibujan generalmente utilizando una herramienta y guardándolo en un documento, porque esto no parece ser beneficioso. 2. Incrementar el valor. La arquitectura controlada por modelos de UML apunta a utilizar los modelos de UML como entradas para herramientas. Los diseñadores desarrollan un Modelo Independiente de la Plataforma (PIM), el cuál es entonces transformado por una herramienta en uno o más Modelos Específicos de la Plataforma (PSM). Como el nombre sugiere, PIM abstrae de decisiones como qué componente de la arquitectura va a ser utilizado; automáticamente, la herramienta refina el PIM añadiendo la información necesaria para la plataforma seleccionada. El código podría ser generado automáticamente desde el PSM, o podría ser escrito a mano. Idealmente, cuando los cambios sean requeridos, se pueden hacer a nivel de PIM, y los PSMs y el código regenerados.
EL PROCESO DE DESARROLLO
59
Esto reduce el esfuerzo, especialmente si el sistema existe en muchas versiones para diferentes plataformas. Sin embargo, esto invita a utilizar UML como lenguaje de programación, lo cuál tiene problemas. Muchos aspectos de UML no están definidos con precisión, de tal forma que las herramientas podrían entender algo de un diagrama UML dado, de forma diferente de lo que pretendía su diseñador. Además, herramientas para la verificación y eliminación de fallos en modelos UML no son tan útiles o están tan avanzadas como las herramientas para la verificación y eliminación de fallos en el código.
RESUMEN
Se han tratado los métodos de desarrollo en general, y la necesidad de lenguajes de modelado. También se ha tratado brevemente la historia de UML, y la pregunta sobre qué metodologías de desarrollo pueden utilizarse con UML. Finalmente, se ha tratado el rol del diseño, modelos y diagramas. Cada sistema tiene un diseño. Puede haber varios modelos de un diseño, centrándose en diferentes aspectos, que tienen que ser todos consistentes. Un modelo puede representarse con algunos diagramas, que tienen que ser todos consistentes. Los modelos pueden ser utilizados para la comunicación entre personas, o pueden ser usados como entradas para herramientas.
Parte
II El lenguaje unificado de modelado
Capítulo 5
Fundamentos de los modelos de clases
63
Capítulo 6
Más sobre los modelos de clases
83
Capítulo 7
Fundamentos de los modelos de casos de uso
103
Capítulo 8
Más sobre los modelos de casos de uso
115
Capítulo 9
Fundamentos de los diagramas de interacción
125
Capítulo 10
Más sobre los diagramas de interacción
137
Capítulo 11
Fundamentos de los diagramas de estado y actividad
147
Capítulo 12
Más sobre los diagramas de estado
161
Capítulo 13
Diagramas de arquitectura e implementación
167
Capítulo 14
Paquetes y modelos
173
Capítulo
5 Fundamentos de los modelos de clases
Este capítulo presenta los diagramas de clases de UML, que se utilizan para documentar la estructura estática del sistema; esto es, qué clases hay y cómo están relacionadas, pero no cómo interactúan para alcanzar comportamientos particulares. Un diagrama de clases puede también mostrar otros aspectos de la estructura estática, tales como paquetes, que se tratarán en los Capítulos 6 y 14. En UML, una clase aparece en una diagrama de clases como un rectángulo con su nombre. La Figura 5.1 es un icono de clase para la clase Libro.
Libro
Figura 5.1 Un modelo de clases muy sencillo.
Más tarde, en este capítulo se verá cómo representar más información sobre los datos y el comportamiento encapsulado por una clase, pero por ahora nos centraremos en la identificación de clases y de las asociaciones entre ellas.
5.1
Identificar objetos y clases La construcción de un modelo de clases incluye la identificación de las clases que deberían existir en nuestro sistema: esta es una parte fundamental del trabajo de diseñar un sistema orientado a objetos. Antes de tratar cómo identificar objetos y clases, discutamos los criterios para tener éxito.
64
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
5.1.1
¿Qué hace que un modelo de clases sea bueno?
En última instancia, hay dos objetivos que se pretenden alcanzar: • Construir, lo más rápido y barato posible, un sistema que satisfaga nuestros requisitos actuales. • Construir un sistema que sea fácil de mantener y adaptar a futuros requisitos. Estos objetivos muchas veces se encuentran enfrentados; una razón del éxito de las técnicas orientadas a objetos, y especialmente de las técnicas de diseño basadas en componentes, es que permiten dar un paso hacia su reconciliación. Para cumplir el primer objetivo, Los objetos de las clases elegidas tienen que ser capaces de proporcionar cada parte del comportamiento que requiere el sistema.
Ya se ha visto, en el Capítulo 1, que para cumplir el segundo objetivo habría que construir un sistema compuesto por módulos encapsulados, con débil acoplamiento y fuerte cohesión. Además: Un buen modelo de clases está formado (dentro de lo posible) por clases que representan clases permanentes de los objetos del dominio, que no dependen de la particular funcionalidad hoy requerida.
Por ejemplo, cualquier sistema de biblioteca contendrá libros, por lo que es razonable tener una clase Libro. (Destacar que los nombres son importantes: no sería tan claro llamar a la clase ArtefactodePapelLiterario o L). Pregunta de Discusión 30 ¿Por qué hay un conflicto entre los dos objetivos anteriores?¿Qué consideraciones podrían determinar que una organización considere una más significativa que la otra?
5.1.2
Cómo construir un buen modelo de clases
Destacar primero que puede utilizar cualquier técnica que le guste para obtener sus clases: cualquier cosa, incluyendo la inspiración divina, está bien, si nos dirige a un modelo de clases bueno por los criterios que se han dado. A la inversa, si se produce un modelo de clases malo (uno que no cumple los criterios) ¡a nadie le importará qué método maravilloso haya utilizado para obtenerlo! En la práctica, es probable que lo haga correctamente la primera vez. La colección de clases en su modelo de diseño es una de las cosas que probablemente cambiará a lo largo y dentro de las iteraciones de desarrollo. Normalmente, identificará las clases más importantes de los
FUNDAMENTOS DE LOS MODELOS DE CLASES
65
objetos de dominio —es decir, aquellas que pertenecen de manera obvia al problema en vez de aquellas que se introducen para resolverlo— primero y más fácilmente; las otras clases, que se corresponden con menor claridad con los objetos del dominio, son más difíciles de identificar con seguridad.
¿Qué dirige? Los expertos en OO tienden a dividirse en dos grupos, aquellos partidarios del diseño dirigido a los datos y aquellos que defienden el diseño dirigido a la responsabilidad. Tal y como se ha visto en los Capítulos 2 y 3, las clases tienen tanto datos como responsabilidades. Una caricatura del diseño dirigido a los datos (DDD) es que supone la identificación de todos los datos en el sistema y luego la división en clases, antes de considerar las responsabilidades de las clases; la técnica de identificación de nombres, que se utiliza en el Capítulo 3 y que se considerará aquí más adelante, es una parte fundamental en el DDD. Una caricatura del diseño dirigido a la responsabilidad (DDR) es que supone la identificación de todas las responsabilidades en el sistema y su división en clases, antes de considerar los datos de las clases. Por supuesto, ambas caricaturas describen aproximaciones extremas que no funcionarían: nadie propone seriamente nada tan extremo. Se sabe que la distinción entre DDR y DDD no es útil en el contexto de este libro. Es mucho más fácil utilizar una aproximación mezclada en un proyecto que describirlo en un libro: por claridad, se presentarán las técnicas de forma separada, pero la naturaleza de un proyecto orientado a objetos con éxito es el que utilice varias técnicas, a menudo de manera simultánea. La identificación de nombres, que se describe a continuación, es una técnica asociada con DDD; las tarjetas CRC, que se tratarán al final del capítulo, es más una técnica DDR.
Una técnica: identificación de nombres En el Capítulo 3 se vio un ejemplo de cómo identificar objetos y clases. Se procede en dos etapas: 1. Identifica las clases candidatas seleccionando todos los nombres y locuciones nominales de la especificación de requisitos del sistema. (Considérelos en forma singular, y no incluya frases que contengan “o” como candidatas). 2. Descarta las candidatas que son inapropiadas por cualquier razón, renombrando las clases restantes, si fuera necesario. Las razones (algunas de las cuales se vieron en el Capítulo 3) por las que se podría decidir que una clase candidata es inapropiada incluye que es1: • Redundante, donde a la misma clase se le ha dado más de un nombre. Es, sin embargo, importante recordar que los objetos parecidos no tienen que ser completamente iguales: una de las cosas que hay que decidir es si las clases son lo suficientemente diferentes para considerarlas clases distintas. Por ejemplo, se incluyen aquí pares como “préstamo” y “préstamo a corto plazo”: son diferentes, pero probablemente sólo en los valores de los atributos. Elija un nombre para la clase que abarque todas las descripciones que quiera que incluya. 1
Esta lista está inspirada, pero no es idéntica, a una descrita en Rumbaugh et al. en [41], y adaptada en el curso de la Open University M868 [47].
66
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
• Impreciso, donde no se puede indicar de forma no ambigua lo que significa un nombre. Obviamente, hay que eliminar la ambigüedad antes de poder decir que se trata de una clase. • Un evento u operación, donde el nombre hace referencia a algo que se hace para, por o en el sistema. A veces, tales cosas están bien modeladas en una clase, pero no es lo normal. Retomando la discusión del Capítulo 2, pregúntese si la instancia del evento u operación tiene estado, comportamiento e identidad. Si no, descártelo. • Meta-lenguaje, donde el nombre forma parte de la manera en que se definen las cosas. Se utilizan los nombres requisitos y sistema, por ejemplo, como parte del lenguaje de modelado, en vez de representar objetos en el dominio del problema. • Fuera del alcance del sistema, donde el nombre es relevante para describir cómo funciona el sistema pero que no hace referencia a algo interno al sistema, por ejemplo, biblioteca en el ejemplo del Capítulo 3. Los nombres de los actores muchas veces se descartan por esta norma, cuando no es necesario modelarlos en el sistema. Se podría utilizar también esta norma para justificar el descarte de semana en el Capítulo 3, aunque este es un ejemplo de dónde es mucho más obvio decir que algo no es una clase que decir por qué. • Un atributo, donde está claro que un nombre hace referencia a algo sencillo sin un comportamiento interesante, que es un atributo de otra clase. (“Nombre” de socio de biblioteca podría ser un ejemplo). Pregunta de Discusión 31 En general, si se duda sobre si mantener una clase, ¿cree que es mejor mantenerla (posiblemente eliminándola más tarde) o eliminarla (posiblemente volviendo a incluirla)?
Esta es una pregunta en la que no se ponen de acuerdo las personas con mayor experiencia: probablemente hay un elemento de psicología individual. Algunas personas mantienen dos listas, una con los candidatos más firmes y otra con los más dudosos, y esta es una técnica útil para evitar perder información mientras se está distinguiendo todavía las cosas de las que se está seguro, de las cosas que tienen que ser fijados todavía. Por ejemplo, en el ejemplo de la biblioteca del Capítulo 3, puede que no se esté esté seguro de descartar préstamo o norma, por lo que podrían estar en la lista de “posibles”. (Se discutirán las clases asociación, de las que resultará ser un ejemplo el préstamo, en el Capítulo 6. Norma no es probable que sea una clase en este sistema particular, pero puede ser útil codificar las tan nombradas normas de negocio como clases, especialmente en los casos en los que las normas son complejas y propensas a cambiar). Una vez que se empieza a identificar asociaciones entre clases, la pregunta de qué clases pertenecen a un modelo de clases a nivel conceptual se responde con mucha rapidez. Esta técnica tan sencilla es una manera extraordinariamente útil de empezar. Esto es todo lo que es: por ejemplo, la lista de razones para descartar clases candidatas no es exhaustiva, y puede haber más de una razón para descartar la misma candidata. Cuando se tiene más experiencia, probablemente, se utiliza de cabeza como comprobación para encontrar cualquier abstracción del dominio que se puede haber olvidado. Probablemente, se identificarán clases y asociaciones en paralelo, aunque, por claridad, aquí se presentan como procesos separados. También es útil empezar a utilizar tarjetas CRC en esta etapa: se tratarán las tarjetas CRC al final de este capítulo.
FUNDAMENTOS DE LOS MODELOS DE CLASES
67
Pregunta de Discusión 32 ¿Es razonable la lista de razones para el descarte? ¿Puede pensar algún caso donde pueda ser muy selectiva o muy permisiva? ¿Quiere modificar la lista?
5.1.3
¿Qué son las clases?
Una clase describe un conjunto de objetos con un rol o roles equivalentes en un sistema. Los objetos y su división en clases se derivan, normalmente, de una de las siguientes fuentes (originalmente definidos por Shlaer y Mellor [42] y, más tarde, parafraseado por Booch [7] y adaptado aquí más ampliamente). • Cosas tangibles o “del mundo real”: libro, copia, curso. • Roles: socio biblioteca, estudiante, director de estudios. • Eventos: llegada, salida, petición. • Interacciones: encuentro, intersección. Estas categorías se solapan, y las dos primeras son fuentes de objetos y de clases mucho más comunes que las dos últimas. Por el contrario, si se han identificado objetos que entran dentro de las dos primeras categorías, las otras dos pueden ayudar a encontrar y nombrar asociaciones entre ellos.
5.1.4
Objetos del mundo real frente a su representación en el sistema
Es importante recordar que los objetos son realmente cosas dentro de un programa de ordenador —que cuando se habla sobre “libros” y “copias”, por ejemplo, realmente nos referimos a la representación de estas cosas dentro de nuestro sistema—. Las consecuencias de esto son que hay que tener cuidado: • de no almacenar información que sea definitivamente irrelevante para nuestro sistema. • de no perder la visión del hecho de que ¡los objetos son el sistema! El último punto es particularmente interesante. Un error clásico en la gente que todavía no está empapada de OO es inventarse una clase, a menudo llamada [Cualquier cosa]Sistema, que implementa todo el comportamiento interesante del sistema. Pero en OO todo el negocio es el sistema —¡este es el tema! Es fácil dejarse llevar hacia un diseño monolítico donde hay un único objeto que conoce y hace todo. Esto está mal porque tales diseños son muy difíciles de mantener: tienden a tener presunciones sobre cómo será utilizado el sistema. (Hay una manera muy diferente de tener una clase que encapsula el sistema: muchas veces es útil, y en algunos lenguajes obligatorio, tener una clase principal, que se instancia una sola vez en cada instancia de ejecución del sistema, y que proporciona el punto de entrada al programa. Al empezar el programa automáticamente crea este objeto principal, que, por turnos, crea el resto de objetos en el sistema. El objeto principal, sin embargo, no tiene ningún comportamiento complejo por sí
68
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
mismo. Todo lo más, podría enviar los mensajes que le llegan de fuera al objeto apropiado. Esto está relacionado con el patrón Fachada (Façade), que consideraremos en el Capítulo 18). En el Capítulo 7 se volverá a la pregunta sobre cómo los participantes de fuera de nuestro sistema informático (conocidos en UML como actores) se representan en nuestro diseño. Esto (se volverá a ello más tarde) es una parte fundamental de lo que se ha tratado aquí hasta el momento. Ya se vio en el Capítulo 3.
P: Revise la descripción de los requisitos para el sistema de la biblioteca en el Capítulo 3. Aparte de las clases identificadas para la primera iteración, ¿qué clases debería haber en el sistema final?
5.2
Asociaciones Al igual que las clases se corresponden con nombres, las asociaciones se corresponden con verbos. Expresan las relaciones entre clases. En el ejemplo del Capítulo 3, se vieron asociaciones tales como es una copia de y toma prestado/devuelve. Hay instancias de asociaciones, al igual que hay instancias de clases. (Las instancias de las clases se llaman objetos; las instancias de las asociaciones se llaman enlaces, en UML, aunque este término actualmente se utiliza muy poco). Una instancia de una asociación relaciona un par 2 de objetos. Se pueden ver las asociaciones conceptualmente o desde el punto de vista de la implementación. Conceptualmente, se almacena una asociación si hay una asociación en el mundo real descrita mediante una sentencia corta como “un socio de la biblioteca toma prestado un libro” y la sentencia parece relevante para el sistema en cuestión. La clase A y la clase B están asociadas si: • un objeto de la clase A envía un mensaje a un objeto de la clase B. • un objeto de la clase A crea un objeto de la clase B. • un objeto de la clase A tiene un atributo cuyos valores son objetos de la clase B o colecciones de objetos de la clase B. • un objeto de la clase A recibe un mensaje con un objeto de la clase B pasado como argumento. En resumen, si algún objeto de la clase A tiene que saber de algún objeto de la clase B. Cada enlace, es decir, cada instancia de la asociación, relaciona un objeto de la clase A y un objeto de la clase B. Por ejemplo, la asociación llamada toma prestado/devuelve entre SocioBiblioteca y Copia podría tener los siguientes enlaces: • Jo Bloggs toma prestado/devuelve copia 17 de El Principio de Dilbert. • Marcus Smith toma prestado/devuelve copia 1 de El Principio de Dilbert. • Jo Bloggs toma prestado/devuelve copia 4 de Reutilización de Software. 2
dades.
En este libro se tratan sólo las asociaciones binarias, aunque en realidad UML tiene asociaciones de otras ari-
FUNDAMENTOS DE LOS MODELOS DE CLASES
69
Pregunta de Discusión 33 Como alternativa de llamar a esta asociación toma prestado/devuelve, (los autores) se podría haber decidido tener dos asociaciones separadas, una llamada toma prestado y la otra llamada devuelve. Está claro que, si en vez de considerar quién toma prestado y quién devuelve, se hubiese considerado quién es el autor de una copia y quién es el dueño, se habría decidido tener dos asociaciones separadas. ¿Qué es diferente en las dos situaciones? ¿Está de acuerdo con nuestra decisión?
Pregunta de Discusión 34 Piense cómo se solapan los casos de asociaciones listados arriba, y considere si alguno de ellos debería eliminarse. Es discutible, por ejemplo, que si un objeto de la clase A recibe un mensaje con un objeto de la clase B como argumento, pero no va a enviarle más tarde a ese objeto ningún mensaje ni lo va a almacenar en un atributo, entonces esto no debería considerarse asociación. En realidad esta situación muchas veces es, aunque no siempre, un mal diseño. Construya algunos ejemplos y considere si piensa que son sensatos. ¿Tiene alguna opinión sobre si se debe considerar como asociación?
El secreto de un buen diseño orientado a objetos está en terminar con un modelo de clases que no distorsione la realidad conceptual del dominio —de forma que alguien que comprenda el dominio no se lleve sorpresas desagradables— pero que también permita una implementación coherente de la funcionalidad requerida. Cuando se desarrolla el modelo de clases inicial, antes de identificar los mensajes que pasan entre los objetos, necesariamente uno se centra en el aspecto conceptual del modelo. Más tarde, cuando se utilizan los modelos de interacción para comprobar el modelo de clases, estaremos más implicados en ver si el modelo permite una implementación coherente de la funcionalidad requerida. Sin embargo, el proceso no es que primero se desarrolle el modelo conceptual, y entonces se olviden las relaciones conceptuales para desarrollar la implementación del modelo de clases. A lo largo del desarrollo, se tiene como objetivo construir un modelo que sea bueno tanto en el aspecto conceptual como en el de implementación. Prosperar en esto es una parte muy importante de lo que nos lleva a un sistema de fácil mantenimiento, debido a que un modelo así tiende a ser relativamente fácil de comprender, y por lo tanto relativamente fácil de modificar de manera sensata. En la Figura 5.2 se puede ver cómo UML representa una asociación general entre dos clases dibujando una línea entre sus iconos. Esto, normalmente, se representa de varias maneras. Debería, al menos, tener una etiqueta con un nombre, por legibilidad. Se puede incluir una flecha en la etiqueta para indicar en qué sentido se aplica. En el Capítulo 6 se discutirá la utilización de las flechas en la línea de asociación para denotar la navegabilidad: ¿es el libro quien sabe de la
es una copia de Copia
Figura 5.2 Asociación sencilla entre clases.
Libro
70
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
copia, o viceversa, o ambos? Puede que se quiera pensar sobre estas preguntas ahora: algunos expertos están convencidos de que es importante responder estas preguntas pronto, otros no están de acuerdo. Pregunta de Discusión 35 ¿Cuáles son las ventajas y las desventajas de decidir la navegabilidad en esta etapa?
En las primeras etapas del desarrollo de un modelo, muchas veces es suficiente dibujar una simple línea, indicando, pero sin fijar, la existencia de acoplamiento. Según se va madurando el diseño, la línea puede sustituirse por varias, indicando diferentes tipos de asociaciones. Algunos tipos de asociaciones son tan comunes que UML tiene una manera predefinida de mostrarlos; otros pueden ser definidos por el diseñador cuando lo necesite. Se verán diferentes formas en el Capítulo 6. ANOTACIÓN TÉCNICA DE UML La definición de asociación que se utiliza en este libro es una definición dinámica: si en tiempo de ejecución los objetos pueden intercambiar un mensaje, tiene que haber una asociación navegable entre sus clases. A veces es conveniente seguir una visión estática más restrictiva, donde la clase A sólo tiene una asociación navegable a la clase B si un atributo de A contiene un objeto (o colección de objetos) de la clase B. UML no especifica qué definición utilizar: en la práctica, es tarea del modelador decidir exactamente qué significa la existencia de una asociación. (Algunas veces, la elección podría estar determinada por la elección de la herramienta, especialmente si se usan características para la generación de código. UML2 también incluye la notación de un “Conector”, siendo un camino más general para la conexión de clases (y otros clasificadores). Si se toma una visión restrictiva de las asociaciones, también se podría querer utilizar conectores en otras circunstancias. Miraremos, de forma concreta, una clase útil de conector, un “conector de ensamblado”, en el próximo capítulo.
Una anotación que se utiliza con frecuencia es la multiplicidad de una asociación. Aunque no siempre puede quedar claro al principio y puede cambiar con un refinamiento del diseño, esto es tan fundamental que llevará algún tiempo pensar sobre ello.
5.2.1
Multiplicidades
En el ejemplo del Capítulo 3, se ponía un 1 en la parte de la asociación es una copia de, correspondiente a Libro porque toda copia (es decir, cada objeto de la clase Copia) está asociada mediante es una copia de con un solo libro (objeto de la clase Libro). Por el contrario, en nuestro sistema puede haber cualquier número de copias de un libro determinado. Por lo que la multiplicidad en la parte de Copia es 1..*. Como se puede ver, es posible especificar: • Un número exacto, simplemente escribiéndolo. • Un rango de números, utilizando dos puntos entre un par de números. • Un número arbitrario, no especificado, utilizando * (asterisco).
FUNDAMENTOS DE LOS MODELOS DE CLASES
71
Libremente, se puede pensar que * en UML es como un símbolo de infinito, por lo que la multiplicidad 1..* expresa que el número de copias puede ser cualquier cosa entre 1 e infinito. Por supuesto, cada vez habrá, en la realidad, un número finito de objetos en nuestro sistema completo, por lo que esto realmente indica que puede haber cualquier número de copias de un libro, a condición de que haya, al menos, uno. Atributos, que se discutirán en la siguiente sección, también pueden tener multiplicidades.
P: Exprese en UML que un Estudiante empieza seis Módulos, donde como máximo pueden matricularse 25 Estudiantes en cada Módulo. P: Considere las diferentes maneras en las que se puede implementar una asociación en su lenguaje de programación. Pregunta de Discusión 36 Exprese en UML la relación entre una persona y sus camisetas. ¿Qué pasaría con los zapatos de la persona? ¿Cree que ha introducido alguna debilidad en UML? ¿Por qué, o por qué no?
Pregunta de Discusión 37 El número cero nunca puede ser una multiplicidad significativa, ¿o sí?
Pregunta de Discusión 38 La existencia de una multiplicidad mayor que uno a veces se supone que significa que los objetos de esa clase tienen que existir como una colección de cualquier tipo. ¿Es una suposición segura?
5.3
Atributos y operaciones El sistema que se construye consistirá en una colección de objetos, que interactúan para completar los requisitos del sistema. Se ha empezado a identificar las clases y sus relaciones, pero esto no puede ir más lejos sin considerar el estado y el comportamiento de los objetos de estas clases. Es necesario identificar las operaciones y los atributos que cada clase debería tener. Algunos serán obvios; otros aparecerán cuando se consideren las responsabilidades de los objetos y las interacciones entre ellos.
5.3.1
Operaciones
Lo más importante son las operaciones de una clase, que definen las maneras en que los objetos pueden interactuar. Tal y como se dijo en el Capítulo 2, cuando un objeto envía un mensaje a otro, le está solicitando al receptor que ejecute una operación. El receptor invocará a un método
72
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
para ejecutar la operación; el emisor no sabe qué método será invocado, ya que puede haber muchos métodos implementando la misma operación a diferentes niveles de la jerarquía de herencia. La signatura de una operación da el selector, los nombres y tipos de cualquier parámetro formal (argumentos) de la operación, y el tipo del valor de retorno. Aquí, como siempre, un tipo puede ser tanto un tipo básico como una clase. Las operaciones se listan en el compartimento de abajo, el tercero, del icono de clase.
P: Revise el ejemplo del Capítulo 3 y obtenga las operaciones para todas las clases.
5.3.2
Atributos
Los atributos de una clase —los cuales, tal y como se trató en el Capítulo 2, describen los datos contenidos en un objeto de la clase— se listan en el segundo compartimento, el de enmedio, de un icono de clase. La Figura 5.3 es una versión del icono de clase Libro que muestra que cada objeto de la clase Libro tiene un título, que es una cadena, y entiende un mensaje con el selector copiasEnEstantería, que no tiene argumentos y devuelve un entero, al igual que tomarPrestado, que tiene como argumento un objeto de la clase Copia y no devuelve ningún resultado.
Libro título: Cadena copiasEnEstantería(): Entero tomarPrestado(c:Copia) Figura 5.3 Un modelo de clases sencillo, con atributo y operación.
Destacar que no se incluyen los atributos que sólo implementan las asociaciones mostradas en el diagrama de clases. Por ejemplo, no se incluye que Libro tiene un atributo copias en el que almacenar la colección de referencias a los objetos Copia asociadas al Libro. La implementación final probablemente tendrá dicho atributo, pero incluirlo añadiría notación extra al diagrama sin añadir información útil. (Esto implicaría incluso una decisión sobre la navegabilidad de la asociación, cuya discusión sería prematura. La navegabilidad se tratará más ampliamente en el Capítulo 6). Una norma es que los tipos de los atributos deberían ser cualquiera de los tipos primitivos (entero, cadena, etc.) o clases que no aparezcan en el diagrama de clases, como librerías de clases. Si el tipo de un atributo en una clase no aparece en el diagrama de clases, normalmente es mejor registrar una asociación entre las dos clases.
P: Busque cualquier atributo obvio que podría existir para el resto de clases del ejemplo del Capítulo 3.
FUNDAMENTOS DE LOS MODELOS DE CLASES
73
Vistas de operaciones y atributos Sólo con asociaciones se tiene una aproximación conceptual y pragmática que se intenta hacer consistente. La aproximación conceptual incluye identificar qué datos están asociados conceptualmente con un objeto de esta clase, y qué mensajes parece razonable esperar que entienda un objeto. La última vista puede llevarle a una visión antropomórfica de los objetos como si tuvieran inteligencia por sí mismos —“Si un libro pudiese hablar, ¿qué preguntas cree que sería capaz de responder?”— cosa que algunas personas ven desconcertante, ¡pero que pueden, sin embargo, merecer la pena! De forma pragmática, hay que comprobar que se han incluido los datos y el comportamiento suficientes para los requisitos en cuestión. Para hacer esto, hay que empezar a considerar cómo trabajan juntos los objetos de nuestras clases para satisfacer los requisitos. Una técnica muy útil para hacer esto es la utilización de las tarjetas CRC, que se describen más tarde en este capítulo.
5.4
Generalización Otra relación importante que puede existir entre las clases es la generalización. Por ejemplo, SocioBiblioteca es una generalización de SocioPlantilla porque, conceptualmente, todo SocioPlantilla es SocioBiblioteca. Todo lo que pueden hacer todos los SocioBiblioteca, naturalmente lo puede hacer un SocioPlantilla. Por lo que si alguna parte de nuestro sistema (por ejemplo, la facilidad de reservar un libro) funciona para un SocioBiblioteca arbitrario, debería funcionar también para un SocioPlantilla arbitrario, ya que todo SocioPlantilla es un SocioBiblioteca. En el lado contrario, puede haber cosas que no tengan sentido para todos los SocioBiblioteca, pero sí para SocioPlantilla (por ejemplo, tomar prestada una revista). SocioPlantilla está más especializado que SocioBiblioteca; o SocioBiblioteca es una generalización de SocioPlantilla. En otras palabras, un objeto de la clase SocioPlantilla debería ajustarse a la interfaz dada por SocioBiblioteca. Esto es, si algún mensaje lo acepta cualquier SocioBiblioteca, también tiene que ser aceptado por cualquier SocioPlantilla. SocioPlantilla, por el otro lado, puede entender otros mensajes especializados que un SocioBiblioteca podría no ser capaz de aceptar —esto es, la interfaz de un SocioPlantilla puede ser estrictamente más amplio que la de un SocioBiblioteca—. De esto se puede ver que decir que existe una relación de generalización entre clases es realizar una declaración importante, aunque informal, sobre la manera en que se comportan los objetos de ambas clases. Un objeto de una clase especializada puede sustituirse por un objeto de una clase más general en cualquier contexto que espere un miembro de la clase más general, pero no al revés.
Esto nos lleva a una regla sobre el diseño de clases donde una es una especialización de la otra. No tiene que haber un abismo conceptual entre lo que hacen los objetos de las dos clases al recibir el mismo mensaje.
74
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
Establecer esto de una manera mucho más precisa es sorprendentemente difícil: véase el Panel 5.1. PANEL 5.1
Diseño por contrato 2: posibilidad de sustitución
En este panel se continúa la discusión del contrato que tiene que cumplir un objeto, considerando cómo se relaciona este concepto con la herencia. Se supone que un objeto de una subclase es reutilizable en cualquier parte en la que es utilizado un objeto de la superclase. En otras palabras, se supone que la subclase cumple el contrato firmado por la superclase. Examinemos qué significa esto en la práctica. El aspecto más sencillo es lo que significa para los atributos de la clase y el invariante de clase. (Recuerde del Capítulo 3 que un invariante de clase es una declaración de los valores de los atributos que tiene que tener para todos los objetos de la clase).
P: En el Capítulo 2 se dijo que una subclase podía tener atributos y operaciones extra además de los de su superclase, pero no podía eliminar ningún atributo u operación de la superclase. ¿Por qué? ¿Qué pasa con las operaciones que no están en la interfaz pública? P: Si una superclase tiene un invariante de clase, ¿necesita la subclase un invariante de clase? ¿Cómo debería ser la relación entre los dos invariantes? De ejemplos de herencia correcta (sustitutiva) e incorrecta en este caso.
La posibilidad de sustitución necesita comprobar si la subclase anula cualquiera de los métodos de la superclase: esto es, define sus propios métodos para implementar las operaciones. Se dijo que los métodos nuevos tienen que hacer, conceptualmente, lo mismo; ahora seremos más precisos. El eslogan general para la subclase, visto como un subcontratista, es: No exija más: no prometa menos
“No exija más” describe las circunstancias bajo las que la subclase debe aceptar el mensaje (sin quejarse). Tiene que estar preparado para aceptar cualquier argumento que la superclase hubiese aceptado. Por lo tanto, su precondición no debe ser más fuerte que la precondición de la implementación de la superclase. Puede ser que la implementación de la subclase sea una mejora de la implementación de la superclase en el sentido de que funciona en más situaciones: esto está bien. “No prometa menos” describe lo que el cliente puede asumir sobre el estado del mundo después de que se haya ejecutado la operación. Cualquier suposición que fuese válida cuando se estaba utilizando la implementación de la superclase debería seguir siendo válida cuando se utilice la implementación de la subclase. Por lo tanto, la post-condición de la subclase tiene que ser al menos tan fuerte como la versión de la superclase. La subdivisión por tipos de comportamiento, también conocida como posibilidad de sustitución de Liskov, después de que Barbara Liskov lo popularizara, es una versión fuerte y precisa de este slogan. Suponga que algunos programas esperan interactuar con un objeto de la clase C, y que en vez de él, recibe un objeto s de la clase S, subclase de C. Si la posibilidad de sustitución de Liskov es válida, hay algún objeto de la clase C que podría utilizarse en vez de s sin alterar nada en el comportamiento del programa.
FUNDAMENTOS DE LOS MODELOS DE CLASES
75
Pregunta de Discusión 39 Construya unos cuantos ejemplos para ver lo que esto significa en la práctica. ¿Hay circunstancias bajo las cuales este requisito podría ser demasiado fuerte? ¿Demasiado débil?
P: En su lenguaje de programación, si una subclase anula un atributo u operación de la superclase, ¿puede cambiar sus tipos? ¿De qué manera?
Considere la lista de casos en que las dos clases estaban asociadas (Sección 5.2). Quizá se debería añadir la palabra “intencionadamente” a algunos de los casos: la clase A y la clase B están asociadas si un objeto de la clase A “intencionadamente” le envía un mensaje a un objeto de la clase B, y así sucesivamente. Suponga que el código de la clase A hace mención a un objeto que el escritor de la clase A espera que pertenezca a la clase B. El tema es que el objeto podría realmente pertenecer a cualquier subclase de la clase B, y —si la posibilidad de sustitución es válida— la clase A funcionará como se esperaba. No es útil añadir una asociación entre la clase A y cada subclase de la clase B: esto confundiría el diagrama sin aportar ninguna información nueva. En otras palabras, si hay una operación como tomarPrestado(c:Copia) definida por cualquier objeto de la clase SocioBiblioteca, la manera en que se ejecuta la operación debería ser claramente comparable a la manera en que se ejecuta para SocioPlantilla. Su comportamiento no tiene que ser idéntico, pero tienen que ser suficientemente parecidos para que otras operaciones que confían en lo que hace la operación tomarPrestado(c:Copia) cuando la solicita un objeto de una clase más general (SocioBiblioteca) funcionen también con un objeto de una clase especializada (SocioPlantilla). La Figura 5.4 muestra cómo UML representa la generalización.
SocioBiblioteca
SocioPlantilla
Figura 5.4 Una generalización sencilla.
76
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
5.4.1
Utilización del lenguaje para comprobar si existe una generalización
La clase Bar es probablemente una generalización de la clase Foo si es verdad que cada Foo es un Bar. A menudo hablamos de relaciones “es un” —un Foo es un Bar. Sin embargo, si se confía con demasiada ingenuidad en esta comprobación, es muy posible que se fracase, debido a la ambigüedad del lenguaje. Por ejemplo (del libro de Fowler y Scott, UML Distilled, página 75 [19]), se podría decir, por casualidad, que “un Border Collie es una Raza” y determinar que Raza es una generalización de Border Collie, cosa que es mentira. “todo Border Collie es una Raza” prueba que es más obvia su falsedad. El problema está en que realmente está mal empezar con la declaración en español de “un Border Collie es una Raza”, ya que relaciona una única instancia de Border Collie con un nombre colectivo, Raza. Debería haber sido “Border Collie es una Raza”. Si se pone “todos”, en el lugar de “un”, ayuda a ver que la primera afirmación es absurda.
5.4.2
Implementación de la generalización: herencia
Una manera (pero no la única) de implementar una generalización es mediante la herencia. Mientras la generalización es una relación conceptual entre clases, la herencia es una relación de implementación 3. En vez de duplicar la definición de SocioBiblioteca, y añadirla para definir SocioPlantilla —cosa que sería posible, y que todavía sería una generalización entre SocioBiblioteca y SocioPlantilla— a menudo se decide utilizar herencia, proporcionada por los lenguajes de programación orientados a objetos para definir SocioPlantilla en función de SocioBiblioteca. Se trata la herencia como concepto técnico en el Capítulo 2; aquí hay que tratar los temas pragmáticos de cómo se utiliza la herencia. La herencia no es la gran solución que puede parecer. El principal problema es que una subclase es dependiente de sus superclases, por lo que el uso de la herencia tiende a aumentar el acoplamiento en un sistema. Si una superclase cambia más tarde, incluso de una manera que no afecte a su comportamiento, esto puede forzar la recompilación de las subclases, o incluso puede obligar a realizar cambios en su código. Esto se suele llamar problema de clase base frágil. Otro efecto del fuerte acoplamiento entre la subclase y su superclase es que, incluso aunque se esté seguro de que una clase es correcta, no siempre es posible utilizar esta confianza para reducir la cantidad de comprobaciones que hay que hacer en la subclase. Se volverá a esto cuando se traten las pruebas en el Capítulo 19. A causa de estos problemas se recomienda utilizar la herencia entre clases, sólo cuando se modela una relación de generalización conceptual. Se tiende a utilizarla, por conveniencia, en otros casos, pero no siempre merece la pena; una implementación que utiliza la composición a menudo es mucho más robusta. Supongamos, por ejemplo, que se tiene una clase Lista disponible y que se quiere implementar una clase Agenda donde las direcciones se almacenan en una Lista. Un clásico error es hacer que Agenda herede de Lista. Una solución mejor es hacer que Agenda tenga una Lista, por ejemplo, por medio de un atributo direcciones:Lista. 3
Cuando las dos nociones se distinguen completamente —tal y como se indica en el Capítulo 2—, muchas veces se utilizan como sinónimos.
FUNDAMENTOS DE LOS MODELOS DE CLASES
77
Pregunta de Discusión 40 ¿Por qué es mejor la solución de la composición que la solución de la herencia? Compare el esfuerzo implicado en el desarrollo inicial de Agenda. Compare entonces los cambios requeridos si la implementación de la Lista o la interfaz cambian, y aquellos requeridos si se decide utilizar una clase diferente en vez de Lista (digamos, Diccionario).
P: ¿Libro y Revista se relacionan por generalización?
5.5
El modelo de clases durante el desarrollo Tal y como se ha mencionado, el modelo de clases se desarrollará gradualmente a lo largo de varias iteraciones del diseño del sistema. Se empieza almacenando las relaciones conceptuales y las funciones, independientemente de cualquier implementación lo más lejana posible. Más tarde se añaden más detalles, introduciendo nuevas operaciones y asociaciones más específicas. Los atributos son añadidos en estos procesos de refinamiento, que son repetidos hasta que se esté satisfecho de que nuestro sistema es completo y consistente.
Modelos frente a diagramas Recuerde que en el Capítulo 4 se explicó la diferencia en UML entre un modelo de un sistema, que es una colección de elementos del modelo que representan una vista del diseño en un determinado nivel de abstracción, y un diagrama de ese modelo, que es una manera de representar el modelo gráficamente. Se avisó también de que, aunque la distinción a veces es importante, en la práctica los términos modelo y diagrama muchas veces se utilizan indistintamente. Aquí hay un caso en el que es importante distinguirlos. Hay un único modelo de clases —la guía de la notación de UML lo llama, de forma más exacta, el modelo estructural estático, ya que, como se verá, puede representar más que clases— para un determinado sistema, y cada clase aparece en él sólo una vez. El modelo de clases describe la estructura estática general del sistema. Sin embargo, puede describirse con varios diagramas de clases diferentes por legibilidad. De forma similar, aunque cada clase aparece una sola vez en el modelo de clases, es posible representar una clase más de una vez en un diagrama de clases. Se podría querer hacer esto si la distribución del diagrama es más legible de esa manera. Sin embargo, ya que en el modelo subyacente hay sólo un elemento de modelo que representa la clase, es vital que los dos iconos de la clase en un diagrama no proporcionen información contradictoria sobre la clase. Una herramienta CASE puede ayudar a asegurar esta consistencia. Sin ella, se recomienda que no representar la misma clase más de una vez en un diagrama de clase.
5.6
Tarjetas CRC Una forma común de comprobar el refinamiento de un buen diseño y una buena guía es utilizar las tarjetas CRC. CRC quiere decir Clase, Responsabilidades, Colaboraciones. Las tarjetas
78
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
CRC fueron introducidas (y descritas en un escrito [2] en la mayor conferencia de OO OOPSLA’89) Kent Beck y Ward Cunningham, como técnica para ayudar a los programadores con experiencia en los lenguajes no OO a “pensar en objetos”. Aunque las tarjetas CRC no son parte de UML, añaden algunas ideas útiles en todo el desarrollo, incluyendo las etapas más tempranas en las que se está identificando las clases y sus asociaciones. Por lo tanto, se va a dar una vuelta breve para ver cómo crearlas y utilizarlas.
5.6.1
Creación de tarjetas CRC
En una pequeña tarjeta, se almacenan: • el nombre de la clase, en la parte de arriba. • las responsabilidades de la clase, en la parte izquierda. • los colaboradores de la clase, que ayudan a ejecutar cada responsabilidad, en la parte derecha de la tarjeta. Las responsabilidades de la clase describen, a alto nivel, el propósito de la existencia de la clase: están relacionadas con las operaciones que proporciona la clase, pero son más generales de lo que se podría suponer. Por ejemplo, “mantener los datos del libro” se acepta como descripción de una responsabilidad, aunque podría haber muchas operaciones necesarias para obtener o restablecer los diversos bits de información afectados. Una clase normalmente no debería tener más de tres o cuatro responsabilidades, y muchas clases tendrán sólo una o dos. Si una clase resulta tener más responsabilidades, piense si puede describirlas de manera más concisa, y si no, piense si sería mejor dividir las responsabilidades de la clase inmediatamente —si una clase no tiene responsabilidades, no debería habérsela inventado— pero las responsabilidades se revisarán cuando utilice las tarjetas CRC. Demasiadas responsabilidades se corresponden con una débil cohesión en tu modelo; muchos colaboradores se corresponden con un fuerte acoplamiento. La utilización de las tarjetas CRC puede ayudar a identificar y fijar estos dos fallos en un modelo. Al principio, puede que no sepa con qué otras clases tiene que colaborar una clase concreta para cumplir una responsabilidad; esto se aclara cuando se utilizan las tarjetas. Si un objeto de una clase colabora con un objeto de otra, lo hace enviándole un mensaje; así que una clase tiene una asociación con cada uno de sus colaboradores. La utilización de tarjetas CRC es la mejor manera de resolver en qué dirección debería ser navegable una asociación, y puede también ayudar a identificar las asociaciones, especialmente entre clases que no representan objetos del mundo real.
5.6.2
Utilización de tarjetas CRC en el desarrollo del diseño
Se pueden utilizar las tarjetas CRC para recorrer los casos de uso, resolviendo cómo el modelo de clases proporciona la funcionalidad requerida por los casos de uso, y dónde están los bits perdidos. (Se puede ver esto como el descubrimiento de colaboraciones e interacciones que llevan a cabo sus casos de uso. Se tratará la forma de almacenar esta información de forma permanente en la notación de UML en el Capítulo 9).
FUNDAMENTOS DE LOS MODELOS DE CLASES
79
Una técnica que puede ser útil es la realización de un rol. Si se está trabajando en un equipo, cada persona puede desempeñar una o más responsabilidades de las tarjetas CRC, donde el conjunto completo de tarjetas representa las clases encargadas del aspecto más importante de las responsabilidades del sistema. Se puede comprobar la completitud del diseño trabajando a través de varios escenarios de los casos de uso relevantes. Primero seleccione un escenario típico de un caso de uso. Por ejemplo, en el caso de uso tomarPrestada copia de libro, se empieza con el escenario de un prestatario que consigue tomar prestado un libro. Según se va volviendo más sólido el diseño, cualquiera que tenga la sospecha de que pueda haber algún problema con el diseño puede sugerir un escenario que ilustre dicho problema (por ejemplo, qué pasa si no hay ninguna copia disponible). La petición inicial se le da a una persona cuyas tarjetas CRC representan una clase cuyas responsabilidades incluyen la realización de un escenario; esto representa un objeto de esa clase recibiendo un mensaje del iniciador del escenario. Si el objeto necesita asistencia de uno de sus colaboradores, le enviará, en la implementación eventual, un mensaje solicitando que ejecute una operación. Cada operación que un objeto puede ejecutar debería formar parte de una de las responsabilidades de la clase del objeto; las responsabilidades de una clase pueden verse como un resumen de la operación que pueden ejecutar. Cuando se utilizan por primera vez las tarjetas CRC, probablemente sólo se considerará si es suficientemente razonable esperar que un colaborador en particular sea capaz de ayudar en la ejecución de una parte determinada de una responsabilidad; más tarde, se pueden examinar las interacciones con mayor detalle para diseñar la verdadera colección de operaciones para cada clase. Si falta un enlace —hay que hacer algo, pero ninguna clase tiene la responsabilidad de hacerlo— esto significa que el diseño es defectuoso o incompleto. Puede que se necesite crear una nueva clase, cambiar las colaboraciones y responsabilidades de una clase existente, o ambas. Es importante recordar que los cambios que se realizan tienen que preservar la coherencia general de los modelos: evitar lanzarse a la solución obvia de un caso de un problema particular que se haya presentado, sin pensar en las otras implicaciones del cambio. Muchas veces es útil escribir una tarjeta nueva para una clase si se ha alterado sustancialmente la original. Destacar que el sistema realmente trabaja por medio de la comunicación entre objetos, en vez de clases: hay que tener presente si una tarjeta CRC para una clase representa, siempre, al mismo objeto de la clase, o si están implicados varios objetos diferentes. Un efecto lateral derivado de trabajar con ejemplos de este tipo es que se crea un espíritu de equipo y todo el mundo se siente partícipe en el diseño. ¡Naturalmente algunos equipos encontrarán esta técnica más útil que otros! Como alternativa, especialmente cuando se trabaja solo, se pueden utilizar las tarjetas para que representen relaciones entre ellos —se puede considerar esto como hacer un borrador de un modelo de clases. Por ejemplo, a algunas personas les gusta agrupar las tarjetas que están relacionadas entre sí mediante generalización, con la más abstracta arriba, de forma que sólo se utiliza la clase especializada cuando la especialización particular es relevante.
5.6.3
Ejemplo de tarjeta CRC
La Figura 5.5 es un ejemplo obtenido del estudio del caso del Capítulo 3. Se empieza dibujando algunas tarjetas CRC para las clases SocioBiblioteca, Libro y Copia. Las decisiones tomadas se basan en la intuición sobre cómo colaborarán las instancias de estas clases. Entonces, se puede comprobar cómo se podría ejecutar un caso de uso particular.
80
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
SocioBiblioteca
Responsabilidades
Colaboradores
Mantener los datos sobre las copias prestadas actualmente Atender peticiones para tomar prestados y devolver copias
Copia
Copia
Responsabilidades
Colaboradores
Mantener los datos sobre una copia determinada de un libro Informar del correspondiente Libro cuando es prestado y devuelto
Libro
Libro
Responsabilidades
Colaboradores
Mantener los datos sobre un libro Saber si hay copias disponibles para tomar prestadas Figura 5.5 Ejemplo de tarjetas CRC para la biblioteca.
Aunque el texto escrito en cada tarjeta es muy escaso, no es algo a obviar: el peso que tiene es el adecuado. Las tarjetas CRC llenas significan, bien responsabilidades expresadas de forma incorrecta, o bien clases no cohesivas. Si se sigue a través del procesamiento de una petición realizada por un PrestatarioLibro para Tomar prestada una copia de un libro, se puede comprobar si sería necesario que estuviesen implicadas otras clases. Se pueden comprobar también los mensajes que se podrían enviar. Si un objeto no puede cumplir una responsabilidad, necesita bien añadirla a su propia definición o bien definir una colaboración con otra clase que la cumpla. Se puede también examinar la relación de generalización entre SocioBiblioteca y SocioPlantilla viendo hasta qué punto comparten las responsabilidades y los colaboradores. Se encuentra que todas las responsabilidades de un SocioBiblioteca las comparte un SocioPlantilla, pero no al revés. Esto confirma la idea de que un SocioPlantilla es una especialización de un SocioBiblioteca. De manera más general, se pueden buscar oportunidades para refactorizar el modelo de clases en uno mejor.
5.6.4
Refactorización
La refactorización es el proceso de alterar el modelo de clases de un diseño orientado a objetos sin alterar su comportamiento visible. Por ejemplo, si en cualquier etapa uno se da cuenta de que una operación no encaja de forma apropiada en la clase en la que se encuentra —las responsabilidades no están situadas en las clases de la mejor manera posible— un paso de refactorización sería obligar a poner las operaciones donde deberían estar, actualizando cualquier documentación de código y diseño que se necesite modificar. La norma ESCRIBA UNA SOLA VEZ es una buena fuente para obtener los pasos de refactorización. Por ejemplo, si uno se encuentra con que tiene dos clases con responsabilidades
FUNDAMENTOS DE LOS MODELOS DE CLASES
81
y comportamiento solapados, muchas veces se pueden tomar los comportamientos comunes y crear una nueva superclase de la que ambas hereden. En realidad, este proceso puede ayudar en uno de los aspectos más difíciles del análisis y el diseño orientados a objetos, a saber, en encontrar las abstracciones correctas que hagan el diseño limpio y robusto. Tales clases, a menudo, no destacan nada en el documento de requisitos —porque no son clases de dominio en sí, aunque expresan lo que varias clases de dominio tienen en común— por lo que la técnica de identificación de nombres no las encontraría. Véase el libro de Kent Beck [3] para ver ideas de refactorización.
P: Analice el ejemplo del préstamo de la biblioteca, utilizando las tarjetas CRC vistas y modifíquelas si fuera necesario.
P: Intente realizar un análisis similar para devolver un libro y devolver una revista. RESUMEN
Este capítulo ha presentado los diagramas de clases, que representan la estructura estática del sistema que se va a construir. Se ha tratado cómo identificar las clases y sus asociaciones, y el concepto de multiplicidad de una asociación. Se ha mostrado cómo se representan los atributos y las operaciones de una clase. A continuación, se ha cubierto la generalización, que puede ser implementada por ejemplo, por medio de la herencia. Finalmente, se ha tratado el rol del modelo de clases a través del desarrollo y se ha ilustrado el uso de las tarjetas CRC para validar un modelo de clases.
PREGUNTAS DE DISCUSIÓN
1. ¿Por qué las clases deben tener los nombres en singular? ¿Cree que hay alguna excepción? 2. ¿Cuáles son las ventajas y desventajas de decidir formalmente desde qué perspectiva se está dibujando un modelo de clases en particular? 3. ¿Bajo qué circunstancias se podrían tener clases en un modelo que no se correspondiesen con objetos del dominio? ¿Puede pensar en algún caso donde esto sea necesario en el ejemplo de la biblioteca? 4. ¿Cómo se establecen las asociaciones en la implementación? ¿Cómo describiría esto su modelo de clases, si fuese necesario? ¿Depende de la etapa de desarrollo? ¿Cómo? 5. Piense en las nociones estáticas y dinámicas de las asociaciones mencionadas en la Anotación Técnica de UML, de la sección 5.2. Construya un pequeño ejemplo de clases que se asocian dinámicamente, pero no estáticamente, y piense en las dos versiones del diagrama de clases de UML que obtenga adoptando cualquier noción. ¿Cuáles cree que son las ventajas y desventajas de cada convención? ¿Qué significa la ausencia de una asociación entre clases, en cada caso?
Capítulo
6 Más sobre los modelos de clases
En este capítulo se consideran otras funciones de los diagramas de clases de UML. Son menos importantes que las descritas en el Capítulo 5, y puede decidir saltarse este capítulo en una primera lectura. Sin embargo, una de las fortalezas de UML es su expresividad, y aquí se da una muestra de ello, cubriendo la mayoría (pero no todas) de las funciones de los diagramas de clases. A lo largo del camino se tratarán algunos aspectos de UML que no son específicos de los modelos de clases, pero que aquí se utilizarán por primera vez. Estos son las restricciones, los mecanismos de principal extensibilidad de UML (estereotipos, propiedades y valores etiquetados), interfaces, dependencias y paquetes. Empezaremos considerando información extra que puede almacenarse junto con la asociación entre dos clases.
6.1
Más sobre asociaciones 6.1.1
Agregación y composición
La agregación y la composición son tipos de asociación: en vez de simplemente mostrar que dos clases están asociadas se puede decidir mostrar más información sobre qué tipo de asociación tienen. La agregación y la composición son dos formas de almacenar que un objeto de una clase es parte de un objeto de otra clase. Por ejemplo, la Figura 6.1, obtenida del estudio del caso del Capítulo 15, muestra que un Módulo es parte de un CursoDeDoctorado. Esta notación, con el diamante vacío, denota agregación, que es una manera general de denotar una relación todo-parte en UML. Destacar que el diamante se coloca en el extremo del todo, no de la parte. También se pueden utilizar todas las otras notaciones que van con la asociación. Por ejemplo, se pueden mostrar las multiplicidades
84
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
1..*
6..*
CursoDeDoctorado
Módulo
Figura 6.1 Una agregación.
como con una asociación normal. Destacar que a un objeto se le permite ser parte, a la vez, de varios objetos: en nuestro caso, un único Módulo podría ser parte de varios CursosDeDoctorado diferentes. (Por ejemplo, la Ingeniería del Software con Objetos y Componentes 2 es parte tanto del curso de doctorado de Ingeniería del Software como del de Informática). Normalmente, uno no se toma la molestia de nombrar una asociación de agregación, ya que el nombre de la asociación sería “es una parte de” y esto está ya reflejado en la notación de agregación, por lo que no es necesario indicarlo también con palabras. Sin embargo, si ayuda a nombrar la asociación (por ejemplo, si se quiere describir la función de la parte en el todo) esto es perfectamente legal. Desde la carencia de cualquier otra restricción nueva se ve que la agregación es fundamentalmente una idea conceptual: ver una agregación en un modelo de clases debería ayudar a comprender las relaciones entre las clases a nivel informal, pero no añade ninguna información formal sobre cómo deben implementarse o qué se puede hacer con ellas. (Si bien, véase la Pregunta de Discusión 47 más tarde en este capítulo: es discutible que alguna agregación implique algunas cosas. Aunque actualmente en UML no se hace). La composición es un tipo especial de agregación que impone algunas restricciones más. En una asociación de composición, el todo posee fuertemente a sus partes: si se copia o borra el objeto todo, sus partes se copian o borran con él. (Para que esto se pueda implementar, la asociación debe ser navegable desde el todo hacia la parte, aunque UML no especifica esto de forma explícita). La multiplicidad en el extremo del todo en una asociación de composición tiene que ser 1 ó 0..1 —una parte no puede pertenecer a más de un todo por composición. El ejemplo con Módulo y CursoDeDoctorado no sigue estas restricciones, por lo que una composición no sería apropiada en este caso. Por otro lado, piense en una aplicación del juego tres en raya en el marco de trabajo considerado en el Capítulo 16, que de forma sensata debería implementarse en función de las clases Cuadrado y Tablero. Cada Cuadrado es parte de un Tablero, y no tendría sentido copiar o borrar un objeto Tablero sin copiar o borrar los objetos Cuadrado que forman el Tablero. Así que, en este caso, la composición es apropiada, y se muestra en la Figura 6.2. La composición se representa como la agregación, salvo que el diamante está relleno. ADVERTENCIA Desde nuestra experiencia, la gente nueva en el modelado orientado a objetos utiliza la agregación y la composición demasiado a menudo. Recuerde que ambos son tipos de asociación, por tanto, siempre que una asociación o composición sean correctas, lo es una simple asociación. En caso de duda, utilice una simple asociación.
MÁS SOBRE LOS MODELOS DE CLASES
85
1 9
Tablero
Cuadrado
Figura 6.2 Una composición.
Pregunta de Discusión 41 Piense en algunos casos donde la agregación o composición sean apropiadas. Por ejemplo, ¿Qué pasa con la relación entre un Empleado y un Equipo? ¿Entre Rueda y Coche? ¿Cuenta y Cliente? ¿Puede describir diferentes contextos en los que habría clases con estos nombres, donde, debido a las diferencias en el contexto, serían apropiadas diferentes relaciones?
Pregunta de Discusión 42 Si sabe C u otro lenguaje orientado a objetos que pueda hacer referencia a los objetos bien por valor o bien por referencia, sabe que a menudo la gente distingue entre agregación y composición diciendo que se tiene una agregación si el todo tiene una referencia o puntero a la parte, y composición si el todo contiene la parte por valor. Piense por qué es esto, y si es apropiado.
Consejo mnemotécnico El símbolo más fuerte, el diamante sólido, representa la relación más fuerte, la composición. Cuando se borra un diamante sólido, hay que borrar el contenido del símbolo del diamante, así como el borde, tal y como cuando se borra un objeto compuesto hay que borrar las partes al igual que el todo1.
6.1.2
Roles
Se ha visto cómo nombrar una asociación. Muchas veces se puede leer un nombre de asociación en ambas direcciones (“está tomando”, “es tomado por”). A veces, sin embargo, es más legible tener nombres separados para los roles que desempeñan los objetos en la asociación, o bien como nombre de la asociación o bien en lugar del mismo. Por ejemplo, la Figura 6.3 deja claro que el rol del Estudiante en esta asociación es dirigido, que podría ser útil, por ejemplo, si dirigido fuese un término comúnmente utilizado en un documento adjunto. Se puede poner tanto el nombre de la asociación como los nombres de rol, aunque lo normal será omitirlos en la mayoría de los casos. Pregunta de Discusión 43 ¿Sería útil dar nombres de rol a la asociación entre Estudiante y Módulo? ¿Por qué? 1
Gracias a Ben Kleiman por sugerir esto.
86
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
Director de Estudios
DdE
dirigido
Estudiante
Figura 6.3 Una asociación con nombres de rol.
6.1.3
Navegabilidad
Considere una situación en la que se tiene una asociación entre dos clases, por ejemplo, entre Módulo y Estudiante, tal y como se muestra en la Figura 6.4. El diagrama almacena que: • Para cada objeto de la clase Estudiante hay seis objetos de la clase Módulo que están asociados con el Estudiante; • Para cada objeto de la clase Módulo hay algunos objetos Estudiante (el número de estudiantes no está especificado) asociados con el Módulo.
está cursando
Módulo
Estudiante 1..*
6
Figura 6.4 Asociación sin navegación.
Sin embargo, todavía no almacenan si sería posible obtener estos objetos en ambas direcciones o sólo en una. ¿Debería un objeto Estudiante ser capaz de enviar mensajes a los objetos Módulo asociados con él? ¿Debería un objeto Módulo ser capaz de enviar mensajes a los objetos Estudiante que representan a los estudiantes del curso? ¿Y ambos? Se puede poner una flecha en uno o en ambos de los extremos de la línea de la asociación para representar que es posible enviar mensajes en la dirección de la flecha. Por ejemplo, la Figura 6.5 indica que un objeto Módulo puede enviar mensajes a los objetos que representan a los estudiantes del Módulo, pero no al revés.
está cursando
Módulo
Estudiante 1..*
6
Figura 6.5 Asociación con navegación en un sólo sentido.
MÁS SOBRE LOS MODELOS DE CLASES
87
Se dice que Módulo conoce a Estudiante, pero no al revés. Una asociación como esta debería ser implementada, por ejemplo, permitiendo a Módulo tener un atributo (estudiantes: ColecciónEstudiante) que sea una colección de objetos correspondiente a los estudiantes que reciben el curso. Esto tiene consecuencias tanto buenas como malas. La aplicación puede necesitar esta información para estar disponible fácilmente: en nuestro caso, por ejemplo, se tienen que crear listas de estudiantes para cada curso, ya que los profesores adjuntos las necesitan, y la manera más fácil de hacer esto es permitir al objeto Módulo recuperar un conjunto de nombres de estudiantes a través del envío de un mensaje a cada objeto de tal colección de estudiantes. Sin embargo, si la clase A conoce a la clase B, entonces es imposible reutilizar la clase A sin la clase B; así no se introduciría la navegabilidad hasta que lo requiera la aplicación actual o haya una buena razón para pensar que se necesitará en el futuro. A veces es fundamental permitir la navegabilidad en ambos sentidos a lo largo de la asociación, pero tal decisión debe ser justificada en cada caso. Pregunta de Discusión 44 (Después de considerar el Capítulo 15). ¿Debería esta asociación ser navegable también en la otra dirección —esto es, debería el Estudiante conocer al Módulo? ¿Por qué?
La notación cruzada para mostrar la no-navegabilidad es nueva en UML2. Utilizando tanto flechas como cruces es posible ser totalmente explícito sobre cómo las asociaciones pueden ser recorridas. Sin embargo, no se deseará o podrá siempre incluir toda esta información. Cuando se construye el modelo de clases inicial, se debería (de forma adecuada) no tener todavía decidido como debería ser la navegabilidad. Incluso cuando se han tomado todas las decisiones, no se debería querer almacenarlas explícitamente: haciéndolo dificultoso, y muchas herramientas de UML no permiten todavía el uso de la notación cruzada. UML sugiere diferentes convenios que podrían ser útiles para ahorrar esfuerzo y reducir el desorden en los diagramas. Por ejemplo, la ausencia de una flecha podría significar no-navegabilidad, o podría significar que la navegabilidad está sin especificar. En este libro no utilizaremos habitualmente la notación cruzada. Si un diagrama de clases muestra alguna flecha de navegabilidad se puede suponer que todas están incluidas; sino, no está especificada la navegabilidad. Pregunta de Discusión 45 ¿Cuándo, si es que hay alguna vez, podría una asociación no ser navegable en ninguna dirección?
Pregunta de Discusión 46 ¿Cuándo debería decidirse la navegabilidad?
Pregunta de Discusión 47 Según UML, la navegabilidad de una asociación es independiente de si la asociación es una agregación, una composición o ninguna de ellas. ¿Qué navegabilidad cree que tiene una asociación de agregación? ¿Y una composición? ¿Puede afirmar qué tipo de navegabilidad debe tener siempre cualquier tipo de asociación para tener sentido?
88
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
6.1.4
Asociaciones calificadas
De vez en cuando es útil dar más detalles de los que se tienen sobre una asociación. Considere de nuevo la aplicación del tres en raya en el marco de trabajo del juego descrito en el Capítulo 16, que está implementado utilizando las clases Cuadrado y Tablero, y suponga que se identifica que la relación entre Cuadrado y Tablero es a través de los atributos fila y columna, cada uno de los cuales toma un valor entre 1 y 3. Si nos olvidamos por ahora del hecho de que la asociación es una composición (recuerde que una asociación es siempre correcta cuando una agregación o composición lo son), la asociación puede aparecer como en la Figura 6.6.
Tablero
1
9
Cuadrado
Figura 6.6 Asociación sencilla entre Cuadrado y Tablero.
Sin embargo, no capta la idea de localizar los nueve Cuadrados dando los nueve pares de valores de los atributos fila y columna. Para hacer esto se utiliza una asociación calificada como la mostrada en la Figura 6.7.
1 Tablero
Fila:{1,2,3}
1 Cuadrado
Columna:{1,2,3}
Figura 6.7 Asociación calificada.
El 1 en el extremo derecho, junto a Cuadrado, especifica que si se tiene un objeto Cuadrado, llamado b, y se especifican los valores tanto para el atributo fila como para el atributo columna, entonces hay exactamente un objeto Cuadrado asociado al objeto Tablero b. El 1 en el extremo del Tablero significa lo de siempre: cada objeto de la clase Cuadrado está exactamente en un Tablero. Fundamentalmente, para cada Tablero se tiene una tabla de búsqueda en la que se localiza un Cuadrado por su fila y columna. Las condiciones descritas aseguran fácilmente que cada Cuadrado aparezca en una sola tabla de búsqueda una sola vez. Cabe destacar que, en general, puede haber más de un objeto para un valor concreto de la clave de búsqueda: los elementos en la tabla de búsqueda pueden ser conjuntos, no sólo elementos únicos.
MÁS SOBRE LOS MODELOS DE CLASES
1 Tablero
Fila:{1,2,3}
89
1 Cuadrado
Columna:{1,2,3}
Figura 6.8 Composición calificada.
En realidad se puede combinar la notación de asociación calificada con otros adornos de las asociaciones; por ejemplo se puede añadir información de que esta asociación particular es una composición, tal y como aparece en la Figura 6.8. ¡Puede que ya se haya dado cuenta de que astutamente no se ha dicho a qué clase pertenecen los atributos fila y columna! Podrían ser atributos de Cuadrado; pero formalmente son atributos de la asociación. Cada enlace entre un Tablero y un Cuadrado (recuerde que un enlace es una instancia de una asociación) tiene valores para fila y columna, que identifican dónde se encuentra el Cuadrado dentro del Tablero.
P: (Obtenido de un ejemplo de la guía de notación de UML). Dibuje una asociación entre Persona y Banco para almacenar el hecho de que una Persona puede estar asociada con muchos Bancos, pero que dados un Banco y un número de cuenta, hay como mucho una Persona con ese número de cuenta en ese banco.
6.1.5
Asociaciones derivadas
Mientras se desarrollan diagramas de clases, a menudo surge la pregunta sobre si se necesita mostrar una asociación o si es suficiente deducir su existencia a partir de algún elemento del diagrama. Por ejemplo, si un Estudiante está asociado con Módulo por medio de está cursando y Módulo está asociado con ProfesorAdjunto a través de enseña curso, ¿Sería necesario también mostrar una asociación enseña estudiante entre ProfesorAdjunto y Estudiante, que relacionara un profesor adjunto con todos los estudiantes que reciben los cursos que enseña ese profesor adjunto? Pregunta de Discusión 48 ¿Cuáles son las ventajas y desventajas de hacer esto?
Se puede hacer esto o no, o se puede utilizar la tercera opción que proporciona UML, que consiste en mostrar esa asociación como una asociación derivada. En otras palabras, existe automáticamente una vez que se han implementado las asociaciones principales: el diseñador no necesita tener en cuenta de forma separada cómo implementar esta asociación. Una asociación derivada se muestra utilizando un signo “/” delante de su nombre, como en la Figura 6.9. (Los triángulos negros, a propósito, pueden utilizarse sobre cualquier nombre de asociación e indican, simplemente, la dirección que describe el nombre de la asociación).
90
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
está cursando
Estudiante
Módulo
enseña curso
/enseña estudiante
ProfesorAdjunto
Figura 6.9 Una asociación derivada.
ANOTACIÓN TÉCNICA DE UML En UML puede haber elementos derivados de diferente manera que las asociaciones derivadas. En general, un elemento derivado se distingue del elemento normal añadiendo un símbolo “/” delante de su nombre. Por ejemplo, se puede indicar que un atributo de una clase es derivado —esto es, que se puede calcular su valor en un determinado objeto si se conoce el valor de todos los atributos normales del objeto, y se tiene acceso a otros objetos que conoce— poniendo un símbolo “/” delante del nombre del atributo en el icono de clase. (Puede que quede más claro, sin embargo, definir una operación de la clase, sin argumentos y cuyo valor de retorno es un valor derivado. Es cuestión de gustos).
En el ejemplo que se ha dado hay una sola posibilidad sensata de lo que realmente tiene que ser una asociación derivada. Un ProfesorAdjunto p está asociado mediante enseña estudiante con un Estudiante e si, y sólo si, hay algún Módulo m tal que, tanto e está asociado con m mediante está cursando como p está asociado con m mediante enseña curso. Si se quiere almacenar esto de forma explícita en el diagrama, se puede bien escribirlo en lenguaje natural en una anotación, o bien escribirlo de manera formal.
6.1.6
Restricciones
Una restricción es una condición que tiene que ser satisfecha por cualquier implementación correcta de un diseño. Por ejemplo, un sistema con las asociaciones enseña curso, está
MÁS SOBRE LOS MODELOS DE CLASES
91
cursando y enseña estudiante tiene un error si la condición que se añade en la sección anterior no se satisface siempre. Sin embargo, las restricciones pueden ser más generales que esto. Pueden restringir elementos del modelo de forma individual, o colecciones de elementos del modelo. Uno de los usos más comunes —y seguros— es expresar un invariante de clase. Por ejemplo: {self.númeroEstudiantes >50 implica (no(self.aula = 3317))}
como invariante de la clase Módulo indica que para cada objeto de la clase Módulo se cumple siempre que si el número de estudiantes matriculados en el curso es mayor que 50 entonces el curso no tendrá lugar en el aula 3317 (probablemente, porque el aula 3317 tiene sitio sólo para 50 personas). Esta restricción formal está escrita en OCL (Object Constraint Language, en Lenguaje de Restricción de Objetos, LRO), que es el Lenguaje de Restricción de Objetos que UML adopta para sus propósitos. Véase el Panel 6.1 para más información sobre OCL. Otra situación común en la que las restricciones pueden ser útiles es aquella en la que hay un “o exclusivo” entre dos relaciones de asociación: un objeto forma parte de (un enlace que es una instancia de) exactamente una de las asociaciones. Por ejemplo, en el ejemplo de la biblioteca del Capítulo 3, se asume que, aunque puede haber varias copias de un libro, había una sola copia de una revista. Ahora supongamos que se quiere modelar un sistema en el que cada objeto Copia representa, bien una copia de un Libro, o bien una copia de una Revista. Se podría comenzar con el diagrama mostrado en la Figura 6.10; pero esto no excluye la posibilidad disparatada de que una Copia podría estar asociada tanto con un Libro como con una Revista, o con ninguno. Para hacer esto se puede utilizar una restricción xor, tal y como aparece en la Figura 6.11. La restricción xor no está escrita en OCL; es una restricción especial predefinida que es parte de UML.
1..* 0..1
es una copia de
Libro
Copia 1..*
es una copia de 0..1
Revista
Figura 6.10 Un diagrama bajo restricción.
92
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
1..* 0..1
es una copia de
Libro
Copia 1..* {xor}
es una copia de 0..1
Revista
Figura 6.11 Utilizando una restricción xor.
Pregunta de Discusión 49 ¿Se le ocurre alguna forma de solucionar este problema sin introducir una restricción? Consejo: piense en la posibilidad de introducir una clase extra.
El hecho de que UML permita añadir restricciones de una manera bastante general aumenta su poder de forma importante. Sin embargo, ¡como siempre, es importante no dejarse llevar por potentes funciones! Cualquier persona tiene que poder leer cualquier diagrama que se escriba, y eso más difícil cuanto más complejo sea el diagrama —se deberían utilizar poco las restricciones—, considerando en cada caso si es sensato introducir más información. Hay también razones técnicas para evitar los diseños que necesitan utilizar restricciones, las cuales limitan varios elementos del modelo que no están contenidos dentro de una clase; tal y como apunta Ian Graham, tales restricciones indican dependencias entre los elementos del modelo restringido, que pueden impedir tanto el mantenimiento como la reutilización. Pregunta de Discusión 50 En realidad, las restricciones pueden expresar parte de la información que de forma general se expresa en UML utilizando una notación especializada más conveniente. ¿Cómo podría especificar la multiplicidad de una asociación utilizando restricciones en vez de la notación normal de multiplicidad?
PANEL 6.1
OCL (Object Constraint Language, en español, Lenguaje de Restricción de Objetos, LRO)
El Lenguaje de Restricción de Objetos se pretende que sea: • formal, de manera que las restricciones que describa no sean ambiguas. • fácil de utilizar, de forma que todo desarrollador lo utilice para escribir restricciones.
MÁS SOBRE LOS MODELOS DE CLASES
93
OCL surgió a partir del método Syntropy desarrollado por Steve Cook y John Daniels, y ha sido desarrollado por IBM como lenguaje de modelado de negocio. Se utiliza en los documentos de semántica de UML para especificar las restricciones para que los modelos de UML estén correctamente formados, al igual que está disponible para los usuarios de UML para indicar restricciones en sus propios modelos. La especificación de UML (véase la página web de este libro) incluye detalles completos de OCL, y hay disponible también un libro [52]. Combinar los dos objetivos de OCL es muy difícil, y no se tiene la convicción de que OCL alcance, todavía, cualquier objetivo. No tiene una semántica formal, por lo que no puede decirse que sea un lenguaje formal. Por el contrario, no está claro que un lenguaje formal con el tipo de potencia requerido pueda llegar a ser realmente fácil de aprender. Mientras le animamos a que aprenda más sobre OCL, nos gustaría darle también un aviso: ADVERTENCIA Mientras la utilización de notaciones formales puede ser útil, ya que las personas implicadas realmente saben cómo leer y escribir en él, una restricción en español es mucho más útil que una restricción en un lenguaje formal con errores, o cuya intención los lectores no entienden. Por lo que si usted y sus compañeros no están seguros de saber escribir algo en OCL, utilicen el español o su lengua habitual.
6.1.7
Clases asociación
Algunas veces es tan importante la manera en que están asociados dos objetos como los objetos en sí. Piense, por ejemplo, en la asociación entre Estudiante y Módulo. ¿Dónde debería almacenar el sistema las notas del estudiante en ese curso? Las notas están relacionadas realmente con el par formado por un estudiante y un módulo. Se podría pensar en implementar un objeto por cada par: el objeto almacenaría las notas del estudiante en ese curso, evitando confundirlos conceptualmente con las notas de otro estudiante de ese curso, o con las notas de ese estudiante en otro curso. Esto significa tratar la asociación entre las clases Estudiante y Módulo como una clase; por supuesto una instancia de la asociación relaciona un estudiante con un módulo, y entonces se dice que son datos adjuntos a ese enlace. Probablemente se quiera también realizar operaciones, aunque sólo sea para obtener y establecer las notas. El resultado es algo que es tanto una clase como una asociación, que se denomina clase asociación. La notación aparece en la Figura 6.12. El icono de clase y la línea de asociación tienen que tener el mismo nombre, ¡porque son lo mismo! Esto presenta un pequeño problema, ya que las asociaciones normalmente tienen como nombre frases verbales y las clases, frases nominales. (También, si se utiliza la convención de mayúsculas y minúsculas que dice que los nombres de las asociaciones se escriben en minúscula y que los nombres de las clases van en mayúsculas, hay que buscar un caso especial para las clases asociación, ¡ya que no se pueden cumplir ambas convenciones a la vez!) Se puede pensar un nombre mejor que está cursando para cubrir ambos.
94
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
1..*
está cursando
6
Estudiante
Módulo
está cursando nota : entero
Figura 6.12 Una clase asociación.
Por supuesto, hay otras formas de almacenar la misma relación entre Estudiante y Módulo: se podría pensar en una nueva clase, por ejemplo Nota, y asociarla con ambas clases como siempre, tal y como se muestra en la Figura 6.13.
1..*
está cursando
6
Estudiante
Módulo
1
1
está cursando 6
nota : entero
1..*
Figura 6.13 Evitando una clase asociación.
Pregunta de Discusión 51 ¿Cuáles son las ventajas y los inconvenientes de ambas aproximaciones? Piense: qué pasaría si el mismo estudiante cursa el mismo módulo dos veces por alguna razón; si se necesita una restricción en cualquiera de los casos; si habría, en realidad, alguna diferencia en las implementaciones que permitiría cualquier notación.
MÁS SOBRE LOS MODELOS DE CLASES
6.2
95
Más sobre clases En el Capítulo 2 se destacó que una clase realmente cumple dos propósitos: define la interfaz que los objetos presentan al resto del sistema, y define una implementación de dicha interfaz. Algunas veces es importante para el diseño separar los dos conceptos, particularmente para distinguir entre niveles diferentes de dependencia entre los elementos del modelo. En realidad, hay tres variantes de la idea de clase —tres maneras distintas de clasificar los objetos— que se considerarán juntas. Varían en la cantidad de información que contienen sobre los atributos, operaciones, y sus implementaciones. En resumen, son: 1. Interfaz. Una interfaz especifica una lista de operaciones que tiene que proporcionar todo aquello que cumpla la interfaz. No tiene asociadas las implementaciones con ninguna de las operaciones. Una interfaz no especifica nada sobre el estado de un objeto que lo incluye; por lo que no tiene atributos y ninguna asociación puede ser navegada desde una interfaz. Se tratarán las interfaces con mayor detalle en la siguiente subsección. 2.
<>. Una clase que tiene el estereotipo <> define la implementación física de sus operaciones y atributos. Puede implementar un tipo.
Cualquier objeto tiene exactamente una clase implementación, aunque puede tener varios tipos e incluir varias interfaces. Normalmente se trabaja con clases sin estereotipos, pero algunas veces la precisión extra que proporcionan los estereotipos es útil. ANOTACIÓN TÉCNICA DE UML Anteriores ediciones de este libro también incluían clases con el estereotipo <> en este apartado. La diferencia entre tal clase y una interfaz, es que las interfaces pueden no tener atributos en UML 1.x, y las clases <> sí podrían. Ambas ideas todavía existen en UML2, pero ahora las interfaces también tienen permiso para tener atributos. Nosotros pensamos que esto hace a las clases <> prácticamente redundantes.
PANEL 6.2
Estereotipos
Un estereotipo es la manera que tiene UML de adjuntar clasificaciones extra a los elementos de un modelo. Es una de las formas que UML ha hecho ampliable. Describe un elemento del modelo y se sitúa en el diagrama cerca del elemento. Por ejemplo, la Figura 6.14 muestra el estereotipo <> sobre la clase símbolo y el estereotipo <<use>> sobre una flecha 2, de dependencia. Esto proporciona información extra sobre la clase y sobre la dependencia. Algunos estereotipos están predefinidos en UML; están disponibles automáticamente y se pueden redefinir. Un ejemplo es <>. Más interesante, es 2 Técnicamente, en la especificación de UML2, ni unos ni otros son realmente un estereotipo —son aplicaciones de la notación de estereotipos para indicar metaclases. Sin embargo, esto hace que no haya diferencia entre la notación o el uso de UML, por eso se obviará la distinción.
96
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
<> Stringifiable
Módulo stringify() : Cadena
stringify() : Cadena
...
Stringifiable <<use>>
imprime
Impresora
Figura 6.14 Una interfaz y su utilización.
que se pueden definir estereotipos propios para expresar cualquier otra clasificación útil. Por ejemplo, si se estuviese implementando una aplicación que tuviese clases persistentes podría decidir, correctamente, definir un estereotipo <> para indicar qué clases son persistentes. UML también permite definir un nuevo icono gráfico para representar una clase <>. El equipo del proyecto necesita, por supuesto, ponerse de acuerdo sobre dónde documentar los estereotipos. En este libro se intenta comentar la realidad, siempre que se utiliza un estereotipo que no es una parte predefinida de UML.
6.2.1
Interfaces
Una interfaz especifica algunas operaciones de algunos elementos del modelo, tales como una clase, que son visibles fuera del mismo. No necesita especificar todas las operaciones que soporta el elemento, por lo que el mismo elemento podría incluir varias interfaces diferentes. En UML2, una interfaz podría también especificar algunos atributos y asociaciones. Esto puede ser útil esporádicamente, pero es necesario utilizarlo con cuidado. La clave es especificar —si hay distintos modos en los que una clase podría aparecer para aportar un atributo, por ejemplo, la interfaz no restringe esta elección. Todos los elementos de una interfaz son públicos. Una interfaz se define en un diagrama de clases utilizando un rectángulo como el del icono de clase, con las operaciones listadas en un compartimento del rectángulo como si fuera una clase. El icono se marca con <>, y no tiene un compartimento de atributo, porque una interfaz no puede tener atributos. Por ejemplo, la Figura 6.14 define una interfaz que se satisface cuando se entiende el mensaje “stringify” y devuelve una cadena. El diagrama también muestra cómo se utilizan las interfaces en UML.
MÁS SOBRE LOS MODELOS DE CLASES
97
La clase Módulo concuerda (o realiza, o soporta) la interfaz; esto es, Módulo tiene un método stringify del tipo correcto. Esto aparece de dos maneras, éstas son: 1. El círculo pequeño etiquetado Stringifiable junto al icono Módulo; 2. La flecha desde Módulo a Stringifiable. Hay que destacar la utilización de la flecha de realización: es igual que una flecha de generación excepto por la línea de guiones. Tal y como sugiere la notación, incluir una interfaz puede verse como una forma de herencia débil. El Módulo proporciona al menos las operaciones especificadas en Stringifiable, y puede proporcionar más, así como con la herencia entre clases. Sin embargo, Módulo tiene que proporcionar sus propias implementaciones, ya que la interfaz Stringifiable no tiene ninguna implementación: sólo se heredan las especificaciones de las operaciones. Por supuesto, no es necesario mostrar la misma información dos veces. Puede ser conveniente omitir la flecha de realización, especialmente cuando un diagrama contiene muchas clases que realizan la misma interfaz. El icono de clase de la interfaz tiene que estar ahí, para definir lo que significa dicha interfaz. La clase Impresora depende sólo de la interfaz Stringifiable; esto es, Impresora no se preocupa de ninguna otra función de una clase Modulo; siempre que proporcione el método stringify la Impresora puede utilizarla. Esto se muestra a través de medio circulo unido a la clase Impresora. Por razones obvias, el círculo y el medio círculo son llamados notación de conexión y bola. La parte de “conexión” es nueva en UML2; ésta es muy conveniente sobre todo si el diseño tiene el uso de muchas interfaces. Hablando estrictamente, debería haber una asociación entre las clases Impresora y Módulo, especialmente si tomamos una vista estática de las asociaciones (como vimos en el Capítulo 5). Vimos una en anteriores ediciones de este libro. Sin embargo, es un poco chapucero tener la clases unidas por una asociación así como por la bola y notación de conexión y bola. Para la mayoría de los casos, sólo la notación de conexión y bola es bastante informativa, de tal forma que hemos decido omitir la asociación. El diagrama también muestra la flecha de dependencia con el estereotipo <<use>> indicando que Impresora depende de la interfaz Stringifiable. Como la flecha de realización, esto puede ser omitido del diagrama desde que los sockets traen la misma información. La Figura 6.14 ha sido diseñada para mostrar toda la notación utilizada con interfaces. La Figura 6.15 ilustra con notación más parsimoniosa. Cuando usamos muchas interfaces, es común situar todas las cajas de interfaz juntas, siempre en filas ordenadas desde la parte principal del diagrama de clases. Esto facilita el buscar qué operaciones aporta cada interfaz, conservando el diagrama de clases principal ordenado. El diagrama de clases principal puede entonces utilizar sólo los nombres de las interfaces, con la notación de conexión y bola.
P: ¿Cómo se implementa una interfaz en su lenguaje? Escriba el esqueleto del código que se corresponde con la Figura 6.14. Pregunta de Discusión 52 ¿Por qué son todos los elementos de una interfaz públicos?
98
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
<> Stringifiable
Módulo
stringify() : Cadena
stringify() : Cadena ...
Stringifiable imprime
Impresora
Figura 6.15
Notación más escasa para la dependencia de interfaz.
Tal y como se verá en el Capítulos 13, los componentes pueden incluir interfaces también. ANOTACIÓN TÉCNICA DE UML En realidad, cualquier clasificador puede incluir una interfaz. Los clasificadores incluyen, por ejemplo, actores y casos de uso, así como las clases, subsistemas y componentes que se han mencionado. La notación es la misma que se ha descrito; por ejemplo, se puede adjuntar un círculo con un palito al símbolo del caso de uso. La definición en sí de la interfaz aparece siempre como un rectángulo. Nos hemos referido al rectángulo como el símbolo de la clase, pero de hecho en UML2 es más general el símbolo para cualquier clasificador. Por defecto, éste es leído como una clase.
6.2.2
Clases abstractas
Una idea relacionada es la clase abstracta. Una clase abstracta, que se enseña utilizando la propiedad {abstract} en el icono de clases, puede tener implementaciones definidas para algunas de sus operaciones. Sin embargo, decir que es abstracta significa que, al menos, una de sus operaciones no tiene definida implementación. Por lo tanto, no se puede instanciar una clase abstracta. Una clase abstracta en la que ninguna de las operaciones tiene una implementación, y en la que no hay atributos, es, efectivamente, lo mismo que una interfaz. Los programadores de C a menudo utilizan las clases abstractas para hacer lo mismo que un programador de Java hace con las interfaces de Java. Una clase abstracta puede ser utilizada correctamente para implementar una interfaz de UML en una aplicación de C. En este caso, una clase que implementa una interfaz hereda de la clase abstracta.
MÁS SOBRE LOS MODELOS DE CLASES
99
Pregunta de Discusión 53 En el Capítulo 2 se dijo que las clases a menudo tienen un rol de fábrica. ¿En qué sentido una clase abstracta es una fábrica de objetos?*. * Gracias a Bend Kahlbrandt por presentar esta cuestión.
PANEL 6.3
Propiedades y valores etiquetados
Acabamos de ver un ejemplo en el que se añade a un elemento, una propiedad para dar más información sobre él. Este es un potente mecanismo en UML, y es la otra manera fundamental, aparte del estereotipado, en el que UML se ha extendido. Al igual que los objetos tienen valores para sus atributos, los elementos del modelo, como son las clases, tienen valores para sus propiedades. Las propiedades fundamentalmente tienen que ver con el modelo en vez de con el sistema implementado. Por ejemplo, en un modelo de UML todas las clases tienen una propiedad lógica esAbstracta, que se supone que tendrá el valor verdadero si la clase es abstracta y falso en otro caso. No hay ninguna sugerencia de que deba haber un atributo real esAbstracto en cualquier parte del sistema: la propiedad sólo proporciona al diseñador una manera sistemática de almacenar la decisión de diseño de que ésta es una clase abstracta. Los diferentes tipos de elementos del modelo de UML tienen disponibles distintas propiedades para almacenar las decisiones apropiadas. Otra, particularmente útil, es la propiedad de operaciones esPregunta. Si un desarrollador especifica que una operación tiene su propiedad esPregunta a verdadero, almacena la decisión de diseño en la que la llamada a la operación no debería afectar al estado del sistema de ninguna manera. Tales operaciones pueden utilizarse en el modelo siempre que sea conveniente y todas las veces que se quiera, sin miedo a provocar efectos laterales no deseados. Por ejemplo, pueden utilizarse en condiciones o restricciones sin causar confusión. Cualquier propiedad puede escribirse en un diagrama añadiendo una etiqueta {nombrePropiedad = valor} cerca del nombre del elemento sobre el que se aplica la propiedad. Debido a que muchas propiedades tienen valor lógico con nombres esAlgo, UML proporciona una forma abreviada para ellas. Se podría escribir {esAbstracto = verdadero}, pero en su lugar se puede escribir solamente {abstract}. Destacar la similitud con la manera en que se escriben las restricciones: en ambos casos, la expresión se escribe entre llaves cerca del nombre del elemento. Se podría pensar en una propiedad como un tipo de restricción. Las propiedades predefinidas de cada tipo de elemento del modelo están descritas en el documento de Semántica de UML [48]. Uno mismo también puede definir sus propios valores etiquetados. Esto es, para cualquier elemento de un modelo se puede definir un nombre (una etiqueta) que debería contener un valor. Por ejemplo, si se quisiera almacenar quién escribió el código de una clase y quién lo revisó, se podrían definir dos etiquetas autor y revisor para aplicarlas a cada clase. El diagrama de clases puede almacenar la información; por ejemplo, insertando {autor = “Perdita Stevens”, revisor = “Stuart Anderson”} cerca del nombre de la clase. Por supuesto, esto sólo será realmente útil si se tiene tanto un conjunto de etiquetas acordadas por el equipo de desarrollo, como algún soporte de herramientas para gestionar y mostrar las etiquetas.
100
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
¿Cuál es la diferencia entre definir un nuevo valor etiquetado, y definir un nuevo estereotipo? Estereotipar es una opción más potente, con peso, que es especialmente útil cuando se quieren hacer varias especializaciones de un tipo de elemento del modelo. Definir un valor etiquetado es un mecanismo con menos peso, para cuando simplemente se quiere asociar algo más de información a un elemento.
6.3
Clases parametrizadas ¡Una clase parametrizada realmente no es un tipo de clase! Puede considerarse como un tipo de función: en vez de tomar algunos valores y retornar un valor, una clase parametrizada toma algunas clases y devuelve una clase. A veces se le llama plantilla: la idea es que tiene algunas ranuras en las que se ponen clases, para obtener una nueva clase. El ejemplo clásico es Lista, el cual, dada una clase C para sustituir el parámetro formal T, describe la clase de listas de objetos C. Por ejemplo, un objeto de clase Lista<Estudiante> representa una lista de estudiantes, de igual modo un objeto de clase Lista<Juego> representa una lista de juegos. Por supuesto, se podrían implementar nuevas clases ListaEstudiante y ListaJuego sin necesitar ninguna facilidad especial. Sin embargo, las listas de estudiantes y las listas de juegos tendrán mucho en común: por ejemplo, ambas proporcionarán operaciones para añadir y eliminar elementos de la lista, y el código para ambas es prácticamente el mismo, sin pensar si los elementos de las listas son Estudiantes o Juegos. Una clase parametrizada permite aprovecharse de este hecho reutilizando la misma clase parametrizada en ambos casos. Definiendo una clase parametrizada una sola vez, y utilizándola dos, se puede ahorrar esfuerzo tanto en desarrollo como en mantenimiento. En UML se muestra una clase parametrizada utilizando una variante del símbolo de clase, el cual tiene un pequeño rectángulo punteado en la esquina superior derecha, donde se listan los parámetros formales de la clase parametrizada. Los tipos de los miembros de la clase pueden (y casi siempre lo hacen) mencionar el parámetro formal. Hay dos formas de indicar que una clase es el resultado de aplicar un argumento en una clase parametrizada. Se muestran ambas en la Figura 6.16.
T Lista Añadir(t:T,pos:Entero) Obtener(i:entero):T Lista<Juego> <> 〈Estudiante〉
ListaEstudiante
Figura 6.16 Una clase parametrizada y su utilización.
MÁS SOBRE LOS MODELOS DE CLASES
101
La especificación de UML sugiere una notación más prolija como Lista en vez de Lista<Juego>. Donde sólo hay un parámetro, como en nuestro ejemplo, esto es una exageración. Sin embargo, puede haber cualquier número de parámetros; si se necesita utilizar más de uno puede ser que encuentre de mérito utilizar la notación más explícita. Destacar que en la Figura 6.16 se muestra una dependencia, pero no otras. Ambas instanciaciones dependen de la clase parametrizada, así como de sus respectivos parámetros. Se omite, por ejemplo, la dependencia de Lista<Juego> sobre Lista porque esta dependencia queda clara con el nombre de la clase.
P: ¿Por qué no se podría construir la clase ListaEstudiante a partir de la clase Lista utilizando herencia?
No todos los lenguajes soportan esta manera de construir clases directamente (a menudo conocido como genericidad); C lo soporta, pero Java, por ejemplo, no. Aunque el lenguaje que se vaya a utilizar en un proyecto no soporte clases parametrizadas, el diseño puede ser más legible utilizando la notación. Pregunta de Discusión 54 Otra manera de obtener beneficios de la reutilización en el ejemplo de la Lista podría ser definir una clase Lista única, no parametrizada en función de una clase muy general como es Object, de la cual toda clase es subclase, y después confiar en la posibilidad de sustitución para permitir introducir cualquier objeto de cualquier clase en una Lista. Esto se hace a menudo en lenguajes que no soportan directamente las clases. ¿Qué ventajas e inconvenientes tiene esta aproximación, comparada con una clase parametrizada Lista? ¿De qué otra manera se podría alcanzar alguno de los beneficios de la reutilización?
6.4
Dependencia Se han visto algunos ejemplos de una dependencia entre dos clases (y, en un caso, entre una clase y una clase parametrizada —¡recuerde que una clase parametrizada no es realmente una clase!)—. Recordemos del Capítulo 1 que A depende de B si un cambio en B puede forzar un cambio en A. Se puede mostrar una dependencia entre dos elementos del modelo UML cualesquiera (y prácticamente cualquier cosa en un diagrama de UML es un elemento del modelo). En cada caso, la dependencia de A con B se indica con una flecha de dependencia punteada desde A hacia B, como en la Figura 6.16. Hay que destacar la diferencia entre una dependencia entre dos clases y una asociación entre las clases. Una asociación entre dos clases representa el hecho en que los objetos de esas clases están asociadas. Una dependencia existe entre las clases en sí, no entre los objetos de esas clases. En realidad, cuando hay una dependencia entre clases normalmente es posible, y preferible, ser más específicos sobre la naturaleza de la dependencia. Por ejemplo, una clase siempre depende de una clase de la que hereda, por lo que no es necesario mostrar explícitamente una dependencia en tal caso.
102
6.5
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
Componentes y paquetes Las dependencias se utilizan de forma más habitual entre paquetes. Un paquete es una colección de elementos del modelo, y define un espacio de nombres para los elementos. Por ejemplo, un paquete podría ser una colección de clases relacionadas y las relaciones entre ellas, o una colección de objetos y las relaciones entre ellos. A menudo es conveniente poder empaquetar cosas como ésta, bien porque forman un componente o simplemente para dividir el trabajo en partes. Un paquete puede aparecer en un diagrama de clases (o, claro está, en cualquier otro tipo de diagrama), como un rectángulo con una “etiqueta” en la esquina superior izquierda. Si un diagrama muestra una dependencia o asociación enlazando un paquete con algo, esto significa que hay algún elemento en el paquete que tiene la dependencia o asociación. No es necesario especificar cuál. Se hablará de componentes, paquetes y subsistemas en más detalle en los Capítulos 13 y 14.
6.6
Visibilidad, protección Las clases, a menudo, tienen atributos u operaciones que no están disponibles para todos los clientes de la clase. UML permite el uso de los símbolos , # y para distinguir entre los miembros de una clase públicos, protegidos y privados 3. Los miembros públicos pueden ser accedidos por cualquier cliente de la clase. El significado exacto de protegido y privado depende del lenguaje. Los miembros privados normalmente sólo pueden ser accedidos por miembros de la misma clase. Los miembros protegidos, habitualmente, tienen un acceso más amplio que los miembros privados, pero no tanto como los miembros públicos.
RESUMEN
Este capítulo ha considerado algunas funciones avanzadas de los diagramas de clases de UML, y los mecanismos principales de UML de extensibilidad: estereotipos, restricciones y valores etiquetados. Se han tratado algunas formas de dar información extraordinaria sobre las asociaciones entre clases, considerando la agregación y composición, roles, navegabilidad, asociaciones calificadas, asociaciones derivadas y clases asociación. También se han cubierto las restricciones, que son una característica fundamental de UML, que pueden utilizarse para llevar a cabo una gran variedad de propósitos; aquí se ha tratado su utilización para especificar invariantes de clase y para almacenar las relaciones entre varias asociaciones. Después, se han considerado las interfaces que, otra vez, pueden aplicarse de forma más general. Se han mencionado las clases abstractas, y las conocidas clases parametrizadas, que no son clases, sino funciones que toman una o más clases como argumentos y devuelven clases como resultado. Finalmente, se ha considerado la dependencia y la visibilidad
3
También está permitido para la visibilidad del paquete, lo cual no se trata aquí.
Capítulo
7 Fundamento de los modelos de casos de uso
Los casos de uso documentan el comportamiento del sistema desde el punto de vista del usuario. En este caso, por “usuario” se entiende cualquier cosa que se desarrolla, ajena al sistema, que interactúa con el mismo. Un usuario podría ser una persona, otro sistema de información, un dispositivo hardware, etc. El modelado de los casos de uso ayuda con tres de los aspectos más difíciles del desarrollo: • La captura de requisitos. • La planificación de las iteraciones del desarrollo. • La validación de los sistemas. Los casos de uso fueron presentados, por primera vez por Ivar Jacobson a principios de los noventa, como un desarrollo a partir de la idea de escenarios. Los escenarios todavía existen en UML, y se tratarán más tarde en este capítulo. Un diagrama de casos de uso es relativamente fácil de comprender de forma intuitiva, incluso sin conocer la notación. Esto es una ventaja importante, ya que el modelo de casos de uso se puede tratar de forma coherente con un cliente que no necesita estar familiarizado con UML. Para entender esto, véase la Figura 7.1, que muestra el diagrama de casos de uso del estudio del caso introductorio del Capítulo 3. Antes, examinemos en detalle los elementos de un modelo de casos de uso. El diagrama muestra, no un único caso de uso, sino todos los casos de uso de un sistema dado. Un caso de uso individual, que aparece como un óvalo con un nombre, representa un tipo de tarea que tiene que soportar el sistema en el desarrollo. (El estándar de UML llama a esto “unidad coherente de funcionalidad”: en este libro se prefiere el término “tarea”). Por supuesto, el diagrama de casos de uso muestra sólo una parte de la información que se necesita. Cada caso de uso también se describe en detalle, normalmente en texto. El diagrama de casos de uso puede verse como un resumen conciso de la información contenida en todas las descripciones de los casos de uso. Un actor, que normalmente aparece con el símbolo de un muñeco, representa un tipo de usuario del sistema (donde, recuerde, por usuario se entiende cualquier cosa externa al sistema
104
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
Reservar libro
Hojear
PrestatarioLibro
Hojeador Tomar prestada copia del libro
Devolver copia del libro
Ampliar préstamo
Actualizar catálogo
Tomar prestada revista
PrestatarioRevista
Bibliotecario
Devolver revista
Figura 7.1 Diagrama de casos de uso para la biblioteca (diagrama 1).
que interactúa con él —no piense que el actor tiene que ser humano, confundido por la apariencia del icono). Hay una línea que conecta un actor con un caso de uso si el actor (mejor dicho, alguien o algo que realice el rol representado por el actor) puede interactuar con el sistema para realizar parte de la tarea. Un diagrama de casos de uso es bastante parecido a un diagrama de clases, en el sentido de que los iconos representan conjuntos de cosas y posibles interacciones, en vez de cosas individuales e interacciones definidas. En el Capítulo 5 ya se vio que la asociación simple es una copia de entre las clases Copia y Libro, mostrada en la Figura 7.2, que representa una
Es una copia de Libro
Copia
Figura 7.2 Asociación simple entre clases.
FUNDAMENTO DE LOS MODELOS DE CASOS DE USO
105
Reservar libro
PrestatarioLibro
Figura 7.3 Comunicación sencilla entre un actor y un caso de uso.
relación entre el conjunto de objetos de la clase Copia y el conjunto de objetos de la clase Libro. Esto es, un objeto individual miCopia de la clase Copia y un objeto individual eseLibro de la clase Libro podrían o no estar relacionados. De la misma manera, la relación de comunicación muy sencilla mostrada en la Figura 7.3 indica que hay una relación entre el conjunto de PrestatariosLibro, y el conjunto de escenarios en el cual un PrestatarioLibro reserva un libro de la biblioteca. Un PrestatarioLibro en particular podría o no estar implicado en un escenario concreto de reserva de un libro. Un escenario es una instancia de un caso de uso, al igual que un objeto es una instancia de una clase. Se volverá a este punto más tarde. Por ahora, destacar dos cosas: • Un actor, en un diagrama de casos de uso, representa un rol que alguien tiene que cumplir, en vez de representar a un individuo en particular. Por ejemplo, un bibliotecario puede ser también un prestatario de un libro: alguien que a veces realiza el papel de un bibliotecario, pero otras veces puede realizar el rol de prestatario de un libro. • Una relación de comunicación entre un actor y un caso de uso no significa que necesariamente alguien de ese rol tenga que estar implicado en la ejecución de una tarea; simplemente significa que puede ser, dependiendo de las circunstancias. Ahora, consideremos los actores y los casos de uso con mayor detalle.
7.1
Actores en detalle Beneficiarios Cada caso de uso tiene que representar una tarea, o unidad coherente de funcionalidad, que se le requiere al sistema que soporte. Normalmente, esto significa que el caso de uso tiene valor para al menos uno de los actores. Al actor para el que un caso de uso tiene valor se le llama beneficiario de ese caso de uso. Es importante identificar los beneficiarios de cada caso de uso, ya que si un caso de uso tiene valor para un determinado actor, ese actor permanecerá conectado al caso de uso a lo largo del desarrollo. (Quizá el nombre del actor puede cambiar, y quizá, incluso se pueda terminar con varios actores porque se reclasifican los roles, pero las personas o los sistemas externos representados por el actor siempre estarán representados de alguna manera). Sin embargo, si un actor no es beneficiario de un caso de uso, entonces la conexión entre el actor y el caso de uso es menos cierta. Puede haber otras maneras de proporcionar el mismo valor, es decir, satisfacer los mismos requisitos. Por esta razón, los desarrolladores tienen que conocer quién necesita un caso de uso y quién está implicado en él sin obtener ningún beneficio del mismo.
106
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
P: Piense en el caso de uso Tomar prestada copia de libro en el sistema de la biblioteca. No se ha mostrado al bibliotecario como actor conectado con ese caso de uso. ¿Cuáles serían las ventajas y desventajas de hacerlo? Pregunta de Discusión 55 A veces se discute que sólo los actores que son beneficiarios deberían aparecer en un diagrama de casos de uso, porque la decisión de los actores que están implicados en la realización de un caso de uso es de diseño, no es parte del análisis de requisitos. ¿Qué piensa y por qué?
Hay circunstancias en las que ninguna de las personas que interactúan con un sistema para ejecutar una tarea, realmente, se benefician de él: la interacción del beneficiario con el sistema en esa tarea es indirecta. Véase la sección 7.4.1.
Identificar actores Los usuarios potenciales de un sistema tienden a ser relativamente fáciles de identificar 1. Para desarrollar un modelo de casos de uso se necesitan identificar los diferentes roles que estos humanos pueden desempeñar, recordando que una persona puede representar distintos roles en distintos momentos. Identificar los roles es algo parecido a considerar usuarios desde el punto de vista del sistema. Si Mary Smith y Joe Bloggs pudiesen ambos estar implicados en alguna parte del comportamiento del sistema (por ejemplo, reservar un libro), y “no provocan ninguna diferencia importante al comportamiento del sistema” si está interactuando con Mary Smith o Joe Bloggs, esto puede significar que tanto Mary Smith como Joe Bloggs son capaces de desempeñar el rol representado por un actor en particular conectado con el caso de uso que representa esa tarea. Pregunta de Discusión 56 ¿Qué diferencias en el comportamiento de un sistema piensa que podrían ser importantes en este contexto? ¿Podría precisarlo?
Hay algunas sutilezas a las que se volverá más tarde en este capítulo y en el siguiente, pero normalmente cualquier persona que interactúa con el sistema estará representado al menos por un actor en el modelo de casos de uso. Por supuesto, un usuario que desempeña varios roles diferentes es representado por varios actores, uno por cada rol.
Actores no humanos La situación con los actores no humanos tiende a ser menos clara, principalmente debido a que es menos claro qué se debería considerar como un sistema externo o dispositivo. Por ejemplo, un teclado no se considera como un dispositivo que interactúa con el sistema, porque hay una persona manejando el teclado. Mostrar el teclado no sería útil; en su lugar, de forma natural uno se abstrae del hecho de que un operador (un humano) golpea un teclado cuya presión sobre las 1
Si no lo son, es sospechoso: ¡posiblemente esté siendo embarcado en la tarea sorprendentemente común de construir un sistema que alguien quiere tener construido, pero que nadie realmente quiere utilizar!
FUNDAMENTO DE LOS MODELOS DE CASOS DE USO
107
teclas se envía al sistema, y muestra un actor que representa al humano interactuando directamente con el sistema. ¿Qué pasaría si se considerase un sistema que toma la entrada desde un lector de código de barras? ¿Y desde un reloj? ¿Y desde Internet? ¿Y desde un sistema informático distinto dentro de la misma compañía? ¿Qué pasaría si el sistema enviase la salida a un sistema externo dispositivo como éstos? ¿Dónde están los límites entre sistemas? Por ejemplo, supongamos que el sistema de la biblioteca permite a los usuarios solicitar préstamos entre bibliotecas, y que cuando se lleva a cabo tal petición, el sistema se pone en contacto con la otra biblioteca vía Internet. ¿Qué actores deberían aparecer en nuestro diagrama de casos de uso? ¿Internet? ¿El otro sistema de biblioteca? ¿Ninguno? La solución es pragmática: se hace lo que parece que va a ser más útil, y distintas personas tienen visiones diferentes. Incluso si está claro lo que es un sistema externo o dispositivo, hay una pregunta sobre qué cosas deberían aparecer en un diagrama de casos de uso. Fowler y Scott [19] tratan las posibles vistas, que se pueden resumir diciendo que se pueden mostrar las interacciones con sistemas externos: 1. Siempre. 2. Cuando es el otro sistema o dispositivo el que inicia el contacto. 3. Cuando es el otro sistema o dispositivo el que toma valor del contacto. De nuevo, hay personas que piensan que los actores deberían representar siempre humanos: por ejemplo, que se podría considerar que el bibliotecario de otra biblioteca apareciese como un actor, pero no el otro sistema de la biblioteca. El peligro con esta forma de ver las cosas, es que significa que puede que haya que conocer aspectos irrelevantes del funcionamiento de un sistema externo, para saber qué roles de las personas están implicados. ANOTACIÓN TÉCNICA DE UML En realidad, en UML se tiende a utilizar el rol para denotar lo que un objeto o actor hace en una colaboración específica: de este modo, un actor, técnicamente, desempeña un rol distinto en cada caso de uso y es un conjunto coherente de roles. Puede que se prefiera pensar en un actor como en una persona “que lleva un sombrero particular”.
7.2
Casos de uso en detalle Se dijo que un escenario es una instancia de un caso de uso, al igual que un objeto es una instancia de una clase. Como con los objetos y las clases, es más fácil describir qué es un escenario a describir qué es un caso de uso, ya que un caso de uso describe un conjunto de escenarios relacionados. Un escenario es una posible interacción entre el sistema y algunas personas o sistemas/dispositivos (en sus diversos roles). La interacción puede describirse como una secuencia de mensajes. Por ejemplo, aquí hay dos escenarios: • La prestataria de libros Mary Smith toma prestada la tercera copia de Guerra y paz de la biblioteca, cuando no tiene ningún otro libro en préstamo. El sistema se actualiza de acuerdo con esto.
108
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
• El prestatario de libros Joe Smiths intenta tomar prestada la primera copia de Anna Karenina de la biblioteca, pero es rechazado porque tiene ya seis libros en préstamo, que es el máximo permitido. Pregunta de Discusión 57 ¿Cuáles son los mensajes en cada caso? ¿”Mensaje” significa lo mismo en este contexto que en el Capítulo 2?
Ambos escenarios son posibles instancias del caso de uso Tomar prestada copia de libro. Hay que destacar que no sólo los interactores, sino también el resultado, son diferentes en los dos casos. Esto es común. Al igual que no todos los objetos de una misma clase envían los mismos mensajes durante su ciclo de vida, los escenarios en un mismo caso de uso pueden suponer un comportamiento diferente. Los escenarios en un caso de uso deberían tener en común que todos ellos intentan ejecutar, fundamentalmente, la misma tarea, incluso aunque el caso de uso incluya flujos alternativos o inusuales. De este modo, un caso de uso engloba un conjunto de requisitos del sistema, posiblemente complejo, que surge durante la captura de requisitos iniciales y se refinará durante el desarrollo del sistema. Se necesita alguna manera de almacenar la información detallada que se tiene sobre lo que implica un caso de uso: ¿cuáles son los posibles escenarios, y qué determina cuál de ellos hay que aplicar en un conjunto de circunstancias dadas? Normalmente, esto se hace asociando una descripción textual con el caso de uso. Una herramienta puede permitir al usuario hacer click sobre el icono ovalado que representa un caso de uso para ver el texto que proporciona la descripción detallada de lo que es ese caso de uso. Se puede utilizar también un diagrama de actividad de UML, o una descripción en algún lenguaje formal, y se puede asociar de forma similar con el caso de uso que describe. Más tarde, se necesitará poder mostrar cómo la colección de clases y componentes diseñada, activa el sistema para realizar el caso de uso. Un caso de uso puede estar asociado con diagramas de interacción, que muestran cómo se realiza en un diseño de sistema en particular en dicho caso de uso, o algún subconjunto de escenarios de los que incluye.
7.3
Límite del sistema Opcionalmente, puede haber una caja en un diagrama de casos de uso, alrededor de los casos de uso, etiquetada con el nombre del sistema. La caja representa el límite del sistema. Un ejemplo es el que aparece en la Figura 7.4. Esto puede ser útil cuando se modela un sistema complejo, el cual se divide en diferentes subsistemas: se podría tener un diagrama de casos de uso por cada subsistema, en cuyo caso el límite del sistema puede ayudar a dejar claro qué subsistema se está modelando. Sin embargo, cuando se dibuja un diagrama de casos de uso para representar un sistema sencillo, es común omitir la caja, y es lo que se hará en el resto de este libro.
FUNDAMENTO DE LOS MODELOS DE CASOS DE USO
109
Sistema librería Reservar libro
Hojear
PrestatarioLibro
Hojeador Tomar prestada copia del libro
Devolver copia del libro
Ampliar préstamo
Tomar prestada revista
PrestatarioRevista
Actualizar catálogo
Bibliotecario
Devolver revista
Figura 7.4 Diagrama de casos de uso para la biblioteca (diagrama 2).
7.4
Utilización de los casos de uso 7.4.1
Casos de uso para la captura de requisitos
Los casos de uso pueden ayudar con la captura de requisitos, proporcionando una forma estructurada de abordarlos: 1. Identificar los actores. 2. Para cada actor, averiguar: • lo que necesitan del sistema: es decir, qué casos de uso hay que tienen valor para ellos. • cualquier otra interacción que esperan tener con el sistema, esto es, en qué casos de uso podrían formar parte para el beneficio de otro. Para propósitos de priorización de trabajo y planificación de iteraciones del desarrollo, también hay que saber hasta qué punto necesita una persona que se realice un caso de uso dado.
110
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
Puede ser útil hacer una lista de qué personas o sistemas reales pueden ejecutar el rol de cada actor, pero normalmente esto no se almacena en el diagrama de UML. En la discusión del sistema con los usuarios potenciales es necesario averiguar los actores que podrían estar en el diagrama de casos de uso, y si hay alguna función en la que parece que deberían participar y que no están incluidos actualmente. Tal y como se destacó cuando se trataron los beneficiarios del sistema como actores, puede haber aspectos del comportamiento del sistema que no se presenten fácilmente como casos de uso para actores. Se conecta un actor con un caso de uso sólo cuando el actor participa en él, no, de manera más general, cuando un actor está involucrado de alguna forma con la existencia de un caso de uso. De modo que pueden existir casos en que una tarea sea esencial, pero no especialmente valiosa para cualquiera de los actores que participan en ella. El clásico ejemplo (mencionado en [19], por ejemplo) es cuando una compañía de servicios envía facturas trimestrales a cada uno de sus clientes. La compañía en conjunto se beneficia de esto; pero el único actor obvio en el caso de uso es el cliente, y posiblemente la compañía de correo, la cual no se beneficia del envío de las facturas. Debería presentar tales tareas cuando las descubra, aun cuando no se beneficie directamente alguno de los actores implicados. (Tenga cuidado, sin embargo, en asegurar que el caso de uso representa un requisito real).
7.4.2
Casos de uso a través del desarrollo
Planificación Antes de realizar una estimación coherente y planificar el proceso para todo el proyecto, se necesita tener una lista con todos los casos de uso del sistema junto con: • una buena idea de lo que significa cada uno. • un entendimiento de quién quiere cada uno y cuánto. • el conocimiento de qué casos de uso tienen más riesgo. • un plan de cuánto tiempo llevaría implementar cada caso de uso. Un punto que parece elemental, es que ¡no se debería planificar la entrega de un sistema en menos tiempo (esfuerzo total, de todos los desarrolladores implicados) que la suma de los tiempos de entrega que se ha planificado para los casos de uso! 2 Llevar a cabo esta aritmética puede ser un antídoto útil para el optimismo de más. Cuando (y es cuando, en vez de si) se encuentra con que el sistema tiene que ser entregado completamente antes de lo previsto, tiene que encargarse de la negociación con el cliente sobre qué casos de uso no deberían proporcionarse en la primera versión. (Kent Beck lo llama juego de planificación, que engloba el hecho de que hay normas, por ejemplo, sobre cómo transcurre el tiempo, que no pueden romperse, ni siquiera para obligar al cliente). Este es uno de los puntos donde se diferencia el nivel de granularidad en que se describen los casos de uso. También es importante la identificación de la funcionalidad común a varios casos de uso y que puede, por lo tanto, reutilizarse: esto se tratará en el siguiente capítulo. Una vez que se sabe qué casos de uso se ha contratado proporcionar, hay que decidir en qué orden se van a implementar, y qué casos de uso pertenecen a qué iteración del sistema. 2
Teniendo en cuenta cualquier utilización que pueda predecir.
FUNDAMENTO DE LOS MODELOS DE CASOS DE USO
111
(Recuerde que una iteración puede ser de alto nivel, interna al proyecto, o puede ser de alto nivel o externa, es decir, puede producir la entrega de un sistema al cliente. El cliente está implicado en la decisión sobre qué casos de uso deben proporcionarse en las iteraciones externas, pero no en las decisiones sobre las iteraciones internas).
Aspectos políticos ¿Recuerda el 25% de los sistemas que nunca se entregan? No es porque los desarrolladores decidan tomarse vacaciones: es porque el proyecto se cancela. Es decir, alguien en alguna parte decide que no merece la pena continuar con él. ¿Cómo se puede impedir que pase esto en nuestro proyecto? Si se han capturado los requisitos en función de los actores y los casos de uso, es probable que se tenga una buena idea de qué casos de uso —esto es, qué aspectos del comportamiento del sistema— son más importantes para qué personas. Por lo que, al igual que otras cosas, se quiere poder demostrar que el sistema primero hace algo de valor para las personas con mayor influencia. Tan pronto como sea posible, se quiere asegurar que todo aquél que tiene poder para hundir el sistema (¡y recuerde que puede incluir gente que no tiene poder formal en la organización!) no tiene una buena razón para hacerlo. Esto significa que han de ver que si el sistema se completa y entrega, obtendrán algo que quieren, y que perderán si cancelan el proyecto. Comparando los casos de uso que son importantes para una persona dada, por supuesto, al igual que otras cosas, habría que implementar primero los de mayor prioridad. Por supuesto, puede decidirse no empezar un proyecto si el análisis de los casos de uso no demuestra que proporcionará beneficio suficiente. ¡Esta es otra manera de reducir la probabilidad de que un proyecto determinado sea cancelado!
Aspectos técnicos Otro criterio, que puede estar en conflicto con los anteriores, es que hay que entregar primero los casos de uso con mayor riesgo, para abordar los mayores riesgos cuando todavía se tiene la contingencia de abordarlos, y así uno no se queda atado en un diseño que no permita tratar los casos de uso más duros (los de mayor riesgo). Pregunta de Discusión 58 En otras circunstancias, se pueden abordar primero las partes más fáciles de un problema. ¿Cuándo es ésta la aproximación correcta?
Hay que destacar que la manera en que los casos de uso son descritos, varía a lo largo del proceso de desarrollo. Para empezar, es importante identificar qué se debería alcanzar en cada caso de uso, no cómo debería alcanzarse. Más tarde se elegirá una implementación. Esto puede provocar el cambio de los actores; probablemente no los que quiere el caso de uso, sino los que intervienen en capacidad “de ayuda”.
Validación del sistema Cada caso de uso describe un requisito del sistema, por lo que un diseño correcto permite que se ejecute cada caso de uso; es decir, realiza cada caso de uso. Una técnica obvia, y muy útil, para
112
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
validar el diseño de un sistema es tomar de uno en uno los casos de uso y comprobar que el sistema permite ejecutar dicho caso de uso. A veces esto se llama recorrido (revisión) del caso de uso. La misma técnica puede utilizarse para derivar pruebas del sistema: puede haber pruebas para cada caso de uso, y donde un caso de uso incluye familias de escenarios significativamente diferentes, debería incluirse un ejemplo de cada familia. Por ejemplo, el sistema de biblioteca tiene que probarse para ver tanto si permite a un usuario tomar prestado un libro, como si no permite a ningún usuario tomar prestados demasiados libros de una vez. La necesidad de estas pruebas se deriva directamente del modelo de casos de uso.
7.5
Posibles problemas con los casos de uso Tal y como se ha visto, los casos de uso ayudan con algunos de los aspectos más difíciles del desarrollo de un sistema, a saber la captura de requisitos, planificación, gestión de iteraciones y planificación de pruebas. Sin embargo, algunos expertos, de entre los que destaca Meyer [35], están en contra de esto. Nosotros también tenemos nuestras reservas, pero más sobre las características del modelo de casos de uso descritas en el siguiente capítulo, que de la forma sencilla aquí tratada. Sin embargo, el modelo de casos de uso debería utilizarse con cuidado, ya que: 1.
Hay peligro de construir un sistema que no sea orientado a objetos. El centrarse en los casos de uso puede ayudar a los desarrolladores a perder visión de la arquitectura del sistema y de la estructura de objetos estática, en las prisas para entregar de alguna manera los casos de uso que son necesarios en la iteración actual. Más tarde, si la funcionalidad de uno de los casos de uso ya ha sido desarrollada, puede ser difícil justificar el tiempo para modificar el diseño manteniendo su integridad con respecto a los casos de uso siguientes. Se puede terminar de nuevo donde se empieza, desarrollando un sistema de arriba a abajo, orientado a la función, imposible de mantener, inflexible. Este peligro puede reducirse con una gestión cuidadosa al principio de cada iteración. Si la iteración anterior deja el sistema en un estado que no es satisfactorio, debería ser refactorizado antes de añadir ninguna funcionalidad nueva, y el plan de la iteración debe permitirlo.
2.
Hay peligro de tener un diseño de requisitos erróneo. Ya se ha visto un ejemplo de cómo sucede esto: la presunción de que un actor está implicado en un caso de uso del cual no obtiene ningún valor es, normalmente, una decisión de diseño, no una restricción. De forma más general, los requisitos por medio de los casos de uso pueden animar a los desarrolladores a pensar de forma operacional: los usuarios tienden a describir los casos de uso como una secuencia muy concreta de interacciones con el sistema que es una manera, no la única, de alcanzar su meta real. Por ejemplo, los usuarios piensan de forma natural en las cosas que tienen que hacerse en un determinado orden, quizá el orden en que se hacen en el presente, aunque otro orden podría ser igualmente apropiado. Es importante que los desarrolladores distingan entre requisitos y diseños candidatos.
3. Hay peligro de perder requisitos si se pone demasiada confianza en el proceso sugerido para encontrar los actores y después encontrar los casos de uso que necesita cada actor. Como se mencionó, no todos los requisitos surgen naturalmente de esta manera.
FUNDAMENTO DE LOS MODELOS DE CASOS DE USO
113
Este peligro puede reducirse haciendo el análisis de los casos de uso y el modelo de clases conceptual en paralelo. Pregunta de Discusión 59 Una táctica podría ser desarrollar un modelo de casos de uso en el que sólo los actores que necesitan un caso de uso dado se comunican con él; los actores “auxiliares” no deberían aparecer. ¿Cuáles serían las ventajas y desventajas de este acercamiento?
PANEL 7.1
¿Desarrollo dirigido por casos de uso?
“Dirigido por casos de uso” es una frase complicada, asociada a menudo con UML, presentada en [28] y utilizada por gran parte de la comunidad de UML. ¿Qué significa, y somos partidarios de ella? En esencia, la idea es que los casos de uso son el aspecto más importante del proceso de diseño. No son desarrollados para hacer la captura de requisitos y después abandonarlos una vez que empieza el diseño: deberían utilizarse a lo largo de todo el proyecto, para seguir los cambios y definir las iteraciones, por ejemplo. Esta aproximación ayuda a mantener el centro donde debería estar, en los requisitos del usuario. Aquí hay un solapamiento con el diseño centrado en el usuario que se tratará brevemente en el Capítulo 19. De manera más controvertida, [28] aboga por el examen de los casos de uso como método principal para encontrar objetos y clases, por ejemplo, así como método principal para encontrar componentes y formas de utilizarlos. Sin embargo, se han descrito algunos de los peligros de la sobre-confianza en los casos de uso. En particular, no creemos que el examen de los casos de uso sea, por sí misma, una buena forma de encontrar objetos y clases. En su lugar, pensamos que el desarrollo de un modelo de clases conceptual debería darse en paralelo con el desarrollo del modelo de casos de uso, y que cada uno se alimentara del otro. Algunas clases se descubrirán examinando los casos de uso. Algunos casos de uso se descubrirán examinando las clases. No consideramos útil clasificarnos como abogados del desarrollo “dirigido por casos de uso” o “dirigido por datos” o “dirigido por responsabilidad”. Un buen desarrollo OO incluirá siempre aspectos de cada una de las tres aproximaciones.
RESUMEN
Este capítulo ha introducido los modelos de caso de uso simples y ha mostrado cómo se utilizan para especificar el comportamiento de un sistema de una forma independiente del diseño. Se ha tratado cómo identificar actores, casos de uso y relaciones de comunicación entre ellos, y cómo utilizar el modelo de casos de uso en el contexto de un proyecto de desarrollo. En el siguiente capítulo se tratarán más características y usos de los diagramas de casos de uso. En los Capítulos 9 y 10 se mostrará cómo los diagramas de interacción se utilizan para demostrar cómo el diseño de un sistema realiza un caso de uso.
114
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
PREGUNTAS DE DISCUSIÓN
1. Piense en los actores del ejemplo de la biblioteca, y considere los conjuntos de personas que pueden ser representados por cada actor. Piense las intersecciones entre los conjuntos: por ejemplo, el conjunto de personas (si hay alguno) que a veces desempeña el rol de prestatario de libros y a veces el rol de bibliotecario. ¿Hay algún conjunto contenido en otro? ¿Cree que sería útil que el diagrama presentase las relaciones entre estos conjuntos de personas así como las relaciones entre los actores? ¿Por qué, o por qué no? ¿Si es así, cómo? 2. ¿Hay algunas relaciones interesantes entre algún caso de uso? Si es así, ¿cuáles son? Otra vez, ¿sería útil representarlos en el diagrama, y si es así, por qué y cómo? 3. Se ha dicho que un caso de uso, normalmente, debería tener valor para al menos uno de los actores. De algunos ejemplos, de sistemas que conozca, de casos de uso que tienen valor para más de uno de los actores implicados. El Capítulo 8 muestra cómo representar algunas relaciones entre actores y entre casos de uso en UML: puede ser interesante comparar lo que permite UML con lo que piense que podría querer.
Capítulo
8 Más sobre modelos de casos de uso
En este capítulo se consideran más aspectos de los modelos de casos de uso y su uso en el desarrollo. Se tratará: • Cómo y por qué se pueden mostrar las relaciones entre casos de uso. • Cómo y por qué se pueden mostrar las relaciones entre actores. El lector debe ser advertido de que cada una de estas funciones hace a un modelo de casos de uso más complejo (aunque posiblemente más pequeño). Al principio del capítulo anterior se declaró que una fuerza importante de los diagramas de caso de uso es su simplicidad. Aquí hay un conflicto obvio. Además, hay un gran desacuerdo en cómo deberían utilizarse exactamente las funciones que aquí se describen. Diferentes partes de la comunidad de UML tienen diferentes ideas y la especificación de UML2 en sí misma deja abiertas, deliberadamente, muchas decisiones. Se recomienda una aproximación a KISS: si duda, no utilice estas características. Finalmente, se tratarán las circunstancias en las que un actor en el modelo de casos de uso debería ser modelado en el sistema mediante una clase, ya que, a menudo, esto causa confusión.
8.1
Relaciones entre casos de uso Hay dos tipos de situaciones principales en las que se puede querer documentar una relación entre dos casos de uso. En el diagrama de casos de uso esto aparece como una flecha punteada de cabeza abierta entre dos elipses de casos de uso (es la misma flecha que se utiliza para mostrar otros tipos de dependencia). Los dos casos se distinguen dándoles diferentes estereotipos: al primero se le da el estereotipo <>, al segundo se le da el estereotipo <<extend>>.
116
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
8.1.1
Casos de uso para la reutilización: <>
El caso más vital es cuando se puede sacar factor común del comportamiento de dos o más casos de uso originales, o (mejor todavía) cuando se descubre que se puede implementar parte de uno de los casos de uso utilizando un componente. Por ejemplo, destacar que las descripciones de los casos de uso Tomar prestada copia de libro y Ampliar préstamo mencionan la necesidad de comprobar si existe una reserva sobre el libro. Si la hay, entonces ni el préstamo puede ser ampliado ni el libro puede ser prestado. Se podría decidir mostrar esta característica común de los dos casos de uso en el diagrama de casos de uso, como aparece en la Figura 8.1. Hay que destacar que la flecha va desde el caso de uso “usuario” hacia el caso de uso “usado”, y se etiqueta (con el estereotipo) <>, para representar que el caso de uso origen incluye el caso de uso destino. De forma más precisa, los escenarios que son instancias del caso de uso origen, contienen subescenarios que son instancias del caso de uso destino. Si el caso de uso destino cambia para contener escenarios diferentes, el caso de uso origen se verá afectado, porque todos sus escenarios también cambiarán; pero el caso de uso destino no depende del caso de uso fuente. Por supuesto, hay una descomposición que se corresponde con las descripciones detalladas del caso de uso. Las nuevas descripciones podrían leerse: • Tomar prestada copia de libro Un PrestatarioLibro presenta un libro. El sistema comprueba que el prestatario potencial es socio de la biblioteca, y que él o ella todavía no tiene el máximo número de libros en préstamo permitidos. Este máximo es seis, a menos que el socio sea de la plantilla, en cuyo caso es 12. Si ambas comprobaciones tienen éxito, el sistema comprueba si hay una reserva sobre el libro (caso de uso Comprobar para reserva); en otro caso, el sistema rechaza el prestar el libro. Si el libro está reservado, el sistema rechaza prestarlo. En otro caso, almacena que ese socio de la biblioteca tiene esta copia del libro en préstamo y apunta la fecha de devolución pegándola en el libro. • Ampliar préstamo Un PrestatarioLibro solicita (bien en persona, bien por teléfono) ampliar el préstamo de un libro. El sistema comprueba si hay una reserva sobre el libro (caso de uso Comprobar para reserva). Si es así, el sistema rechaza ampliar el préstamo. En otro caso, almacena que el préstamo del libro por este socio de la biblioteca
Ampliar préstamo
<> Comprobar para reserva
PrestatarioLibro Tomar prestada copia de libro
<>
Figura 8.1 Reutilización de casos de uso: <>.
MÁS SOBRE MODELOS DE CASOS DE USO
117
ha sido ampliado, actualizando los registros de ambos. Se apunta la nueva fecha de devolución pegándola en el libro. Si el prestatario está en persona, esto se hace pegándola de nuevo; de forma alternativa, si la ampliación se hace por teléfono, se le solicita al prestatario que altere la fecha de devolución. • Comprobar para reserva Dada la copia de un libro, el sistema busca en la lista de reservas pendientes alguna reserva sobre ese libro. Si encuentra alguna, compara el número de reservas, n, con el número de copias del libro que se sabe que hay en la estantería de libros reservados, m. Si n m entonces el sistema indica que esa copia está reservada, en otro caso indica que no. Pregunta de Discusión 60 ¿Es ésta la mejor factorización de funcionalidad? ¿Cómo compararía esta factorización con otras? ¿Puede hacerlo basado sólo en un modelo de casos de uso?
La documentación compartida o la funcionalidad reutilizada, como ésta, en un diagrama de casos de uso tiene varias ventajas: • Es una manera conveniente de almacenar la decisión de que se va a utilizar un componente, o de evitar almacenar la misma información en más de una descripción detallada de un caso de uso. • Sacar factores de partes de la descripción del caso de uso puede hacer que las descripciones de los casos de uso sean más cortas y fáciles de comprender, con tal que los casos de uso incluidos sean unidades de funcionalidad coherentes por sí mismas. • Identificar la funcionalidad común entre casos de uso en una etapa temprana puede ser una manera de descubrir una posible reutilización de un componente que puede implementar la funcionalidad compartida. Tal y como se trató en el Capítulo 7, el diagrama de casos de uso es una entrada importante en el proceso de planificación. Por lo tanto, es útil saber dónde se comparte la funcionalidad entre casos de uso; esto evita presupuestar el tiempo para la funcionalidad dos veces, que es lo que se haría si no se descubriera la funcionalidad compartida hasta más tarde. Sin embargo, destacar que no se desarrollaría un plan detallado basándonos sólo en el modelo de casos de uso, por lo que <> no es la única manera de alcanzar planes precisos. Sin embargo, hay también ciertos peligros asociados con la identificación y documentación de la reutilización de esta manera, especialmente si los casos de uso incluidos representan pequeñas partes de funcionalidad. • Hay un serio peligro de que, en la búsqueda de la reutilización en el modelo de casos de uso orientados a la funcionalidad, se vuelva atrás a un estilo de diseño de descomposición funcional de arriba a abajo: exactamente igual al estilo inflexible que se supone que la orientación a objetos ayuda a evitar. • La incorporación en un modelo de casos de uso de <> es difícil de leer por alguien que no está acostumbrado a UML, por lo que empieza a perder su atractivo como documento visible al cliente. Además, cuanto más complejo es el diagrama de casos de uso, es más difícil mantenerlo actualizado, especialmente si permite incorporar información de diseño así como sobre los requisitos.
118
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
Para abordar el primer punto, es aconsejable desarrollar el modelo de clases del nivel conceptual en paralelo con el modelo de los casos de uso, y utilizar técnicas como las tarjetas CRC, para asegurar que cualquier reutilización que aparece en el diagrama de casos de uso le da sentido conceptual también al nivel de objetos. Por supuesto, a la inversa, el uso de tarjetas CRC y de técnicas similares puede ayudar a identificar funcionalidad compartida, que debe aparecer entonces en el modelo de casos de uso. ¿Cuánta funcionalidad se necesita compartir, antes de que merezca la pena documentar la sección compartida como un caso de uso separado? Como guía aproximada, probablemente, se debería separar la funcionalidad compartida sólo si el tiempo que lleva desarrollarlo es significativo en términos de planificación, lo que normalmente significa que el tiempo que se tiene que reservar para hacerlo es mayor que la unidad mínima de tiempo en que está formulado el plan, quizá, un “día de ingeniería ideal”. En el caso que se ilustra, es improbable que sea verdad, por lo que no se querría, en la práctica, separar este caso de uso.
8.1.2
Componentes y casos de uso
Los componentes y los casos de uso interactúan (al menos) de dos maneras. Primero, consideremos el impacto de utilizar un componente en el modelo de casos de uso y, después, cómo un modelo de casos de uso puede ayudar a especificar un componente. Si se es muy serio a la hora de hacer el diseño basado en componentes, es fundamental pensar en utilizar componentes lo más pronto posible, en realidad, desde el principio del proyecto. Hay varias razones para esto. Primero, se necesita adaptar la manera en que se describen los casos de uso para encajar los componentes disponibles; se puede necesitar también negociar los cambios en los requisitos para hacer un buen uso de los componentes disponibles. Esto puede parecer radical: pero piense en el caso paralelo en, por ejemplo, arquitectura. No es probable que se le pregunte exactamente qué medida y forma quiere que tengan sus puertas. Es más probable, que se le pida elegir de entre una gama de tamaños y formas, cada uno con su propio coste; si es completamente posible especificar que quiere una puerta redonda, verde con un tirador de latón, puede esperar pagar un precio mucho más alto por ella, y esperar más tiempo, que si quiere algo estándar. Parece razonable decir que si la industria del software quiere hacer el cambio a la ingeniería basada en componentes, la aproximación a la flexibilidad de requisitos tendrá que cambiar de forma similar. Pregunta de Discusión 61 ¿Está de acuerdo? ¿O piensa, por ejemplo, que la flexibilidad inherente del software será suficiente para mitigar esto?
En segundo lugar, se quiere saber, antes de gastar tiempo y esfuerzo calculando cómo construirlo nosotros mismos, si se puede implementar algún requisito utilizando un componente. Aquí uno se encuentra por primera vez con una pregunta que se repetirá: cuando se utiliza un componente que no se necesita desarrollar, ¿aparece en los diagramas de diseño? ¿o simplemente se trata como si fuese parte del lenguaje de programación, y se utiliza? Cualquiera es posible; cuál es mejor depende del componente que sea (y algunos lo amplían al gusto). Por ejemplo, supongamos que se utiliza una clase ColecciónOrdenada de una librería de clases de conjuntos, para
MÁS SOBRE MODELOS DE CASOS DE USO
119
Comprobar para reserva
CompradorReserva
Figura 8.2 Un diagrama de casos de uso que describe un componente.
ordenar los artículos en una lista de libros retrasados. Probablemente no tiene valor mostrar Ordenar cosas como un caso de uso <>; esta parte de funcionalidad es demasiado pequeña. Sin embargo, si se utiliza un componente más complejo probablemente se quiera mostrarlo. Naturalmente, si se planifica desarrollar un componente reutilizable que incluya parte, pero no toda la funcionalidad de un caso de uso dado, también tendrá sentido describir el componente propuesto claramente con su(s) propio(s) caso(s) de uso. La principal diferencia entre un caso de uso para un componente y un caso de uso para todo el sistema, es que los actores que interactúan con un componente podrían ser objetos externos al mismo, en vez de humanos o sistemas externos o dispositivos. Un objeto externo al componente, se parece a un sistema externo o dispositivo desde el punto de vista del componente; sólo se ha cambiado la perspectiva. Por ejemplo, si Comprobar para reserva es un componente que debería documentarse de forma separada, su documentación podría incluir la descripción detallada dada antes y el diagrama de casos de uso, muy sencillo, que aparece en la Figura 8.2. Tal y como se ha mencionado anteriormente, este pequeño ejemplo probablemente es demasiado sencillo para tratarlo de esta manera.
Resumen: utilización de <> Considere la utilización de una relación <> entre casos de uso: • Para mostrar cómo el sistema puede utilizar un componente que ya existe. • Para mostrar la funcionalidad común entre casos de uso. • Para documentar el hecho de que el proyecto ha desarrollado un nuevo componente reutilizable. Un proyecto probablemente empezará desarrollando un diagrama de casos de uso sencillo que no utiliza las funciones descritas en este capítulo; un diagrama que utiliza <> probablemente está mejor visto como refinamiento de tal diagrama, sobre el que se han tomado algunas decisiones de diseño.
8.1.3
Separación del comportamiento variable: <<extend>>
Si un caso de uso incorpora dos o más escenarios con diferencias significativas —esto es, pueden ocurrir varias cosas distintas dependiendo de las circunstancias— se puede decidir que sería más claro mostrarlos como un caso principal y uno o más casos secundarios. Hacer esto es un
120
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
problema de juicio, ya que siempre se pueden mostrar casos variables en un caso de uso. Por ejemplo, se podrían separar Tomar prestada copia de libro en el caso normal en el que al usuario se le permite tomar prestado el libro, y en el caso inusual en el que al usuario no se le permite tomar prestado el libro porque él o ella ya tiene prestados el máximo número de artículos. Se utiliza la flecha <<extend>> desde el caso menos central al caso central, tal y como se muestra en la Figura 8.3. Tenga cuidado: la flecha va desde el caso excepcional al caso normal, ¡la mayoría de la gente piensa en ella como “la contraria” a la flecha <>!
PrestatarioLibro <<extend>>
Rechazar préstamo
Tomar prestada copia de libro
Figura 8.3 <<extend>>.
Otra vez hay una descomposición correspondiente de la descripción del caso de uso. En la nueva versión de la descripción del caso normal, se debe mostrar: • la condición bajo la cual se aplica el caso excepcional. • el punto en el que la condición se prueba y el comportamiento puede divergir: este es el punto de extensión. UML permite (pero no requiere) mostrar la condición con la flecha de extensión, y almacenar el punto de extensión en la elipse del caso de uso central, tal y como aparece en la Figura 8.4. Probablemente es más útil si se está utilizando un lenguaje de descripción formal o semiformal para describir los casos de uso.
PrestatarioLibro
<<extend>> demasiados libros en préstamo Tomar prestado copia de libro
Rechazar préstamo
Puntos de extensión validación de estado: después de confirmar identidad
Figura 8.4 <<extend>> con punto de extensión.
MÁS SOBRE MODELOS DE CASOS DE USO
8.2
121
Generalizaciones Dos actores, o dos casos de uso, pueden estar relacionados por medio de la generalización, al igual que dos clases 1. Por ejemplo, en el ejemplo de la biblioteca, toda persona PrestatarioRevista es PrestatarioLibro, porque las personas con derecho a tomar prestada una revista también pueden tomar prestados libros. Se puede decidir almacenar una relación de generalización entre los actores correspondientes utilizando la misma notación que se utiliza para las clases, tal y como se muestra en la Figura 8.5. Cuando los casos de uso están relacionados a través de una generalización, la idea es mostrar una tarea y una versión especializada de la misma. Otra vez, se utiliza la flecha de generalización estándar, que va desde el caso de uso especializado al caso de uso más general. Por ejemplo, si se tiene un caso de uso Reservar libro, se podría tener una especialización de la misma llamada Reserva de libro por teléfono. Esto podría ser útil si el sistema de la biblioteca necesita comportarse de manera diferente para una reserva por teléfono; por ejemplo, si implica que haya que introducir de forma manual el número de la tarjeta de la biblioteca del usuario debido a que la tarjeta no puede ser escaneada. Esto es muy parecido a <<extend>> y es discutible que UML deba tener ambos. Una regla básica es que si se quiere describir un comportamiento extra que muchas veces hay que añadir dependiendo de las condiciones “de tiempo de ejecución”, probablemente se quiera <<extend>>; mientras que si lo que se quiere es una etiqueta para una versión especializada de una tarea completa, probablemente se quiera generalización.
PrestatarioLibro
PrestatarioRevista
Figura 8.5 Generalización entre actores. 1
Clases, actores y casos de uso son todos clasificadores en UML y cualquier clasificador puede ser generalizado.
122
8.3
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
Actores y clases Es muy común que un sistema interactúe con un (instancia de) actor y que tenga también un objeto interno representando la instancia de actor. Hay dos tipos de situaciones fundamentales en las que puede pasar lo siguiente: 1. El sistema puede necesitar almacenar datos sobre un actor, normalmente una persona en un determinado rol. Por ejemplo, el sistema de biblioteca necesita saber qué personas tienen derecho a tomar prestados libros, y cuántos libros tiene cada uno de ellos en préstamo, para ejecutar el caso de uso Tomar prestada copia de libro. En sistemas orientados a objetos, suele significar que hay un conjunto de personas reales que pueden tomar el rol descrito a través de un actor del sistema dado, y también un conjunto de objetos del sistema, uno por persona, que almacena información sobre las personas en ese rol. 2. Una situación sutilmente diferente es cuando el sistema envuelve un sistema externo para proporcionar una forma manejable para que partes del sistema puedan acceder al sistema externo y viceversa. Por ejemplo, un monitor de procesamiento de transacciones externo podría ser accedido por medio de mensajes enviados a un objeto TPMonitor interno que, por turnos, invoca la funcionalidad real del sistema externo. O un programa de interfaz de usuario separado podría representarse dentro del sistema utilizando un objeto UI que en realidad media entre el programa UI externo y el sistema principal, pasando mensajes en ambos sentidos. Las funciones de estos dos casos pueden combinarse: por ejemplo, una de las maneras más fáciles de proporcionar una interfaz de usuario sencilla a un sistema, que puede mantener la necesidad del sistema de responder a diferentes usuarios de distintas maneras, es hacer que los casos de uso, que pueden ser iniciados por Jane Bloggs, estén disponibles como métodos de un objeto del sistema que representa a Jane Bloggs. Un ejemplo de esto es la manera en que se implementa el caso de uso Tomar prestada copia de un libro en el Capítulo 3. Sin embargo, es fácil confundirse e importante recordar dónde está el límite del sistema. La diferencia principal es que se pueden programar los objetos del sistema para que hagan lo que uno quiera, ¡pero no los actores del sistema! Pregunta de Discusión 62 ¿Cuáles son las ventajas y las desventajas de representar las instancias de actor como objetos del sistema?
8.3.1
Notación: actores como clases
Sólo para hacer las cosas aún más confusas, se puede escuchar a gente decir que los actores son clases, con el estereotipo <>. Esto es cierto a nivel de notación: un actor puede estar representado por un icono de clase con el estereotipo <> en vez de un muñeco, tal y como se muestra en la Figura 8.6. Sin embargo, como se adelantó anteriormente, en realidad los actores y las clases son ambos clasificadores, en vez de ser uno cualquiera, un tipo del otro.
MÁS SOBRE MODELOS DE CASOS DE USO
123
<> PrestatarioLibro
PrestatarioLibro
Figura 8.6.
Estos dos símbolos significan lo mismo.
RESUMEN
En este capítulo se ha mostrado cómo la relación <> puede almacenar la funcionalidad común a varios casos de uso, y cómo la relación <<extend>> puede almacenar lo que ocurre en casos inusuales. Se ha tratado la generalización entre actores y entre casos de uso, y la relación entre actores y clases. En los siguientes dos capítulos se demostrará cómo los diagramas de interacción pueden almacenar la interacción de los objetos para realizar los casos de uso.
PREGUNTA DE DISCUSIÓN
1. Se ha sugerido que la versión de un diagrama de casos de uso, utilizando las características aquí descritas, debería utilizarse en conjunción con una forma más sencilla de la descrita en el capítulo anterior. ¿Cómo cree que una herramienta CASE podría soportar esto de forma sensata?
Capítulo
9 Fundamentos de los diagramas de interacción
Hasta ahora se han visto los dos modelos de UML más importantes: • El modelo de casos de uso, que describe las tareas que el sistema debe ayudar a ejecutar • El modelo de clases, que describe las clases que tratan de alcanzar esto y las relaciones entre ellas. En la discusión del Capítulo 5 sobre las tarjetas CRC, se empezó a abordar, de manera informal, el tema de cómo asegurar que el modelo de clases sea capaz de realizar los casos de uso. Los diagramas de interacción de UML permiten almacenar en detalle cómo los objetos interactúan para ejecutar una tarea. El principal uso de tales diagramas es mostrar cómo el sistema realiza un caso de uso, o un escenario en particular, en un caso de uso. Se considerarán otros usos al final del capítulo. Se pueden utilizar las tarjetas CRC para examinar qué objetos interactúan y cómo, y se pueden utilizar los diagramas de interacción para almacenar lo que pasa de forma precisa. Es útil para examinar varias opciones posibles en los casos difíciles. Los diagramas de interacción, pueden ser también una ayuda para la comunicación entre desarrolladores, si hay varias personas o grupos diferentes desarrollando partes de una única interacción. Normalmente no se desarrollan diagramas de interacción para cada caso de uso o para cada operación: como siempre, se hacen cuando el beneficio tiende a superar el coste. Si se tiene una herramienta CASE, que puede utilizar los diagramas de interacción para ayudar con la generación de código, es probable que merezca la pena desarrollar los diagramas de interacción. UML proporciona dos tipos de diagramas de interacción: los diagramas de secuencia y de comunicación. Los diagramas de secuencia son mucho más expresivos, permitiendo mostrar cursos alternativos de una acción, la repetición de partes de comportamiento, y más cosas. Sin embargo, en los casos más sencillos muestran casi la misma información; dado un modelo de clases subyacente, algunas herramientas CASE pueden generar uno a partir del otro. Qué es mejor, depende del aspecto de la interacción en el que uno se tenga que centrar: se volverá a esta cuestión después de mostrar formas sencillas de ambas.
126
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
En este capítulo se describirán los casos más sencillos de ambos tipos de diagrama de interacción, y se tratarán sólo las interacciones de procedimiento sencillas. En el Capítulo 10 se tratarán diagramas de secuencia más complejos, y las cuestiones que surgen cuando se quiere describir sistemas concurrentes. ANOTACIÓN TÉCNICA DE UML Los diagramas de interacción son un área de UML que ha cambiado de forma significativa en UML 2. Los tecnicismos subyacentes son ahora algo más complejos (y no siempre quedan totalmente claros en el estándar). Por ejemplo, las cajas en los diagramas de interacción no son técnicamente “objetos”; son especificaciones de objetos, que describen los roles que los objetos desempeñan en las interacciones. No se entrará en esta cuestión y se hará referencia a ellos como objetos.
9.1
Colaboraciones Colectivamente, los objetos que interactúan para ejecutar alguna tarea, junto con los enlaces entre ellos, se conocen como colaboraciones. Por ejemplo, la Figura 9.1 muestra una colaboración que es apropiada para realizar el caso de uso Tomar prestada copia de libro, que se trató brevemente en el Capítulo 3. Una colaboración, donde no aparece ninguna interacción, es bastante parecida a una instancia de una parte del modelo de clases. Muestra objetos, enlaces y actores.
elLibro : libro
unSocio: PrestatarioLibro
elSocioBiblioteca :
LaCopia : Copia
SocioBiblioteca
Figura 9.1 Una colaboración sencilla, sin interacciones.
Objetos Cada objeto aparece como un rectángulo, que está etiquetado con nombreObjeto:nombreClase. Aquí nombreClase tiene que ser el nombre de una clase del modelo de clases. No es
FUNDAMENTOS DE LOS DIAGRAMAS DE INTERACCIÓN
127
necesario que haya un objeto por cada clase, porque algunas clases serán irrelevantes en la colaboración particular que se esté tratando. Puede haber dos o más objetos diferentes de una misma clase. Puede que le haya ocurrido que los nombres de objetos como elSocioBiblioteca no son muy informativos. Si, como aquí, un nombre así no hace el diagrama más legible, puede omitirse; el rectángulo del objeto puede etiquetarse simplemente como: SocioBiblioteca.
Enlaces Los enlaces entre objetos aparecen como asociaciones en el modelo de clases. Ya que un enlace es una instancia de una asociación, debe haber una asociación en el modelo de clases entre las clases de dos objetos cualesquiera enlazados. Una vez más, la colaboración no tiene que incluir enlaces para todas las asociaciones, sólo para las relevantes. Aquí se puede mostrar información extra sobre la naturaleza del enlace, como en el modelo de clases. Por ejemplo, pueden utilizarse flechas en las líneas para mostrar la navegabilidad o también pueden aparecer los nombres de la asociación si se considera que el diagrama queda más claro.
Actores Los actores pueden aparecer como en un diagrama de casos de uso. Si la colaboración está describiendo la realización de un caso de uso, los actores en la colaboración se corresponderán con los actores del caso de uso en el diagrama de casos de uso. Puede haber varios actores, pero siempre habrá uno que inicia el caso de uso: le llamaremos el iniciador.
9.2
Diagramas de comunicación A continuación, se trata cómo mostrar una interacción en un diagrama de colaboración: esto es, cómo mostrar la secuencia de mensajes intercambiados entre los objetos enlazados.El resultado se conoce como diagrama de comunicación. Utilice las tarjetas CRC o cualquier otra técnica para decidir cuál sería la secuencia de mensajes. Aquí se está considerando una única interacción en particular: por ejemplo, se decide mostrar el flujo de Tomar prestada copia de libro donde al usuario se le permite tomar prestado el libro, en vez de cualquiera de las posibles variantes en las que el usuario tiene demasiados libros en préstamo o el libro está reservado. (Por cierto, si es completamente obvio qué mensajes tienen que intercambiar debería considerar si merece la pena o no dibujar el diagrama de interacción completo. Puede ser, por ejemplo, si el diagrama ayuda a que la gente se comunique). Almacene los mensajes junto a los enlaces en el diagrama de comunicación. Por ejemplo, la Figura 9.2 muestra el flujo normal de Tomar prestada copia de libro. Cada flecha etiquetada representa un mensaje enviado por el objeto que está en la cola de la flecha hacia el objeto al que apunta la flecha, por lo que tiene que haber un enlace entre estos dos objetos, y tiene que ser navegable en la dirección del mensaje. Además, el objeto destino tiene que entender el mensaje. Es decir, la clase del objeto al que apunta la flecha tiene que proporcionar la operación adecuada. El desarrollo de diagramas de interacción puede ayudar a identificar las asociaciones entre las clases, y las operaciones que necesitan las clases.
128
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
elLibro : Libro
unSocio : PrestatarioLibro tomar prestado(la copia)
2.1: prestada
elSocioBiblioteca :
laCopia : Copia
SocioBiblioteca 1: tomarPrestada 1: okParaTomarPrestada
Figura 9.2 Interacción mostrada en un diagrama de comunicación.
Es bueno revisar el modelo de clases mientras se desarrollan los diagramas de interacción. Sin embargo, ¡es fundamental estar seguro de que el modelo de clases se mantiene correcto!
La solución más obvia al problema principal puede que no sea la mejor de todas: puede que se tengan que volver a revisar las decisiones previas para obtener una solución que funcione correctamente en todos los casos. Es fundamental mantener consistentes los modelos: puede ser útil una herramienta CASE y/o una herramienta de gestión de configuración.
Activaciones: flujo de control En las interacciones procedurales (frente a aquellas que implican concurrencia, que se tratarán en el Capítulo10), en cada momento hay exactamente un objeto operando. Un objeto empieza a operar cuando recibe un mensaje; en este momento se dice que tiene una activación existente. Eventualmente, debería devolver una respuesta al emisor del mensaje. En medio puede simplemente realizar cálculos, o puede enviar mensajes a otros objetos para hacerles operar. Si envía un mensaje, todavía tiene una activación existente, pero no puede continuar con el cálculo hasta que reciba una respuesta al mensaje. Es decir, el mensaje es síncrono, y el envío de un mensaje pasa el control al receptor del mismo. En todo momento, hay una pila de activaciones existentes, cada una de las cuales está asociada a un objeto que ha recibido un mensaje que todavía no ha contestado. El objeto asociado a la activación de la cima tiene el control y está operando; los objetos cuyas activaciones sólo aparecen en la parte más baja de la pila están esperando las respuestas a los mensajes que han enviado. El objeto de la cima puede enviar un mensaje, en cuyo caso el receptor del mismo obtiene una activación existente que se convierte en la nueva cima de la pila. Por otro lado, la activación de la cima puede terminar su operación, contestar al mensaje que provocó esta activación, y ser eliminado de la pila. La siguiente activación se convierte en la nueva activación de la cima, y su objeto asociado —que envió el mensaje que acaba de ser contestado— recupera el control.
FUNDAMENTOS DE LOS DIAGRAMAS DE INTERACCIÓN
129
En los sistemas procedurales ordinarios (hasta ahora se han considerado todos) sólo un actor puede iniciar una actividad, es decir, envía un mensaje “inesperado”. Un objeto del sistema envía mensajes sólo después de haber recibido uno (y antes de contestarlo).
Puede pensar en el control como en un “elemento léxico (token)” que se envía como un mensaje a lo largo de los enlaces en un diagrama de comunicación, y después se devuelve cuando se haya tratado el mensaje. Aquí no se muestran de forma explícita estas devoluciones. Para hacer un seguimiento de la pila de objetos con activaciones existentes, los mensajes se enumeran unívocamente con un esquema de numeración anidado. El primer mensaje de un objeto a otro (es decir, sin contar el mensaje del actor a un objeto, que es el que inicia la interacción —ese mensaje no está numerado) tiene el número uno. Cada vez que un objeto O recibe un mensaje, el número de ese mensaje se utilizará como prefijo de todos los mensajes que se envían hasta que O responde a dicho mensaje. Por ejemplo, si el mensaje que activa O tiene el número 7.3, todos los mensajes enviados desde entonces hasta que se le responde, tienen número 7.3.algo. Si O envía un mensaje a otro objeto P, ese mensaje tiene el número 7.3.1. Si después de obtener una respuesta al mensaje 7.3.1, O envía otro mensaje, tendrá el número 7.3.2, y así sucesivamente.
P: Si después de recibir el mensaje 7.3.1 desde O, el objeto P envía un mensaje, ¿cuál sería su número? Pregunta de Discusión 63 Podría pensar que es más obvio numerar todos los mensajes como 1, 2,..., en vez de utilizar este esquema anidado. ¿Cuál sería la diferencia? ¿Puede crear una situación en la que esto fuese ambiguo?
P: Si se han enviado los mensajes 2.4.1, 2.4 y 2.4.1.7, pero no han recibido respuesta, ¿qué se puede decir de los objetos que enviaron y recibieron cada mensaje? ¿Cuáles están activos? ¿Cuántos y cuáles, están operando? P: ¿Es posible que los mensajes 4.5 y 4.6 hayan sido enviados y no hayan recibido respuesta? ¿Por qué? ¿Se puede generalizar?
9.3
Diagramas de secuencia Un diagrama de secuencia muestra los objetos y actores que participan en una colaboración encima de las líneas punteadas. La línea representa el tiempo visto por el objeto: es la línea de vida del objeto. Se supone que el tiempo pasa según nos movemos de arriba a abajo en el diagrama. Un mensaje aparece como una flecha desde la línea de vida del emisor hasta la línea de vida del receptor. Los últimos mensajes están en la parte de abajo de la página. Según UML2, no se muestran los números de los mensajes en los diagramas de secuencia; normalmente, toda la información que se necesita sobre el orden de los mensajes es evidente en los diagramas de secuencia. La Figura 9.3 (también utilizada en el Capítulo3) muestra la versión de un diagrama de secuencia del diagrama de comunicación sencillo que hemos utilizado antes.
130
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
elSocioBiblioteca :
laCopia : Copia
elLibro : Libro
SocioBiblioteca unSocio : PrestatarioLibro tomarPrestado (laCopia)
1: okTomarPrestado
2: tomarPrestado 2.1: prestado
Figura 9.3 Interacción mostrada en un diagrama de secuencia.
No importa el orden en que aparecen los objetos; sin embargo, el diagrama será más legible si se colocan los objetos que participan antes lo más a la izquierda posible, de manera que la mayoría de los mensajes fluyen de izquierda a derecha. Cuando un objeto tiene una activación existente, se muestra un rectángulo estrecho que cubre su línea de vida. Opcionalmente, se pueden sombrear las partes de la activación en las que el objeto está realmente operando. De nuevo opcionalmente, se puede indicar cuándo tienen lugar las respuestas a los mensajes: aunque tal y como se trató anteriormente, un objeto deja de tener una activación existente en el momento exacto en que responde al mensaje que provocó la activación, por lo que se puede decir cuándo tienen lugar las respuestas mirando las activaciones.
P: ¿Por qué las flechas de los mensajes siempre apuntan al comienzo de los rectángulos de activación, y nunca a la mitad? ¿Por qué los mensajes de un objeto a sí mismo es una excepción a esta norma? (Para más información, véase más abajo).
Destacar que, aunque los enlaces entre objetos no aparecen de forma explícita en un diagrama de secuencia, todavía hay una colaboración subyacente, tal y como la hay en un diagrama de comunicación. Si dos objetos intercambian un mensaje, debería haber una asociación entre sus clases. PANEL 9.1
¿Dónde deben colocarse los mensajes? Ley de Demeter
Como se ha dicho, por debajo de toda interacción hay una colaboración: un conjunto de objetos con enlaces entre ellos. Se ha dicho que los objetos deberían intercambiar mensajes sólo cuando hay una asociación entre sus clases (o son de la misma clase). Pero esto no es una norma de diseño, ya que si los objetos pueden intercambiar un mensaje, entonces, por definición, hay una asociación entre sus clases: si en el modelo de clases parece como si no existiese, simplemente es un error en el modelo.
FUNDAMENTOS DE LOS DIAGRAMAS DE INTERACCIÓN
131
Considere el fragmento del diagrama de clases de la Figura 9.4 1. En este caso sólo se muestran las formas de navegación explícitas: por ejemplo, la flecha desde Trabajo hacia ControladorGeneral ilustra que cada objeto Trabajo puede enviar un mensaje directamente al ControladorGeneral asociado. (Esto puede deberse a que cada objeto Trabajo tiene una referencia a un ControladorGeneral; de hecho en el caso real había un único ControladorGeneral global). No se muestra una flecha desde Trabajo hacia ControladorTrabajo. La razón es que si un objeto Trabajo quiere enviar un mensaje a su ControladorTrabajo (cosa que hacía a menudo, en el caso real) tiene primero que enviar el mensaje obtenerCT al ControladorGeneral, con él mismo como parámetro (“dame mi ControladorTrabajo”). El ControladorGeneral busca qué ControladorTrabajo controla ese trabajo, y devuelve una referencia a ese ControladorTrabajo. El Trabajo puede ahora utilizar esa referencia para enviar un mensaje al ControladorTrabajo. ControladorTrabajo
ControladorGeneral 1
0..*
ObtenerCT(t:Trabajo):ControladorTrabajo 1
1
0..*
0..* Trabajo
Figura 9.4 Mal diseño, se rompe la norma de Demeter.
Pregunta de Discusión 64 ¿Qué formas alternativas hay para permitir que Trabajo tenga acceso al comportamiento que proporciona ControladotTrabajo? ¿Por qué cree que se eligió esta manera?
Pregunta de Discusión 65 ¿Qué otra cosa es sospechosa en este diseño?
El principal problema con este tipo de diseño, es que es difícil de mantener. Si cambia ControladorTrabajo, o si se decide modificar la relación entre ControladorGeneral 1
Ejemplo comercial real: se han cambiado los nombres de las clases, pero el significado es similar.
132
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
y ControladorTrabajo, los clientes de ControladorGeneral tendrían que ser examinados para ver si tienen que cambiar también. Hasta cierto punto, el ControladorGeneral expone no sólo lo que debería ser su propia interfaz, sino también la estructura del diagrama de clases al que está adjunto. Su interfaz es mucho más grande de lo que parece. Seguir la Norma de Demeter permite a los diseñadores evitar “este tipo de diseño”. Dice que como respuesta a un mensaje m, un objeto O debería enviar mensajes sólo a los siguientes objetos: 1. A sí mismo. 2. A los objetos enviados como argumentos al mensaje m. 3.
A los objetos que O crea como parte de su reacción ante m.
4. A los objetos que son accesibles directamente desde O, es decir, utilizando los valores de los atributos de O.
P: ¿Cómo rompe el diseño anterior la Norma de Demeter? P: Considere otra vez el caso de una Agenda implementada con una Lista. Se ha excluido (en el Capítulo 5) por mal diseño, la idea de permitir que Agenda herede de Lista, y se ha dicho que en su lugar Agenda debería poseer una Lista por medio de una agregación. ¿Por qué la Agenda no debería tener un método que devolviese la Lista a los objetos que quieran añadir o borrar direcciones de la misma? ¿Qué debería hacer en su lugar?
La Norma de Demeter está descrita en [33]. Es una pequeña parte de lo que Karl Lieberherr, su inventor, llama programación adaptativa. Aquí no tenemos espacio para describirlo, pero hay algunos enlaces desde la página web de este libro.
9.4
Características más avanzadas 9.4.1
Mensajes desde un objeto a sí mismo
Un objeto puede, y frecuentemente lo hace, enviarse un mensaje a sí mismo. En un diagrama de colaboración aparece un enlace desde el objeto a sí mismo, y en un diagrama de comunicación los mensajes pasan a lo largo de ese enlace de la manera normal. En un diagrama de secuencia, aparece una flecha de mensaje desde la línea de vida del objeto de vuelta a sí misma. Sin embargo, hay un problema. Antes se dijo que cuando un objeto recibe un mensaje obtiene el control, y se añade una nueva activación existente de ese objeto a la cima de la pila de activaciones existentes. En este caso, el objeto ya tiene una activación existente cuando envió el mensaje; ahora tiene una activación nueva diferente, ¡porque él también es el receptor del mensaje! Es decir, este objeto está asociado a dos activaciones diferentes en la pila. Se puede mostrar esto utilizando una activación anidada; el rectángulo estrecho que representa la nueva activación aparece ligeramente desplazado del rectángulo que representa la activación antigua, para que sea visible. La Figura 9.5 muestra una versión del diagrama de secuencia en el que aparecen todas estas características opcionales. En la programación orientada a objetos pura, cada invocación a una función es el resultado de un mensaje, y los objetos pueden enviarse tantos mensajes a sí mismos que un diagrama de
FUNDAMENTOS DE LOS DIAGRAMAS DE INTERACCIÓN
:SocioBiblioteca
: Copia
133
: Libro
unSocio: PrestatarioLibro tomarPrestado (laCopia)
okTomarPrestado
tomarPrestado prestado
Figura 9.5 Interacción mostrada en un diagrama de secuencia, con características opcionales.
interacción puede quedar confuso. Se podría decidir omitir los mensajes de un objeto a sí mismo, considerando que son cálculos internos del objeto. Pregunta de Discusión 66 ¿Hacer esto crea problemas o ambigüedades? Si es así, ¿cómo deberían resolverse? Piense un caso en el que un objeto se envía un mensaje a sí mismo, y parte de su reacción ante este mensaje es enviar un mensaje a un objeto diferente.
9.4.2
Valores de retorno
A veces es útil nombrar los valores que son respuesta a un mensaje; por ejemplo, a menudo el valor devuelto por un mensaje es un argumento de un mensaje posterior. Los valores devueltos aparecen en la flecha del mensaje original, mostrando una asignación al nombre de una nueva variable. La sentencia de asignación enlaza la variable, que puede entonces utilizarse en mensajes enviados después de recibir la respuesta del mensaje. También se permite (pero no es obligatorio) mostrar el valor concreto que se devuelve; por ejemplo, se podría escribir: n = obtenerNombre(): “Pepito Pérez”. La Figura 9.6 (que muestra también la creación y borrado de objetos, que se describirán en el siguiente apartado) muestra un ejemplo. Pregunta de Discusión 67 ¿Podría ser útil nombrar el valor devuelto por un mensaje si no fuese mencionado en un mensaje posterior?
134
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
:ProfesorAdjunto :OENG obtenerNombre() n = obtenerNombre()
new DirectorDeEstudios (n)
:DirectorDeEstudios
destroy()
Figura 9.6 Diagrama de secuencia: creación y borrado de objetos, y uso del valor de retorno.
9.4.3
Creación y borrado de objetos
El conjunto de objetos implicados en una interacción no es siempre estático; los objetos pueden crearse y borrarse durante una interacción. En UML2 (a diferencia de UML1.x), sólo los diagramas de secuencia proporcionan notación para mostrar la creación y el borrado de objetos; esto resalta que los diagramas de comunicación están pensados para que se utilicen sólo con interacciones sencillas. En un diagrama de secuencia, se puede indicar la creación de un objeto moviendo la caja del objeto hasta el punto en el que es creado. Se utiliza una flecha de punta abierta para el mensaje de creación. La destrucción de un objeto se muestra con la finalización de su activación con una gran X. La Figura 9.6 muestra la creación de un objeto y la destrucción de otro objeto. El ejemplo es una ampliación del estudio del caso del Capítulo 15: describe un escenario de un posible caso de uso Promover ProfesorAdjunto, que no se describe en el Capítulo 15. Un profesor adjunto, descrito en el sistema por un objeto de la clase ProfesorAdjunto, es ascendido a director de estudios (es decir, un profesor adjunto con responsabilidades especiales con estudiantes en particular). Hay que crear un nuevo objeto DirectorDeEstudios, y el antiguo objeto ProfesorAdjunto hay que borrarlo. (En algunos lenguajes de programación esto podría hacerse cambiando la clase de un objeto existente) 2. 2 Se asume que las promociones serán entre los años académicos, por lo que no hay objetos Módulo o Estudiante asociados con el objeto ProfesorAdjunto que tiene que ser reasociado con el nuevo DirectorDeEstudios.
FUNDAMENTOS DE LOS DIAGRAMAS DE INTERACCIÓN
135
Nombres de mensajes para la creación y borrado Los mecanismos para la creación y destrucción de objetos son dependientes del lenguaje. Normalmente, se puede inicializar un objeto con algunos valores al tiempo que se crea; puede ser conveniente indicar esto en un diagrama de interacción utilizando un mensaje con un nombre adecuado como “new” o “create”, con los valores de inicialización como argumentos. Por supuesto, éste no es un mensaje normal a un objeto, ¡ya que el objeto no existe hasta después de que llegue el mensaje! Muchos lenguajes (por ejemplo Java y Smalltalk) son colectores de basura: es decir, los objetos se destruyen automáticamente cada cierto tiempo cuando no hay referencias a los mismos en el sistema (familiarmente hablando). En este caso, el programador no necesita borrar los objetos de forma explícita. (Esto elimina una gran cantidad de errores de programación: en realidad, es tan útil que Bertrand Meyer convirtió tal gestión de memoria automática en uno de sus “Siete Pasos Hacia la Felicidad Orientada A Objetos” [35]). Tales lenguajes normalmente tienen convenciones sobre cómo un objeto deja claro al colector de basura (y a cualquier lector del código) que no se necesita más. Por ejemplo, puede cambiar una referencia que previamente apuntaba al objeto para apuntar a otro sitio. Cuando ninguna parte del sistema está utilizando un objeto, éste será eliminado por el colector de basura. En los lenguajes donde el programador tiene que gestionar la memoria explícitamente, es crucial que un objeto sólo destruya los objetos de los que es responsable, así como únicamente sus propias partes: véase el tratamiento de la composición en el Capítulo 6. También es importante que algunos objetos destruyan un objeto que no se necesita más: un fallo en esto, nos lleva a “agujeros de memoria”. Para evitar ambos tipos de problemas, el diseño debe especificar dónde recae la responsabilidad de destruir cada objeto. Pregunta de Discusión 68 Si el sistema se tiene que implementar en un lenguaje colector de basura, ¿es coherente que un diagrama de interacción muestre la destrucción de un objeto? Si es así, ¿cuándo y por qué?
P: ¿Cómo se crean y destruyen los objetos en los lenguajes que conoce? Desarrolle un diagrama de secuencia sencillo en el cual: 1. Un objeto O recibe un mensaje m de un actor. 2. O crea un nuevo objeto P. 3. O envía un mensaje a Q. 4. Después de recibir la respuesta desde Q, P retorna. 5. O destruye u olvida a P, siguiendo las convenciones de su lenguaje. A continuación, escriba el código para implementar este escenario.
P: Si, al recibir un mensaje, un objeto realiza alguna operación y después se destruye a sí mismo, ¿Cuál podría ser el valor de retorno? Si su lenguaje soporta de forma explícita la destrucción de objetos, escriba un método en el que el receptor del mensaje se elimine a sí mismo.
136
9.5
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
Diagramas de interacción para otros propósitos Hasta aquí se han estado utilizando los diagramas de interacción para mostrar cómo un sistema completo realiza un caso de uso. Pueden ser útiles también para describir otros comportamientos. Aquí hay algunos ejemplos.
9.5.1
Muestra de cómo una clase proporciona una operación
Cuando un objeto recibe y actúa sobre un mensaje, iniciará una interacción con otros objetos del sistema. Un diagrama de interacción puede utilizarse para mostrar qué objetos están implicados y qué mensajes se intercambian. Lo que inició el mensaje original es externo a esta colaboración. Puede representarse como un actor (quizá Iniciador). Como alternativa, el mensaje inicial puede tener un círculo negro pequeño añadido a su cola, para indicar que es un mensaje encontrado, por ejemplo, un mensaje que no ha sido enviado por ninguno de los objetos del diagrama.
9.5.2
Descripción de cómo funciona un patrón de diseño
Se tratarán los patrones de diseño en el Capítulo 18. En UML la estructura de un patrón de diseño se trata como una colaboración parametrizada, dentro de la cual las clases están conectadas para obtener una colaboración real. La interacción entre los objetos de estas clases puede describirse utilizando los diagramas de interacción.
9.5.3
Descripción de cómo utilizar un componente
El tipo de componente más sencillo, uno de los cuales se contiene a sí mismo y proporciona sólo una interfaz describiendo lo que puede hacer, puede tratarse en todas partes como una caja negra. Sin embargo, a veces los componentes tienen interfaces más complicados que ésta; pueden tener varios puntos de conexión con el resto del sistema, cada uno de ellos requiriendo la presencia de un objeto que proporcione una cierta interfaz. En un caso como este, puede ser importante comprender cómo los componentes esperan interactuar con estos objetos que son externos a él. Un diagrama de interacción, que es parte de la documentación del componente, puede representar cada objeto de estos como un actor. El usuario del componente tiene que asegurarse que los objetos que desempeñan estos roles interactúan con el componente tal y como se espera.
RESUMEN
Se ha tratado cómo utilizar los diagramas de interacción —diagramas de secuencia y de comunicación— para describir cómo interactúan los objetos para alcanzar parte del comportamiento. Los diagramas de comunicación son mejores para mostrar los enlaces entre los objetos; los diagramas de secuencia son mejores para ver la secuencia de mensajes intercambiados. Los diagramas de interacción pueden utilizarse para describir cómo un sistema realiza un caso de uso, o para otros propósitos entre los que se incluyen mostrar cómo una clase realiza una operación o cómo se utiliza un componente complejo.
Capítulo
10 Más sobre diagramas de interacción
En este capítulo se tratarán dos clases de las características más avanzadas de los diagramas de interacción. La mayor parte de este capítulo atañe a los diagramas de secuencia, más que al otro tipo de diagrama de interacción: los diagramas de comunicación. Esto es debido a que, especialmente en UML2, los diagramas de comunicación están pensados para las interacciones sencillas. Se tratará: • Cómo representar el comportamiento condicional e iterativo. • Cómo modelar el paso de mensajes en sistemas concurrentes.
10.1
Más allá de las secuencias simples de mensajes En el Capítulo 9 se trató cómo mostrar una única secuencia de mensajes posible. Debido a que un caso de uso puede incluir diferentes escenarios sustancialmente diferentes, a veces es útil mostrar un comportamiento condicional o un número variable de iteraciones en un diagrama de interacción, para cubrir varias situaciones. Como en cualquier situación donde pueden utilizarse las características más avanzadas de UML para aumentar la expresividad, hay un serio peligro de derrotar al objeto1, mediante el desarrollo de un diagrama demasiado complicado de leer.
10.1.1
Comportamiento condicional
Un mensaje, o una parte de comportamiento más grande, puede ser guardado por una condición. Esto se puede ver en varios de los ejemplos de los estudios de casos. El comportamiento sólo se 1
Juego de palabras deseado.
138
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
ejecuta si la guarda tiene valor verdadero cuando el sistema llega a ese punto en la interacción. En un diagrama de comunicación, sólo se puede poner una guarda en un mensaje individual. Esto se hace escribiendo la condición entre corchetes delante del mensaje. Así se representa una condición si sencilla. Una condición representa una expresión lógica, es decir, es verdadera o falsa. UML no establece cuáles pueden ser las condiciones: se podrían expresar en español, en OCL (Object Constraint Language, Lenguaje de Restricción de Objetos, LRO), en su lenguaje de programación o utilizando cualquier otra notación. Por supuesto, el proyecto tiene que ponerse de acuerdo sobre qué utilizar y ser consistente. En un diagrama de secuencia, se tiene una notación más engorrosa, pero también más expresiva. Para ver un ejemplo, véase la Figura 10.1. La parte del diagrama de secuencia que representa el comportamiento condicional está encerrado en un rectángulo. El rectángulo tiene la etiqueta opt, abreviatura de “optional” (opcional, en inglés), y la condición de guarda está escrita entre corchetes al lado de la etiqueta opt. Esto es más explicativo ya que permite mostrar una parte de comportamiento que está controlada por la misma condición; en un diagrama de comunicación, habría que poner guardas en cada uno de los mensajes. Sin embargo, puede parecer muy engorroso si la condición sólo afecta a un solo mensaje. En UML1.x se permitía etiquetar una sola flecha de mensaje con una condición entre corchetes, en los diagramas de secuencia tal y como se hace en los diagramas de comunicación. Podría ser conveniente permitir en un proyecto esta notación, aunque en UML2 no sea formalmente correcto. Cuando se quiere representar una condición si/entonces/si-no o una condición múltiple, se puede utilizar una notación similar, pero utilizando la etiqueta alt en vez de opt. Las diferentes alternativas quedan separadas por líneas de puntos, tal y como se muestra en la
elSocioBiblioteca:
laCopia : Copia
elLibro : Libro
SocioBiblioteca
unSocio: PrestatarioLibro tomarPrestado(laCopia)
opt
[okTomarPrestado]
tomarPrestado prestado
Figura 10.1
Comportamiento opcional en un diagrama de secuencia.
MÁS SOBRE DIAGRAMAS DE INTERACCIÓN
:Copia
:Reservas
139
:Libro
tomarPrestado(laCopia)
alt
[reserva] notificarDisponibilidad(self)
[si-no]
devuelto(self)
Figura 10.2
Comportamientos alternativos en un diagrama de secuencia.
Figura 10.2. Hay que asegurarse de que las diferentes condiciones son excluyentes entre sí (en cada momento, como mucho una de las condiciones puede tener valor verdadero). Pregunta de Discusión 69 Recordar que en UML2, en un diagrama de secuencia los mensajes no se numeran, a diferencia de los mensajes en los diagramas de comunicación, o de los mensajes en los diagramas de secuencia en UML1.x. Piense cuál sería la mejor manera de hacer referencia a un mensaje particular en un diagrama de secuencia complicado.
10.1.2
Iteración
Piense en un escenario en el que un objeto envía un mensaje a otro objeto un número de veces. Si el número de veces es fijo —es el mismo para todos los escenarios del caso de uso— se podría mostrar el mensaje en el diagrama de interacción ese número de veces. Sin embargo, no es muy conveniente hacer esto, especialmente si al enviar el mensaje se envía siempre una cadena de mensajes. Peor, si el número de veces que se envía el mensaje varía en cada escenario de un caso de uso, puesto que en general es imposible mostrarlo. Al igual que en los lenguajes de programación, se necesita una estructura de bucle, o iteración. UML permite, en un diagrama de comunicación, marcar con un asterisco un mensaje para indicar que se envía de forma reiterada. En el caso más sencillo, no se necesita especificar cuántas veces se envía; pero a menudo se combina el asterisco con una cláusula iterativa, que es una expresión entre corchetes como la condición en un mensaje condicional (no confundirlo).
140
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
La cláusula de iteración describe, de alguna manera, cuántas veces se enviará el mensaje. Una vez más, la expresión puede ser cualquier cosa que el proyecto acepte utilizar. Algunos ejemplos podrían ser: • [i:= 1..10] el mensaje se enviará 10 veces. • [x < 10] el mensaje se va a enviar de forma reiterada, hasta que x sea menor que 10; • [artículo no encontrado] el mensaje se enviará reiteradamente, hasta que el artículo se encuentre.
P: ¿Qué fragmentos de programa implementaría usted para cada ejemplo? P: ¿Cuál es la diferencia entre un mensaje marcado con un * y otro marcado con *[verdadero]? Al igual que en la ejecución condicional, esta notación (en UML1.x) se utilizaba también en los diagramas de secuencia, pero ahora no se permite formalmente. En su lugar, en los diagramas de secuencia se puede utilizar un rectángulo con la etiqueta loop con la cláusula de iteración al lado, tal y como aparece en la Figura 10.3. Esto tiene la ventaja de que queda claro qué
:EvaluadorMovimiento
:JugadorAutomovil
:ConstructorEstrategia obtenerMejorMovimiento
loop
[para cada movimiento legal m]
evaluar(m)
Figura 10.3
Comportamiento iterativo en un diagrama de secuencia.
MÁS SOBRE DIAGRAMAS DE INTERACCIÓN
141
mensajes se van a repetir, lo que es un problema con la notación más sencilla. Probablemente sea una buena idea utilizar esta notación cada vez que haya que repetir más de un mensaje; por ejemplo, hay que evitar utilizar una marca de iteración sencilla en un mensaje si el receptor del mensaje actúa enviando otro mensaje.
10.2
Concurrencia Los diagramas de interacción que se han considerado hasta aquí indican cómo describir el funcionamiento de un caso de uso u operación en función de los mensajes y respuestas que pasan de uno en uno entre los actores y objetos. Se ha asumido que, con cada mensaje, el emisor espera una respuesta antes de continuar con sus propias acciones. Como mucho, uno de nuestros objetos está operando (ejecutando un trabajo útil) en cada momento. Por supuesto, este es el caso normal, ya que muchas aplicaciones están diseñadas para funcionar en una única computadora que tiene un sólo procesador. Los sistemas como estos, a menudo, se llaman procedurales o mono-hilo, porque hay un procedimiento paso a paso con un único hilo de ejecución. En un diagrama de secuencia simple, se puede englobar esto como un hilo único, distribuido a lo largo de las flechas de mensajes, que baja por las activaciones y vuelve en los retornos. ¡Una parte de un hilo será suficiente para cubrir todas las flechas! (Aunque, tenga cuidado: la palabra procedural se utiliza también en otros sentidos). Sin embargo, muchos sistemas de hoy día no son mono-hilo; son concurrentes de alguna manera. Algunos ejemplos de tipos de sistemas concurrentes son: • Sistemas distribuidos, donde el cálculo se realiza de forma simultánea en diferentes procesadores (requiere cada uno, al menos, un hilo de ejecución). • Aplicaciones multi-hilo, en las que varios hilos de ejecución proceden en paralelo, posiblemente planificados en un solo procesador o utilizando varios. • Muchos sistemas reactivos, que obtienen la entrada (datos o eventos) del entorno de varias maneras y tienen que reaccionar, a menudo, en tiempo real. Estas categorías se solapan y no son exhaustivas. La característica común es que varias cosas deben ejecutarse de una sola vez en el sistema que se está construyendo; varios objetos pueden estar operando a la vez, varios mensajes pueden enviarse a la vez. En realidad, los desarrolladores muchas veces tienen que considerar la concurrencia, incluso cuando cada aplicación que diseñan es mono-hilo. Si varias aplicaciones se están ejecutando a la vez en diferentes procesadores (quizá en diferentes máquinas de red), se tienen múltiples flujos de control. Los sistemas externos que se representan como actores a menudo tienen sus propios flujos de control (¡y por supuesto, los actores humanos, siempre los tienen!). La concurrencia sólo se vuelve más obvia cuando el sistema diseñado está formado por varias aplicaciones que interactúan entre sí. Los sistemas concurrentes, a menudo, se confunden con los sistemas de tiempo real, porque los sistemas en tiempo real a menudo son concurrentes y viceversa. Sin embargo, los conceptos son muy diferentes y no siempre se dan a la vez: un sistema mono-hilo puede ser un sistema en tiempo real, y un sistema concurrente puede no serlo.
142
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
P: Dé ejemplos de un sistema mono-hilo que no sea de tiempo real; un sistema concurrente que no sea de tiempo real; un sistema mono-hilo en tiempo real; un sistema concurrente en tiempo real. (Puede necesitar utilizar una definición amplia de sistema). P: (Particularmente relevante si está utilizando Java). ¿Tiene herramientas disponibles para escribir código multi-hilo? Investigue cómo se puede hacer, y escriba pequeños programas de ejemplo. Pregunta de Discusión 70 ¿De qué manera pueden interactuar varias aplicaciones mono-hilo?
Está fuera del alcance de este libro considerar el diseño de aplicaciones concurrentes en profundidad. En el resto de este capítulo se tratará de dar sólo una idea general a las consideraciones que surjan.
10.2.1
Modelización de varios hilos de control
Hay (al menos) tres maneras de iniciar un nuevo hilo de ejecución. 1. El único hilo existente puede dividirse en varios hilos. Es decir, un objeto que está operando (por ejemplo, debido a que acaba de recibir un mensaje) puede enviar dos mensajes concurrentemente. En un diagrama de comunicación se utilizan los números de secuencia de los mensajes para indicar qué mensajes son concurrentes. Recordemos del Capítulo 9 que los números se utilizan para representar mensajes secuenciales en una misma activación, y si hay varias activaciones anidadas —provocadas por mensajes que han sido enviados pero que no han sido contestados todavía— habrá varios números separados por puntos, con la primera activación a la izquierda y la activación más reciente a la derecha. Por ejemplo, el mensaje 2.13.4 se envía desde la activación provocada por el mensaje 2.13, después del mensaje 2.13.3. (En realidad, en un sistema procedural, no se puede enviar el mensaje 2.13.4 hasta que se reciba la respuesta al mensaje 2.13.3 —si hay mensajes 2.13.3.1, etc. todos ellos tienen que haber sido también completados antes de poder continuar con el mensaje 2.13.4). La diferencia, cuando se manejan sistemas concurrentes, es que se pueden utilizar nombres (cadenas de caracteres) en vez de números, para mostrar los mensajes que se han enviado a la vez. Por ejemplo, los mensajes 2.13.A y 2.13.B se han enviado ambos a la vez dentro de la activación provocada por el mensaje 2.13.
P: Dibuje un diagrama de secuencia en el que aparezcan los mensajes con la siguiente secuencia de números (no estarán en este orden, y se necesitarán otros mensajes): 2.B.2, 1, 2.A. Pregunta de Discusión 71 ¿Esta secuencia de numeración es suficiente? ¿Puede poner un ejemplo de una secuencia de mensajes coherente que no pueda ser numerada utilizando este esquema?
2. Un actor, o un objeto activo, puede iniciar un nuevo hilo de ejecución. Es decir, puede enviar un mensaje “por iniciativa propia” sin necesidad de haber recibido primero un
MÁS SOBRE DIAGRAMAS DE INTERACCIÓN
143
mensaje, al tiempo que se está ejecutando un cálculo en alguna parte. En realidad, esta es casi la definición de un objeto activo: un objeto activo es aquél que posee su propio flujo de control. Está claro, los ejemplos canónicos de los objetos activos son objetos que representan procesos o hilos. Un objeto activo se muestra en UML como cualquier otro objeto, pero con un borde más ancho.
P: ¿Su lenguaje de programación soporta objetos activos? ¿Qué clases pueden tener los objetos activos?
La decisión de cómo asignar procesos concurrentes a diferentes procesadores se documenta en los diagramas de despliegue de UML, que se describirá en el Capítulo 13. La decisión probablemente se tome en una de las primeras etapas del proyecto, como parte de la decisión de su arquitectura. 3. Un objeto puede enviar un mensaje asíncrono a otro objeto —es decir, puede hacer que otro objeto empiece a operar sin detener su propio cálculo. Esto es más complicado de describir utilizando una metáfora de hilo —el emisor divide su único hilo en dos, ¡uno lo aísla y se lo entrega al receptor del mensaje! En los dos últimos casos, la forma anidada de numeración puede no ser útil, porque la ejecución no es anidada: los objetos son concurrentes. En su lugar se puede utilizar, la secuencia de numeración sencilla 1, 2,... Si se está modelando un sistema en tiempo real complejo, o un sistema en el que la concurrencia es importante, se podría considerar la utilización de una variante especial (o perfil) de UML diseñada para tales sistemas. Por ejemplo, tal perfil podría definir estereotipos para tipos de mensajes relevantes. La descripción de dicho perfil queda fuera del alcance de este libro, aunque puede encontrar varios enlaces a más información desde la página web del libro. Aunque el UML estándar, proporciona notación para mensajes asíncronos, tal y como se ha dicho anteriormente; véase la Figura 10.4 ADVERTENCIA En la Figura 10.4 se muestra lo que describe UML, pero en realidad, las convenciones difieren tanto que se pueden ver variantes.
Tipo de interacción
Símbolo
Síncrono o llamada
—
La situación procedural “normal”. El emisor pierde el control hasta que el receptor termina de tratar el mensaje, entonces recupera el control, que opcionalmente puede aparecer como una flecha de retorno.
Retorno
<– –
No es un mensaje, sino que es el retorno de un mensaje anterior. Desbloquea un envío síncrono
→
El emisor no pierde el control; envía el mensaje y puede continuar inmediatamente. El receptor del mensaje pasa también a activo, si no lo estaba ya.
Asíncrono
Figura 10.4
Significado
Variantes del envío de un mensaje en un diagrama de secuencia.
144
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
No todos los mensajes en una aplicación tienen que ser del mismo tipo. Algunos objetos pueden interactuar de forma síncrona, es decir, como partes no concurrentes de un único componente, mientras que los mensajes entre otros objetos pueden ser asíncronos. Esto puede suceder, por ejemplo, cuando algunos mensajes se envían a un componente de ejecución independiente, separado. Para nuestro ejemplo se trata otra vez el sistema de registro de estudiantes desarrollado en el Capítulo 15. Supongamos que el caso de uso Registro para módulos (ligeramente más complejo que el resto de los tratados en el capítulo) es de la siguiente manera. Este caso de uso asume que cada DirectorDeEstudiosCS4 (cada director de estudios de un estudiante CS4, como actor: escribimos DdE para acortar) ha pre-aprobado ciertas combinaciones de módulos, posiblemente basándose en cada estudiante. Por ejemplo, las combinaciones más estándares deberían estar pre-aprobadas por todos los estudiantes, y una combinación en particular no estándar debería ser aprobada por un estudiante en particular, después de que el estudiante haya discutido la elección con el DdE. Se podría modelar esto a través de un caso de uso Aprobar combinaciones con el DdE como actor.
Registro para módulos El estudiante visita una página web y elige unos módulos. En la confirmación, el sistema almacena esta elección. No necesariamente de inmediato, el sistema hace una de las tres cosas posibles: 1. Si la combinación es pre-aprobada para este estudiante, envía un correo de confirmación al estudiante y apunta a ese estudiante en los módulos nombrados. 2.
Si la elección no está pre-aprobada para este estudiante, pero es legal según las regulaciones de la universidad, envía un correo al director de estudios del estudiante. Las responsabilidades del sistema terminan aquí: el DdE se pone en contacto con el estudiante, y puede aprobar la elección a través del caso de uso Aprobar combinaciones.
3. Si la elección es ilegal según las regulaciones de la universidad, envía un correo explicando el problema al estudiante.
P: Si está familiarizado con la programación web, piense cómo debería implementarse tal sistema, y escriba una descripción más detallada (¿caso de uso?) dejando claras sus decisiones de alto nivel, por ejemplo, qué partes del sistema están en qué lenguaje y dónde se ejecutan. ¿Cuáles son las ventajas y desventajas de su descripción, comparada con la anterior? ¿Considera apropiado que la descripción de un caso de uso incluya información extra? ¿Por qué?
La Figura 10.5 presenta una posible realización del escenario número 2 de este caso de uso. Se utilizan mensajes asíncronos entre los objetos del sistema y los actores para representar tanto las elecciones de cursos propuestos por el estudiante como el mensaje de correo enviado por el sistema.
P: Desarrolle realizaciones del resto de escenarios de este caso de uso. (Necesitará leer primero el Capítulo 15).
MÁS SOBRE DIAGRAMAS DE INTERACCIÓN
:Estudiante
145
:DirectorDeEstudios
Ada Lovelace : EstudianteCS4
Dr. J. Bloggs : DirectorDeEstudiosCS4
elegirMódulos(m1,...m6) confirmaElección(m1,...,m6,self) correo electrónico
Figura 10.5 Paso de mensajes asíncrono.
Pregunta de Discusión 72 1. ¿Podría ser útil utilizar mensajes asíncronos en los diagramas de interacción, si el sistema final fuese de un único hilo? 2. Tal y como se ha dicho, UML ha reducido el número de tipos de mensajes disponibles en el estándar, partiendo de que se pueden añadir más variantes cuando sea necesario, por ejemplo en una variante de UML que se centre en el desarrollo de sistemas en tiempo real. ¿Qué tipos de mensajes cree que se necesitaría en tales contextos, y por qué?
RESUMEN
Este capítulo ha cubierto dos de los aspectos más avanzados de los diagramas de interacción de UML. Primero, se ha tratado cómo mostrar más de un escenario en un diagrama, permitiendo la representación del paso de mensajes iterativos y condicionales. Después, se han introducido, muy brevemente, algunas características adecuadas para el modelado de sistemas concurrentes.
Capítulo
11 Fundamentos de los diagramas de estado y de actividad
Hasta aquí se ha tratado: • Cómo describir los requisitos de un sistema utilizando casos de uso. • Cómo modelar la estructura estática de un sistema —incluyendo qué clases hay y qué mensajes aceptan los objetos de esas clases— utilizando un modelo de clases. • Cómo modelar la manera en que interactúan los objetos para satisfacer los requisitos —describiendo los mensajes que pasan entre ellos— utilizando diagramas de interacción. Sin embargo, no se ha tratado cómo modelar la “decisión” de un objeto sobre qué hacer cuando recibe un mensaje. Dos diagramas de interacción pueden mostrar objetos de la misma clase recibiendo el mismo mensaje, pero respondiendo de forma distinta. A menudo esto es razonable, porque el comportamiento de un objeto puede verse afectado por los valores de sus atributos. Para implementar, mantener o probar la clase, se necesita comprender cuáles son las dependencias entre el estado de un objeto y su reacción ante los mensajes, u otros eventos. Tal y como se verá en este capítulo, los diagramas de estado de UML (o gráficos de estado, o diagramas de gráficos de estado) almacenan estas dependencias de una manera adecuada. En este capítulo se tratará el uso más común de los diagramas de estado, particularmente, se mostrará cómo un objeto reacciona al recibir un mensaje enviando otros mensajes. Pensando un poco, se podrá utilizar la misma notación para describir actividades complejas. La idea es que moverse de una (sub)actividad a la siguiente, cuando la primera actividad se ha completado, es muy parecido a un objeto que pasa de un estado a otro significativamente diferente cuando recibe un mensaje. Se verá que los diagramas de actividad, que son una variante de los diagramas de estado adaptados para mostrar las conexiones y las dependencias entre actividades, pueden ser una ayuda para comprender las actividades complejas. A veces hay que elegir entre utilizar un diagrama de actividad y utilizar un diagrama de interacción; se abordará la elección.
148
11.1
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
Diagramas de estado Empecemos con un ejemplo muy sencillo en el que un objeto recibe un mensaje y lo que hace depende de los valores de los atributos y enlaces1. En el sistema de la biblioteca, un objeto de la clase Copia puede tener un atributo lógico enEstantería, que almacena si el objeto representa una copia de un libro que está actualmente en la biblioteca, o uno que está en préstamo. La interfaz de la clase Copia especifica que el objeto debería estar dispuesto a aceptar el mensaje tomarPrestado(). Se pretende que informe al objeto Copia que la copia real acaba de ser tomada prestada de la biblioteca. Este mensaje sólo debería llegar cuando el atributo enEstantería es verdadero —si la copia real se está prestando, ¡debe estar en la biblioteca! La reacción del objeto Copia debería ser poner su atributo enEstantería a falso (manteniendo el aspecto real que se supone que tiene que describir), y enviar un mensaje al objeto Libro asociado con él para informarle de que esa copia acaba de ser prestada. ¿Qué pasaría si el mensaje tomarPrestado() llega cuando el atributo enEstantería es falso? Esto significa que algo ha ido realmente mal —el estado del sistema no describe correctamente el estado del mundo real— por lo que no sería apropiado que la reacción del objeto Copia ignorase el problema. En su lugar, debe, de alguna manera, señalar un error, quizá escribiendo un mensaje de error en un archivo de operaciones. El valor del atributo de la copia enEstantería es importante para comprender el comportamiento del objeto, a nivel de qué mensajes envía después de recibir un mensaje. Se pueden nombrar los dos estados significativamente distintos de un objeto Copia, en la estantería y en préstamo, y almacenar los mensajes que hacen que pasen de un estado a otro como eventos que provocan las transiciones entre estados, tal y como se muestra en la Figura 11.1. (No se ha mostrado lo que el objeto debería hacer si recibe un mensaje inesperado; esto se tratará en el epígrafe 11.1.1).
devuelve() en préstamo
en la estantería tomarPrestado()
Figura 11.1 Diagrama de estado de la clase Copia.
El punto negro con una flecha hacia el estado en la estantería es una marca de inicio. Esto significa que cuando se crea un nuevo objeto de la clase Copia, empieza en el estado en la estantería. Las marcas de inicio son opcionales. Es útil mostrarlas cuando los objetos de una clase siempre empiezan en un estado. Por ejemplo, este es el caso si los objetos de una clase siempre se crean con los mismos valores por defecto para sus atributos. Sin embargo, a menudo los objetos se crean con los valores de sus atributos pasados como parte de la orden de creación (por ejemplo, como argumentos de un constructor). En ese caso, el estado inicial del objeto varía dependiendo de cómo se crea, por lo que no debería aparecer ninguna marca de creación. 1
Por “lo que hace” se quiere decir qué mensajes envía, no sólo qué argumentos o valores se retorno elige: se puede decir que su comportamiento depende cualitativamente de los valores de sus atributos y enlaces.
FUNDAMENTOS DE LOS DIAGRAMAS DE ESTADO Y DE ACTIVIDAD
149
P: ¿Es razonable suponer que un objeto Copia siempre se crea en estado en la estantería, es decir, con enEstantería verdadero? ¿Por qué? P: ¿Cómo son los valores iniciales de los atributos de un objeto en su lenguaje de programación? Por ejemplo, ¿puede la definición de clase especificar valores por defecto? Si es así, ¿a pesar de todo puede ignorar los valores por defecto cuando se crea un objeto? Pregunta de Discusión 73 Considere la alternativa de tener un único mensaje, digamos tomarPrestadoODevolver, al cual, como respuesta, un objeto Copia introduce el valor de en la estantería. ¿Cuáles son las consecuencias de tal cambio de diseño?
11.1.1
Mensajes inesperados
En la Figura 11.1 no se han mostrado las flechas para representar la recepción del mensaje tomarPrestado() en el estado en préstamo o el mensaje devolver() en el estado en la estantería. Bajo circunstancias normales, tales mensajes no deberían llegar: si lo hacen, es un error. Sin embargo, la clase define la interfaz que un objeto de la clase Copia tiene que satisfacer, y esta interfaz hace que un objeto Copia acepte los mensajes tomarPrestado() y devolver(). Por lo que el código de la clase Copia tendrá que hacer algo si llegan estos mensajes “erróneos”, como informar del error de alguna manera. La decisión sobre qué pasaría en circunstancias inesperadas como éstas es una decisión de arquitectura que debe tomarse y documentarse una vez, por lo que no hay que almacenar de forma separada lo que pasa con cualquier evento parecido. Una solución común es tener un solo objeto, globalmente accesible de una clase Error, cuya única responsabilidad es informar de los errores. Cualquier objeto que recibe un mensaje inesperado, le envía un mensaje al objeto Error describiendo lo que ha pasado. Se está utilizando la convención de que si un diagrama de estado no muestra cómo se trata un mensaje en un estado en particular, significa que el mensaje nunca debe llegar cuando el objeto está en ese estado. Aquí se presentan mensajes de manejo de error sólo si se requiere algo especial en ese caso particular. ADVERTENCIA Una convención alternativa —autorizada por la especificación de UML— es que un evento, como es la llegada de un mensaje, que no provoca ninguna transición, simplemente se ignora. Esto será importante cuando se traten otros tipos de eventos en el Capítulo 12. La convención aquí utilizada se utiliza normalmente en la práctica, porque es muy conveniente, pero se aplica sólo a la llegada de mensajes que están en la interfaz de un objeto.
Pregunta de Discusión 74 ¿De qué otras maneras se pueden tratar los mensajes inesperados? ¿Cuáles son las ventajas y los inconvenientes de los enfoques que considera?
150
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
11.1.2
Nivel de abstracción
El diagrama de estado para Copia presentaba sólo dos estados, aunque posiblemente haya otros muchos posibles valores diferentes de atributos de un objeto Copia. (Todavía no se han definido completamente los atributos de la clase Copia, pero probablemente tenga algún tipo de número de biblioteca, algún registro de qué libro es copia, y posiblemente más). Esto es debido a que la mayoría de los valores diferentes de los valores de los atributos son equivalentes hasta donde nos concierne en este momento: el comportamiento de un objeto Copia no depende de forma significativa, por ejemplo, del Libro del que es copia. Lo que es significativo puede depender de la parte del sistema en la que se está interesado. Sin embargo, en la implementación final, los valores de los atributos de un objeto determinan en qué estado del diagrama de estado se encuentra. En realidad, el estado del objeto también depende, en principio, de los objetos a los que está enlazado y sus atributos y así sucesivamente... No obstante, los sistemas en los que dos objetos con los mismos valores de atributos pueden estar en estados diferentes de un diagrama de estado tienden a ser difíciles de entender y de mantener. Dados los valores de todos los atributos de un objeto, debería ser capaz de identificar exactamente el estado en el que el objeto debería estar en un diagrama de estado. A veces es útil almacenar el rango de valores de atributos que cubre un estado en el diagrama de estado. Por ejemplo, se podría añadir la restricción {enEstantería=verdadero} al estado en la estantería del diagrama de estado, y {enEstantería=falso} en el estado en préstamo. Una, y sólo una de estas restricciones sería siempre verdadera. Esto es una exageración en nuestro ejemplo, pero es útil cuando una combinación de atributos más compleja determina el estado en un diagrama de estado.
P: ¿Cuál es el menor número de estados que podría haber en un diagrama de estado correcto de la clase Copia? ¿Se podría decir algo del mayor número de estados? Piense en el dibujo de los diagramas de estado con (a) el menor número posible de estados, (b) un número de estados mayor que dos. ¿Está convencido de que el diagrama con dos estados es más útil? Pregunta de Discusión 75 (Después de leer el Capítulo 6). ¿Cuál sería la diferencia si la clase tuviera un invariante de clase?
11.1.3
Estados, transiciones, eventos
La Figura 11.1 muestra los elementos más importantes de un diagrama de estados, a saber: • Estados, que aparecen como cajas con esquinas redondeadas. • Transiciones, entre estados, que aparecen como flechas. • Eventos, que provocan las transiciones entre estados. Hasta aquí sólo se han considerado los tipos más comunes de eventos, particularmente el receptor de un mensaje; esto se muestra simplemente escribiendo el mensaje (incluyendo los nombres de sus argumentos, si tiene alguno) en la flecha de transición. • Marca de inicio, que aparece como un punto negro con una flecha (sin etiquetar) que apunta al estado inicial del diagrama.
FUNDAMENTOS DE LOS DIAGRAMAS DE ESTADO Y DE ACTIVIDAD
151
ADVERTENCIA Técnicamente, en UML2, lo que se muestra en el diagrama no es un evento, sino un disparador. Un disparador especifica un tipo de evento; el evento en sí mismo es la ocurrencia real que provoca que se lance la transición. No se insistirá en esta diferencia, y se continuará utilizando el término “evento” para ambos casos.
No sorprende saber que un diagrama de estados puede mostrar también una marca de fin. Esto es un punto negro con un anillo alrededor, y significa que el objeto ha alcanzado el final de su vida y será destruido. Puede haber varias marcas de fin en un diagrama, o ninguna.
P: ¿Cuál es la diferencia en significado entre un estado con ninguna transición saliente, y uno con una flecha hacia una marca de fin?
Hasta aquí, se ha tratado el tipo sencillo de diagramas de estado, conocido como Máquinas de Estado de Protocolo (MEP). Una MEP puede mostrar cómo el estado de un objeto cambia como respuesta a los mensajes que recibe. No intenta mostrar ninguna de las acciones que el objeto tiene que realizar como consecuencia. A menudo, esta es la manera más útil de utilizar los diagramas de estado en la práctica, pero a veces se quiere mostrar algunas de las acciones del objeto en una máquina de estados. En el siguiente apartado se verá cómo hacer esto.
11.1.4
Acciones
Se ha dicho que los diagramas de estado eran útiles para comprender cómo la reacción de un objeto a un mensaje depende de su estado; por ejemplo, se quiere mostrar los mensajes que envía. Un objeto que envía un mensaje respondiendo a un mensaje que se le ha enviado, es un ejemplo de una acción que es una reacción de un objeto a un evento. Un evento es algo que se le hace a un objeto, como es enviarle un mensaje. Una acción es algo que hace el objeto, como es enviar un mensaje.
Se puede mostrar la acción después del evento de la transición, separando ambos por un símbolo “/”. La Figura 11.2 muestra el objeto Copia enviando los mensajes prestado(self) y devuelto(self) al objeto Libro que está asociado a él, como parte de su reacción ante la recepción de los mensajes tomarPrestado() y devolver(). Analizando la notación: el símbolo “/” indica que lo que viene detrás es una acción libro seguido de un punto identifica al objeto al que se ha enviado el mensaje: se supone que la clase Copia incluye un atributo libro para implementar la asociación entre Copia y Libro que aparece en el diagrama de clases de la Figura 3.5. Por último, devuelto(self) es un ejemplo
devolver()/libro.devuelto(self) en préstamo
en la estantería tomarPrestado()/libro.prestado(self)
Figura 11.2
Diagrama de estados de la clase Copia, con acciones.
152
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
de un mensaje que incluye un parámetro; en este caso, el mensaje devuelto espera un argumento que es un objeto de la clase Copia, para indicar qué copia se acaba de devolver, y en este caso, el objeto Copia se envía a sí mismo (para ser más exactos, en la mayoría de lenguajes, una referencia a sí mismo). Pregunta de Discusión 76 En el Capítulo 5 se dijo que no es útil mostrar los atributos de una clase cuyo único propósito es implementar las asociaciones que aparecen en el diagrama de clases: esto viola la regla ESCRIBA UNA SOLA VEZ, que establece que siempre que sea posible, se debe evitar almacenar información dos veces si esto implica mantener consistentes las versiones (véase Capítulo 2). Aún así es necesaria alguna manera de hacer referencia al objeto Libro asociado con el objeto Copia. ¿Cree que la decisión de inventar un atributo libro para Copia es razonable? ¿Se debería actualizar ahora nuestro modelo de clases para indicar este atributo? ¿Cuáles son las opciones, y cuáles son sus pro y sus contra?
Escribir una acción en una transición es algo adecuado en esta situación. Sin embargo, suponga que se tienen varias maneras diferentes de entrar en el mismo estado (lo que es perfectamente legal, y claro está, muy común) y la misma acción sucedería en el momento en que se entra en el estado, sin importar qué transición está teniendo lugar. Se podría mostrar la acción en cada una de las transiciones, pero esto es muy tedioso y propenso a errores —viola otra vez la regla ESCRIBA UNA SOLA VEZ. En su lugar, se puede mostrar directamente nuestra intención, escribiendo la acción dentro del estado, como reacción al evento especial entrada. Implícitamente hay un evento entrada cada vez que el objeto entra en un estado, aunque no se muestre o se consideren estos eventos, a menos que se les quiera asociar alguna acción. De forma similar, se pueden mostrar las acciones que deberían tener lugar cada vez que se abandona un estado dado asociando la acción con un evento salida en un estado. La Figura 11.3 muestra el uso de un evento entrada; la Figura 11.4 muestra el uso de un evento salida. ¡Ambos diagramas muestran exactamente lo mismo que el de la Figura 11.2! en préstamo entrada/libro.tomarPrestado(self)
Figura 11.3
devolver()
en la estantería
tomarPrestado()
entrada/libro.devuelto(self)
Diagrama de estado de la clase Copia, con acciones de entrada.
en préstamo
devolver()
en la estantería
salida/libro.devuelto(self)
tomarPrestado()
salida/libro.tomarPrestado(self)
Figura 11.4
Diagrama de estados de la clase Copia, con acciones de salida.
Se puede utilizar cualquier combinación de estas acciones.
P: ¿En qué orden cree que se ejecutarán las acciones foo(), bar() y baz() en la Figura 11.5?
FUNDAMENTOS DE LOS DIAGRAMAS DE ESTADO Y DE ACTIVIDAD
algunEvento /bar()
unEstado salida/baz()
153
otroEstado entrada/foo()
Figura 11.5 Varias acciones en un diagrama.
P: Dibuje algunos diagramas de estado más para Copia con el mismo significado que los anteriores. Pregunta de Discusión 77 Aunque las Figuras 11.2, 11.3 y 11.4 significan lo mismo, hay varias razones por las que se podría preferir una. Piense, por ejemplo, en el mantenimiento. ¿Qué piensa?
En realidad, este uso de los eventos entrada y salida dentro de un estado es un caso especial de la notación más general, al que se volverá en el capítulo siguiente.
Haciendo buen uso de las acciones ¿Qué se puede poner exactamente después del símbolo “/” en una transición? Se ha indicado que se puede describir el envío de un único mensaje. ¿Qué pasa con el comportamiento más complicado? Después de todo, si una transición representa a un objeto respondiendo un mensaje utilizando uno de sus métodos, se podría considerar mostrar la implementación del método completo en la transición. UML proporciona un lenguaje de acción que permite arbitrariamente almacenar comportamiento complejo en UML. Sin embargo, cuando se utilizan acciones complejas los diagramas pueden volverse fácilmente ilegibles y confusos. Sin el apoyo de una buena herramienta, mantener los diagramas coordinados con el código es prácticamente imposible. Una vez que uno se acostumbra a almacenar el comportamiento completo del sistema en UML, se estará utilizando de forma eficiente como lenguaje de programación. La opinión de los autores es que UML no es un buen lenguaje de programación. No fue diseñado para que utilizarlo como tal, y hay varios documentos técnicos en su definición que implican que diferentes herramientas pueden interpretar los mismos diagramas UML de manera diferente. En conjunto, normalmente es mejor mantener las acciones en las transiciones de forma sencilla —quizá de forma informal— u omitirlas todas. A veces, una buena alternativa es utilizar post-condiciones en las transiciones de la máquina de estado de protocolo para indicar que debería ser verdadero después de que la transición haya terminado, pero no cómo el sistema debería asegurarse de ello. Esto tiene que ver con el uso de guardas por lo que trataremos esta posibilidad al final del siguiente apartado.
11.1.5
Guardas
A veces, la ocurrencia del mismo evento en el mismo estado puede o no provocar un cambio de estado, dependiendo de los valores exactos de los atributos del objeto. (Es decir, es necesario
154
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
interesarse por más detalles y no sólo por el estado del diagrama de estado en el que se encuentra el objeto). Esto se puede indicar utilizando la misma notación condicional que se utiliza en los diagramas de comunicación (Capítulo 10). Para ilustrar esto, volvamos al ejemplo del diagrama de estado de la clase Libro, que apareció por vez primera en el Capítulo 3. Los objetos Libro tienen un estado ligeramente más interesante que los objetos Copia, porque puede haber muchas copias de cada libro, y un objeto Libro está disponible para prestar siempre que haya al menos una copia en la estantería. Por lo tanto, el mensaje prestado() provoca un cambio del estado está disponible para prestar sólo si es la última copia de la estantería; en otro caso, el objeto Libro permanece en está disponible para prestar. La Figura 11.6 ilustra esto utilizando las dos condiciones (mutuamente exclusivas) [última copia] y [no última copia]. Como en los diagramas de comunicación, una condición puede expresarse en español u otras lenguas, en un lenguaje de programación, en OCL (Object Constraint Language, Lenguaje de Restricción de Objetos, LRO), o en cualquier otra notación que se decida en el proyecto. devuelto()
no está disponible para prestar
devuelto()
está disponible para prestar
prestado()[última copia]
prestado()[no última copia]
Figura 11.6 Diagrama de estados para la clase Libro.
Debido a que la transición de estado inversa, donde se recibe un mensaje devuelto() en el estado no está disponible para prestar, no tiene guarda, un evento como este siempre hará que el Libro cambie al estado disponible para prestar, tal y como se esperaba. Las guardas, a menudo conocidas como precondiciones, pueden utilizarse tanto en las máquinas de estado de protocolo, como en cualquier otra máquina de estado. Normalmente, las guardas en las MSP aparecen antes del nombre del evento. Si es útil se puede indicar también una post-condición después de un símbolo “/”, donde una acción sería una máquina de estado general. Por ejemplo, una transición en una MSP se etiquetaría con [última copia] prestado()/[estantería vacía]. Esto significa que la transición no puede tener lugar a menos que llegue el mensaje prestado() con la precondición [última copia] con valor verdadero; si se da la transición al final de la misma la post-condición [estantería vacía] tiene que ser verdadera. Destacar que no almacena nada sobre cómo el sistema aseguraría que la post-condición se vuelve verdadera. Esta es la diferencia fundamental entre las máquinas de estado de protocolo, con post-condiciones, y las máquinas de estado generales o de comportamiento, con acciones. La Figura 11.6 también ilustra que una transición puede llevarnos desde un estado, de nuevo a él mismo. Se tiene que poder mostrar tales transiciones si se sigue la convención mencionada antes, en la que la ausencia de una transición que muestra la llegada de algunos mensajes
FUNDAMENTOS DE LOS DIAGRAMAS DE ESTADO Y DE ACTIVIDAD
155
indica que es un error que llegue ese mensaje. Si se omiten las transiciones a uno mismo en el ejemplo, según la convención significaría, por ejemplo, ¡que es un error que alguien devuelva la copia de un libro cuando ya hay una copia del mismo libro en la estantería!
P: ¿Por qué no hay guardas en el evento devuelto? Pregunta de Discusión 78 ¿Por qué no se puede simplemente añadir un estado extra para mostrar que queda una copia de un Libro y así evitar utilizar guardas? Si no encuentra ninguna razón, intente dibujar el diagrama de estado que resultaría.
Pregunta de Discusión 79 Suponga que se decide utilizar una máquina de estado de protocolo para modelar el comportamiento de un objeto. De este modo, no se pueden utilizar acciones en las transiciones para modelar la reacción de un objeto a un evento. ¿Qué diagramas de UML, u otras técnicas, serían útiles para modelar esta reacción?
Pregunta de Discusión 80 ¿Cuáles son los pro y los contra de utilizar una máquina de estado de protocolo frente a utilizar máquinas de estado generales?
En el siguiente capítulo se ampliará la notación de los diagramas de estado; por ejemplo, cómo UML puede mostrar diagramas de estado anidados y diagramas en los que tienen lugar varias cosas de forma concurrente. PANEL 11.1
Diseño de clases con diagramas de estado
El diagrama de estado de una clase debería ser lo más sencillo posible. Cuanto más dependa el comportamiento de un objeto de su estado, es más difícil comprenderlo. Las clases con diagramas de estados complejos provocan varios problemas relacionados. Primero, es más difícil escribir correctamente el código para esa clase; las implementaciones de los métodos terminan con muchas condicionales. En segundo lugar, es más difícil probar la clase. Se volverá a este tema en el Capítulo 19: ahora podría pensar cómo probar la clase Libro, y cómo las pruebas requieren una relación con el diagrama de estado. (Merece la pena recordar que a menudo hay que decidir con qué detalle se va a mostrar un diagrama de estado. Por supuesto, mostrar menos detalle para una misma clase no reduce realmente el número de pruebas necesarias, ¡aunque puede reducir el número que se sabe que se va a necesitar!) En tercer lugar, y lo más importante, es mucho más difícil que el código externo utilice una clase correctamente si el comportamiento de la clase depende de su estado de una manera compleja. Por ejemplo, el cliente tiene que asegurarse de que no se envía ningún
156
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
mensaje a un objeto que está en un estado en el que ese mensaje provoca un error. Cualquier cliente externo tiene que hacer un seguimiento de alguna manera de lo que se ha hecho a un objeto, de manera que pueda averiguar en qué estado estará el objeto, o tiene que preguntarle al objeto sobre su estado antes de enviarle el mensaje. En el primer caso, para estar seguro de que todo el sistema es correcto hay que estar convencido de que el cliente siempre acierta en sus presunciones sobre el estado del objeto. El segundo caso, puede ser más seguro, pero también tiene como resultado el envío de mensajes extra, lo que aumenta las cosas que hay que entender y probar, y también lleva tiempo.
P: ¿Cuál es la relación entre el estado de un objeto Libro y los estados de los objetos Copia asociados?
De manera que si se tiene una clase con muchos estados, merece la pena plantearse si hay diseños mejores. A veces puede ser útil dividir una única clase con muchos estados en dos o más clases con un comportamiento más sencillo. Por ejemplo, en vez de una única clase Copia con dos estados, se podría pensar en tener dos clases, por ejemplo CopiaEnEstantería y CopiaEnPréstamo. Entonces, en vez de tener cambios de estado se tendría la creación y borrado de objetos de estas nuevas clases. Esto es una mejora dependiendo de las circunstancias, y en particular, de si las clases más pequeñas se muestran como clases con entidad propia o no. Si es natural pensar en el cambio de estado como en la conversión de un objeto de una de las nuevas clases candidatas en un objeto de otra nueva clase candidata, la división es probable que nos lleve a un buen diseño. Si esto no se ve natural, probablemente será menos útil. Pregunta de Discusión 81 Piense en detalle los cambios que serían necesarios si se divide la clase Copia tal y como se ha descrito arriba. ¿Cree que esto sería una mejora, o no?
Pregunta de Discusión 82 Otro factor importante en la toma de este tipo de decisiones es la frecuencia con que se va a necesitar el cambio de estado o de clase. ¿Por qué es esto importante?
Pregunta de Discusión 83 Piense en el Sistema de Administración CS4 descrito en el Capítulo 15. En vez de las dos clases ProfesorAdjunto y DirectorDeEstudios, se podría tener una única clase ProfesorAdjunto con un atributo esDirectorDeEstudios. Piense qué diferencia habría, y si cree que sería una mejora.
Pregunta de Discusión 84 Mucha gente (por ejemplo, Clemens Szyperski en [44]), dice que un componente no debería tener ningún estado persistente, por lo que, en particular, su diagrama de estado debería tener un único estado. ¿Por qué cree que dicen esto?
FUNDAMENTOS DE LOS DIAGRAMAS DE ESTADO Y DE ACTIVIDAD
11.2
157
Diagramas de actividad Los diagramas de actividad describen cómo se coordinan las actividades. Por ejemplo, un diagrama de actividad puede utilizarse (al igual que un diagrama de interacción) para indicar cómo podría implementarse una operación. Un diagrama de actividad es particularmente útil cuando se sabe que una operación tiene que alcanzar un número de cosas distintas, y se quiere modelar las dependencias fundamentales entre ellas, antes de decidir en qué orden se van a hacer. Los diagramas de actividad son mucho mejores que los diagramas de interacción para mostrar esto con mayor claridad. Los diagramas de actividad son también útiles para describir el desarrollo de los casos de uso individuales y que puede depender de otros casos de uso. Muchas veces, los casos de uso encontrados no suceden en un orden arbitrario, sino como parte del flujo de trabajo general de un área de las actividades de un cliente. Por ejemplo, a veces la actualización de datos como parte de un caso de uso tiene que terminarse antes de que otro caso de uso, que lee esos datos, pueda empezar. Los dos pueden representar tareas separadas, de manera que es lógico que sean casos de uso separados, pero no independientes. En ambos casos, los diagramas de actividad almacenan las dependencias entre actividades, así como qué cosas pueden darse en paralelo y qué tiene que estar terminado antes de que otra cosa pueda empezar. Tal y como esto sugiere, el bloque fundamental de un diagrama de actividad es una actividad, y un cambio de actividad normalmente significa que la actividad anterior se ha completado. ANOTACIÓN TÉCNICA DE UML En UML1.x los diagramas de actividad eran, a nivel formal, diagramas de estado ampliados por conveniencia con alguna notación extra. En UML2 se describen de forma independiente. Sin embargo, los diagramas de actividad y los diagramas de estado todavía tienen suficientes cosas en común como para considerarlos relacionados.
Destacar que, aunque los diagramas de actividad pueden ser útiles para modelar el flujo de trabajo, lo son mucho más para modelar el negocio. Hay algunas ampliaciones de UML para el modelado del negocio, pero UML ha sido criticado por ser débil en esta área. Los elementos de los diagramas de actividad se describen a continuación. • Una actividad aparece como una caja con nombre, y esquinas redondeadas. Es decir, la notación para una actividad es la misma que para un estado. Se puede pensar en una actividad como en un tipo de estado que se abandona, no como respuesta a algún evento que llega de fuera, sino cuando la actividad que representa termina. La actividad puede implicar varios pasos, incluyendo la espera a la llegada de eventos, aunque esta actividad detallada normalmente no se muestra. • Una transición de actividad aparece como una flecha, al igual que una transición en el diagrama de estado. A diferencia de las transiciones, sin embargo, las transiciones de actividad no se etiquetan con eventos o acciones. (No es necesario etiquetarlas con eventos porque se lanzan cuando la actividad anterior ha terminado, no se debe a un evento externo; y no es necesario etiquetarlas con acciones porque las acciones es mejor incluirlas en
158
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
las actividades). Sin embargo, una transición de actividad puede etiquetarse con una condición de guarda, si la siguiente actividad depende de la situación. • Una barra de sincronización es una barra gruesa horizontal que describe la coordinación de actividades. Una vez que todas las actividades que tienen transiciones de actividad apuntando a la barra han terminado, pueden pasar la barra. En ese punto, todas las transiciones de actividad dirigidas a la barra se activan, por lo que las actividades a las que se dirigen estas transiciones empiezan en paralelo. Es decir, la barra de sincronización proporciona una manera de expresar cosas como esperar a que terminen todas las subtareas antes de continuar (unión), y empezar varias tareas en paralelo (división). • Un diamante de decisión se utiliza para representar las decisiones, como alternativa a las guardas de transiciones separadas que abandonan el mismo estado. • Marcas de inicio y fin se utilizan como en el diagrama de estado. Las principales diferencias entre los diagramas de actividad y los diagramas de estado, aparte de la notación extra que se acaba de describir, son que: • las transiciones de actividad no se etiquetan con eventos o actividades, como se ha tratado más arriba. • la actividad se pretende que continúe, siguiendo el flujo descrito por el diagrama, sin quedarse parado. Así, por ejemplo, si hay guardas en las transiciones salientes de una actividad, normalmente sólo se cumple una de ellas. Esta no es una norma universal: a veces puedes querer un último paso de la actividad en algunas circunstancias, pero no en otras, en cuyo caso sería correcto tener guardas no exhaustivas, es decir, para que a veces se dé el caso en que no se cumple ninguna guarda. La Figura 11.7 muestra el flujo de trabajo de la biblioteca del Capítulo 3 como diagrama de actividad. Esto no describe cómo funciona el sistema de información de la biblioteca: describe la interacción humana dentro de la cual el sistema tiene que encajar. Comprender este tipo de contexto de negocio debería ayudar a desarrollar un sistema utilizable y originalmente útil; a veces puede ser útil el desarrollo de diagramas como este para clarificar su comprensión. Se utiliza la barra de sincronización para mostrar el comienzo y el final de una actividad concurrente, donde el bibliotecario presenta una petición de préstamo al sistema y devuelve el libro a la estantería2. Se ve la mezcla de las actividades de un cliente con aquellas del bibliotecario y el final de esta fase cuando el cliente termina y el bibliotecario vuelve a servir al cliente siguiente. El diagrama también ilustra la utilización de decisiones para mostrar la bifurcación del comportamiento.
P: Vuelva a dibujar la Figura 11.7 para mostrar una actividad en la que el bibliotecario pega la fecha en un libro prestado. ¿Debería esto ser concurrente con alguna actividad existente?
P: Vuelva a dibujar la Figura 11.7 para mostrar las actividades detalladas dentro del propio sistema de la biblioteca. 2 Realmente, este es un uso amplio de “concurrencia”, pero que es común en UML. Probablemente el bibliotecario no pueda hacer estas dos actividades a la vez: lo que se quiere decir es que no importa en qué orden se realizan.
FUNDAMENTOS DE LOS DIAGRAMAS DE ESTADO Y DE ACTIVIDAD
socio [prestatario]
159
bibliotecario
encontrar libro en estantería
[retomador]
esperar en cola [devolver] [tomar prestado]
almacenar devolución
poner libro de vuelta a la estantería
almacenar préstamo
preparar para el siguiente socio
Figura 11.7
Diagrama de actividad de la biblioteca a nivel de negocio.
Particiones y calles Un diagrama de actividad puede contener varios grupos de actividades relacionados. Pueden estar relacionados de acuerdo a los objetos y los actores que los ejecutan, a los casos de uso de los que forman parte, o cualquier otro concepto que sea útil. Esto se puede ilustrar dividiendo el diagrama en particiones, conocidas como calles, que agrupan las actividades de acuerdo con esto. La Figura 11.7 ha dividido la biblioteca en acciones ejecutadas únicamente por socios y las que implican al bibliotecario.
P: Vuelva a dibujar la Figura 11.7 para mostrar las actividades divididas de acuerdo con los casos de uso del Capítulo 3.
160
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
P: Dibuje un diagrama de actividad para representar la operación tomarPrestado en un objeto SocioBiblioteca (cuyo diagrama de secuencia se describe en la Figura 3.6). Muestre los sucesos en secuencia sólo cuando haya que hacerlo.
UML2 contiene mucha más notación que se puede utilizar en los diagramas de actividad de los que se han tratan en este libro. Por ejemplo, es posible almacenar cómo las actividades transforman los objetos, cómo se reciben las señales y se manejan las excepciones. ¡Incluso se puede incluir fragmentos del diagrama de secuencia en el diagrama de actividad para obtener lo que se conoce como vista general del diagrama de interacción! Sin embargo, la notación sencilla tratada aquí normalmente es suficiente y, como siempre, el desarrollo de diagramas demasiado complejos tiene tantos riesgos como beneficios. Para más información, puede consultar la definición de UML [48].
RESUMEN
Se ha descrito cómo utilizar los diagramas de estado para modelar la manera en que los cambios significativos de los atributos de un objeto afectan a la forma en que reacciona ante los eventos, tales como los mensajes. Se ha destacado que, aunque un objeto tenga un comportamiento tan complejo que pueda ser útil mostrarlo en un diagrama de estado, es mejor evitar, cuando sea posible, el diseño de clases con un comportamiento basado en estados complejo. También se han tratado los diagramas de actividad, que modelan las dependencias entre actividades, como son las operaciones implicadas en la realización de un caso de uso, o los casos de uso de un sistema.
Capítulo
12 Más sobre los diagramas de estado
Este capítulo trata algunas características menos usuales, pero a veces útiles, de los diagramas de estado y de actividad en UML. Se tratará: • Cómo mostrar eventos y acciones, aparte del paso de mensajes, en los diagramas de estado. • Estados compuestos, donde un único estado de un diagrama de estado tiene una estructura adecuada que se quiere mostrar. • Estados compuestos concurrentes, donde varias máquinas de estado se ejecutan independientemente —esto es útil para modelar sistemas concurrentes.
12.1
Otros tipos de eventos UML clasifica los eventos que pueden provocar una transición de estado de la siguiente manera: • Evento de llamada: el receptor de un mensaje que solicita que se ejecute una operación. Este es el caso más común, considerado en el Capítulo 11. Tal y como se ha visto, el evento incluye los parámetros en el mensaje, así como su selector. • Evento de cambio: ocurre cuando una condición cambia de falso a verdadero. Un evento de cambio se escribe con la palabra clave cuando seguida de una expresión que describe la condición, escrita entre paréntesis; por ejemplo cuando (x=10). Un evento de cambio es útil para describir la situación en la que un objeto cambia de estado porque modifica el valor de sus atributos después de recibir una respuesta al mensaje que envió, en vez de como resultado inmediato del objeto que recibe un mensaje. Hay un ejemplo de esto en el Capítulo 16, donde un objeto PosiciónActual envía un mensaje a un objeto Juego, cuya respuesta será utilizada como el nuevo valor del atributo Mover de PosiciónActual, que indica de qué jugador es el turno siguiente. Dependiendo de este
162
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
nuevo valor, PosiciónActual puede o no provocar una transición de estado. Esto se modela con el evento cuando(Mover = A). • Evento de señal: la recepción de una señal. Al igual que un evento de llamada, un evento de señal puede tener parámetros entre paréntesis. Se tiene que definir una señal como un tipo especial de clase con la palabra clave <<señal>> delante de su nombre, sin operaciones y con los parámetros de las señales en el compartimento de atributos. Las señales pueden estar relacionadas unas con otras por medio de la generalización, pero no pueden estar relacionadas con clases normales. • Evento de tiempo: en general, una expresión que denota el tiempo que tiene que pasar después de un evento con nombre pero que normalmente se utiliza, por medio de la palabra clave después, indicando el tiempo en el que se entró en el estado actual, por ejemplo después (0.5 segundos). Para mostrar que algo ha tenido lugar en un tiempo absoluto concreto, se utiliza la palabra clave a, por ejemplo a(2am). Pregunta de Discusión 85 ¿Cómo de absoluto cree que un evento de tiempo absoluto debería ser? El ejemplo a(2am), ¿es razonable, o debería ser a(2am, 15 Mayo, 2006)? ¿Cómo depende esto de sus suposiciones sobre el tipo de sistema con el que está trabajando?
12.2
Otros tipos de acciones Se ha visto, en el Capítulo 11, que los diagramas de estado pueden mostrar los mensajes que un objeto envía en reacción a un evento. También se mencionó que UML permite un concepto mucho más general de acciones, pero que es peligroso utilizarlo. En breve se dará un ejemplo de una acción ligeramente más compleja. La Figura 12.1 muestra un posible diagrama de estado para la clase Promedio del sistema de simulación descrito en el Capítulo 17. Un objeto Promedio espera mensajes de actualización desde los objetos de simulación en un modelo. Dicho mensaje envía un nuevo valor, que tiene que añadirse al total actual almacenado en el atributo suma. El número de actualizaciones recibidas hasta aquí, almacenado en el atributo observaciones, también tiene que incrementarse. Hay varios puntos que destacar.
realizarInforme() / imprimirResumen() / suma:= 0/observaciones:= 0
entrada / iniciarTiempo := ahora() actualizar(val : Real) / suma := sum + val/observaciones++
Figura 12.1
Diagrama de estado para la clase Promedio: ¡no es un buen estilo!
MÁS SOBRE LOS DIAGRAMAS DE ESTADO
163
1. Como es normal, UML no fija una norma para la sintaxis de las acciones; puede ser adecuado escribirlas en español u otra lengua, pseudocódigo o en el lenguaje de programación final. Por supuesto, no tienen que hacer referencia a nada que el objeto no conozca. Pueden hacer referencia a los atributos, operaciones y enlaces del objeto, y a cualquier parámetro del mensaje que activó la transición. 2. Una secuencia de acciones se presenta separando las acciones con el símbolo ”/” o con punto y coma; UML no especifica qué sintaxis utilizar. Las acciones se ejecutan de izquierda a derecha (aunque en este caso esto no importa). 3. Se ha mostrado actualizar(val:real) como un evento interno, escribiéndolo dentro de la caja (como se hizo con los eventos especiales entrada y salida, en el Capítulo 11). La diferencia entre esto y la auto-transición, que aparece como una flecha que sale de un estado y que apunta a sí mismo, es que cuando se escribe el evento dentro del estado, los eventos de entrada y salida del estado no se activan. La idea es que mostrar una flecha desde un estado a sí mismo se corresponde, primero, con el objeto abandonando el estado (por lo que tiene lugar el evento de salida) y, después, vuelve a entrar en el mismo estado (por lo que tiene lugar el evento de entrada). Mostrar el evento dentro del estado evita que se den los eventos de entrada y salida. 4.
De manera ocasional, los diagramas de un solo estado como este son una forma conveniente de mostrar cómo el objeto reacciona ante un evento. Incluso el compartimento del nombre de un estado es opcional: ya que hay sólo un estado aquí no se ha nombrado.
P: Represente la misma información en un diagrama diseñado para aclarar, ¡en vez de para utilizar tantos elementos de UML como sea posible! Pregunta de Discusión 86 Vuelva a pensar en la clase Libro del ejemplo de la biblioteca. ¿Cómo podría mostrar más información? ¿Cree que esto sería útil?
P: Dibuje un diagrama de estado para una Copia en la biblioteca donde, si no se ha devuelto en tres semanas, se marca como retrasado. (Se necesitará realizar los cambios apropiados a las definiciones de la clase).
12.3
Análisis de los estados Hasta aquí se han tratado los estados como entidades únicas, sin estructura interna. Raramente es útil considerar un estado que contenga un comportamiento interno que pueda ser representado en un diagrama de estado. A tales estados se les llama estados compuestos. Estrictamente hablando, se han considerado ya dichos estados compuestos, cuando se vio la manera en que se tratan los eventos internos. Un evento interno y su secuencia de acciones resultante, implícitamente, es una única transición dentro de una máquina de estados interna (anidada).
164
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
ANOTACIÓN TÉCNICA DE UML UML2 realmente proporciona varios mecanismos sutilmente diferentes para anidar una máquina de estados dentro de otra: estados de la submáquina, estados compuestos, y hacer diagramas de estado para modelar las actividades. Debido a que las diferencias sólo son importantes cuando se utilizan características no abordadas en este libro, no se va a entrar en más detalles.
La Figura 12.2 muestra el diagrama de estado de alto nivel de un Cliente, que se modela como una EntidadActiva en el modelo de simulación de una cola sencilla para la espera de ser servido, utilizando el paquete de simulación de eventos discreto que se describe en el Capítulo 17. El objeto Cliente puede estar activo o en la lista de eventos, esperando su próxima oportunidad para activarse. La línea ejecutar/detalleActivo en el estado activo indica que el comportamiento detalleActivo debería llevarse a cabo mientras el objeto Cliente está en estado activo. El comportamiento detalleActivo podría describirse de varias maneras. Una opción es desarrollar un diagrama de estado aparte para describir dicho comportamiento. Entonces activo es un estado compuesto: hay un diagrama de estado detallado llamado detalleActivo anidado en el estado activo. La Figura 12.3 muestra este diagrama de estado anidado. Las marcas de inicio y de fin son obligatorias para los estados compuestos, aunque son opcionales para los estados simples. La razón es que se necesita saber dónde empieza la máquina de estados interna, y cuándo termina. Cuando una transición alcanza un estado compuesto, se entra en su estado de inicio. Cuando su diagrama de estado interno alcanza su estado final, hay un evento de “finalización” implícito. Esto no tiene un nombre, por lo que las transiciones desde un estado con una máquina de estados anidada pueden no estar etiquetadas, como las transiciones desde las actividades en los diagramas de actividad. Al igual que en los diagramas de actividad, puede haber varias transiciones, cada una de ellas con una guarda; como mucho una de las guardas debería ser verdadera. La Figura 12.3 muestra que el Cliente entra en uno de los tres estados dependiendo del valor actual del atributo eventoSiguiente. Estos subestados representan: 1. La espera a unirse a la cola. 3. La espera en la cola para ser servido. 4. La espera a que termine el servicio una vez que se ha alcanzado el servidor. En todos los casos, esto simplemente significa establecer un valor para tiempoEv indicando la duración de la actividad, y establecer un valor apropiado para eventoSiguiente.
cuando(TiempoEv=ahora()) en lista de eventos
activo hacer/detalleActivo [eventoSiguiente=Finalizado]
[no eventoSiguiente=Finalizado]
Figura 12.2
Diagrama de estado para la clase Cliente.
MÁS SOBRE LOS DIAGRAMAS DE ESTADO
[eventoSiguiente=1]
165
entrada/seguimiento("Inicio") salida/tiempoEv:=etiempoEv+2; eventoSiguiente :=2;
[eventoSiguiente=2] entrada/seguimiento("trabajo") salida/tiempoEv:=tiempoEv+2; eventoSiguiente :=3; [eventoSiguiente=3]
Figura 12.3
entrada/seguimiento("Fin") salida/eventoSiguiente : =Finalizado;
Diagrama de estado anidado detalleActivo para el estado activo del Cliente.
Pregunta de Discusión 87 En realidad, la Figura 12.3 muestra un diagrama de estado algo degenerado. ¿Por qué? ¿De qué otra manera se podría modelar este comportamiento?
12.4
Concurrencia dentro de los estados En el Capítulo 11 se vio que los diagramas de actividad pueden representar varias actividades que suceden a la vez, utilizando las barras de sincronización para expresar la subdivisión y la unión de subtareas. Los diagramas de estado utilizan una notación diferente, pero también pueden mostrar la división y la mezcla de submáquinas concurrentes. Esto se consigue dibujando un estado anidado donde el comportamiento interno del estado está formado por regiones que se ejecutan independientemente. Las regiones se separan con líneas punteadas, como en la Figura 12.4. Cada región tiene su propio estado inicial y estado final.
Figura 12.4 Diagrama de estado con concurrencia.
166
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
P: En nuestro ejemplo no hay transiciones desde un estado en una región concurrente hacia un estado en otra región. ¿Cree que tal transición sería razonable en algún caso? Si es así, desarrolle un ejemplo y explique lo que significa.
RESUMEN
En este capítulo se han tratado algunas características más de los diagramas de estado. Se han considerado eventos y acciones más generales que los ejemplos de paso de mensajes del Capítulo 11, y se ha continuado con el tratamiento de los diagramas de estado con estados compuestos, con o sin concurrencia.
Capítulo
13 Diagramas de arquitectura e implementación
La mayoría de las discusiones en este libro se han centrado en cómo analizar los problemas y diseñar las soluciones utilizando técnicas de orientación a objetos. Se han tocado temas de pequeña escala sobre cómo el diseño se transforma en código, pero no se ha tratado la arquitectura general de la aplicación que hay que desarrollar, aunque se dijo en la Parte I que tales decisiones eran importantes. En este capítulo se pone remedio a esta deficiencia. Ya se ha visto cómo utilizar los diagramas de clases, que son un tipo de diagramas de estructura, para mostrar la estructura estática del sistema. A menudo las clases se describen con demasiado detalle para dar una visión general del sistema: al final, es imposible ver la madera del árbol. Un diagrama de estructura de nivel superior, que muestre componentes lógicos y sus relaciones, puede ser útil. Este es uno de los aspectos de la arquitectura del sistema. El sistema implementado tiene que ejecutarse sobre hardware, cumpliendo requisitos no funcionales, tales como el rendimiento y la escalabilidad. Un diagrama de despliegue muestra la estructura de un sistema en tiempo de ejecución: qué implementaciones de componentes se ejecutan en qué procesador y cómo se configura el hardware para proporcionar los recursos necesarios.
13.1
Diagramas de estructura de componentes ADVERTENCIA Tal y como se destacó en el Capítulo 1, la palabra “componente” tiene muchos significados. UML2 utiliza este término de manera muy dieferente a UML1.x. En líneas generales, los “componentes” de UML2 son los “subsistemas” de UML1.x, y los “artefactos” de UML2 son los “componentes” de UML1.x. El Panel 18.1 aborda las diferentes definiciones de “componente”, pero el punto principal es que hay que saber qué definición está utilizando una persona en un momento dado.
168
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
MotorJuego <>
InterfazJugador
Figura 13.1
Un diagrama de componentes que muestra las dependencias.
Para nuestro propósito, un componente es una parte lógica de un sistema, normalmente mayor que una clase, que puede (en principio) ser sustituida, reutilizada o vendida de forma independiente. Los componentes aparecen como rectángulos con el símbolo del componente en la esquina superior derecha, tal y como aparece en la Figura 13.1. ANOTACIÓN TÉCNICA DE UML En UML1.x, el pequeño símbolo que aparece dentro de un componente era el símbolo utilizado para representar componentes. Esta notación resultó ser inadecuada en la práctica, por lo que ha sido excluído. Recordar que, aunque normalmente se piensa en el rectángulo como en el símbolo de una clase, realmente es el símbolo de cualquier clasificador. Un componente no es una clase, pero es un clasificador. El pequeño símbolo de la esquina es lo que le indica al lector que ese clasificador en concreto es un componente.
Un componente, al igual que una clase, puede realizar una interfaz; por ejemplo, puede proporcionar ciertas operaciones (véase la Sección 6.2.1). Se podría indicar que un componente realiza una interfaz añadiendo un pequeño círculo etiquetado al símbolo del componente. Los componentes pueden depender unos de otros; las dependencias entre componentes se representan utilizando flechas de dependencia punteadas, como las utilizadas entre clases en el Capítulo 6. En realidad, los componentes generalmente dependen sólo de las interfaces de otro: por lo general no deberían tener que conocer la estructura interna del otro componente, por ejemplo. (Este, después de todo, es el propósito de la definición de interfaces). Para representar esto, una flecha de dependencia puede apuntar al círculo de la interfaz, en vez de al cuerpo del componente. Con frecuencia se utiliza la notación “de la pelota y el enchufe” para mostrar las conexiones entre componentes; véase el Capítulo 6. El ejemplo en la Figura 13.1 ilustra las dependencias entre componentes. Utiliza el ejemplo de un paquete de juegos sencillos sacado del Capítulo 16. Aquí se supone que el juego se ha implementado como una aplicación cliente-servidor, donde el servidor es MotorJuego (que
DIAGRAMAS DE ARQUITECTURA E IMPLEMENTACIÓN
169
incluye todas las clases de las que se habla en el Capítulo 16) y el cliente es la interfaz del jugador (la aplicación de interfaz de usuario, que no se ha tratado). Aparece un estereotipo definido por el usuario en la dependencia.
P: ¿Qué mecanismos hay en su lenguaje de programación que permitan que un componente ejecutable pueda utilizar los servicios proporcionados por otro componente?
13.2
Modelo de despliegue El diagrama de despliegue muestra: • Los enlaces de comunicación física entre elementos hardware (máquinas y otros recursos, tales como impresoras). • Las relaciones entre máquinas físicas y procesos – qué se ejecuta dónde.
13.2.1
La capa física
Se empieza considerando el sistema físico, que está formado por nodos con asociaciones entre ellos. Un nodo generalmente es un procesador, capaz de ejecutar componentes software. Sin embargo, a veces es útil utilizar nodos para modelar dispositivos mucho más sencillos, como son las impresoras. Como siempre, se pueden definir estereotipos para distinguir entre tipos de nodos si se considera útil hacerlo (véase el Panel 6.2). Los nodos, que representan cosas físicas individuales, tienen tipos de nodo. Por ejemplo, la Figura 13.2 muestra que el nodo shillay es de tipo EstaciónDeTrabajo.
shillay : Estación de trabajo
Figura 13.2
<>
craro : PC éter C
Un diagrama de despliegue sin el software.
Pregunta de Discusión 88 ¿Importa si, para describir un tipo de nodo, se utiliza un estereotipo o un tipo de nodo?
Las líneas continuas entre los nodos representan las conexiones físicas entre las máquinas. (Estrictamente hablando éstas son asociaciones, como las de los diagramas de clases, pero las
170
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
asociaciones entre nodos tienen que ser conexiones físicas). Éstas pueden representar cables, redes de área local, módems y líneas de teléfono o cualquier cosa. Un enlace puede tener un estereotipo, de manera que queda claro en el diagrama qué tipo de enlace se tiene. Pueden darse más detalles como propiedades dentro de una especificación textual del enlace, o nodo. Esto podría incluir una figura para la potencia de un procesador o un ancho de banda para un enlace. Tales detalles muchas veces son tan importantes como la topología.
13.2.2
Despliegue del software sobre el hardware
Los componentes son conceptos de diseño. La relación entre un componente en el diseño y los ficheros reales, ejecutables, scripts, tablas de bases de datos, etc., en el sistema final puede ser compleja. UML2 proporciona un nuevo concepto, el artefacto, para representar las entidades más concretas como los ejecutables, que realmente se despliegan en el hardware. Cuando se indica qué artefactos se despliegan en qué hardware, tal y como se indica en la Figura 13.3, se está mostrando cómo va a correr el sistema en tiempo de ejecución. Se muestra un artefacto dentro de un nodo para representar que ese artefacto se ejecuta en dicho nodo.
craro : PC
shillay : Estación de trabajo <> éter C <<artefacto>> OXO:MotorJuego
<<artefacto>>
P2:InterfazJugador
<<artefacto>>
P1:InterfazJugador
Figura 13.3 Un diagrama de despliegue con el software.
A menudo un artefacto será la implementación de un componente, lo que difumina la diferencia entre componente y artefacto. Nuestro ejemplo, sitúa dos componentes InterfazJugador y un componente MotorJuego en las dos máquinas que aparecen en el diagrama anterior. El MotorJuego y una InterfazJugador se ejecutan en la estación de trabajo, mientras que la otra InterfazJugador se ejecuta en el PC. Naturalmente, si dos artefactos se sitúan en diferentes nodos pero implementan componentes que tienen una dependencia en tiempo de ejecución entre ellos, ¡tiene que haber un enlace físico entre los nodos! PANEL 13.1
El Modelo de Despliegue en el Proyecto
Aunque se ha dejado los modelos de implementación para más tarde, las decisiones sobre la estructura del sistema a este nivel, normalmente se toman pronto en el proyecto.
DIAGRAMAS DE ARQUITECTURA E IMPLEMENTACIÓN
171
1. Su cliente puede tener hardware existente que tiene que utilizar el sistema, o puede que tenga hacer un desarrollo para un segmento del mercado en particular. Su sistema puede que se tenga que comunicar con sistemas existentes, lo que puede restringir las decisiones. 2. Los requisitos no funcionales del sistema pueden determinar o influir en las decisiones sobre el hardware, y software de bajo nivel, como son los sistemas operativos. Por ejemplo, un sistema de tiempo real, normalmente tendrá que ejecutarse en un sistema operativo en tiempo real especial; un sistema que proporciona acceso a datos de misión crítica necesitaría ejecutarse en un hardware con tolerancia a fallos con una base de datos duplicada. Las prestaciones del sistema se verán fuertemente afectadas por el despliegue, y se deberá tener en mente las limitaciones de la topología elegida durante el desarrollo. Por ejemplo, en una aplicación cliente-servidor las decisiones sobre cómo diseñar las comunicaciones entre cliente y servidor se verán afectadas por el ancho de banda disponible. 3. Las decisiones sobre hardware y sistemas operativos están interrelacionados con las decisiones de los lenguajes de programación, bibliotecas de componentes, etc. — ¡se tiene que poder compilar el código para el entorno elegido! 4. En un proyecto corto, especialmente en uno que utilice hardware especializado, puede que se necesite solicitar pronto el hardware, para no tener retraso en la entrega del sistema por esperar por él.
RESUMEN
Se han tratado los dos tipos de diagramas de implementación de UML, los diagramas de estructura de componentes y los diagramas de despliegue, y cómo utilizarlos. Los diagramas de componentes expresan la estructura del sistema implementado, ayudando a hacer un seguimiento de las dependencias para facilitar el mantenimiento, y almacenar la reutilización de componentes. Los diagramas de despliegue muestran cómo se despliega un sistema sobre una configuración de hardware particular.
Capítulo
14 Paquetes y modelos
Recuerde que un elemento del modelo es el término general de UML para, más o menos, cualquier cosa que pueda representarse por un elemento del diagrama. Por ejemplo, clases, casos de uso, actores, asociaciones, generalizaciones, operaciones, paquetes, métodos, etc., son todos elementos del modelo.
14.1
Paquetes Un paquete es una colección de elementos del modelo. Normalmente, los elementos del modelo que abarcan una parte coherente del sistema, como es un componente, se agrupan en un paquete. Los paquetes son elementos del modelo en sí mismos, por lo que un paquete puede contener otros paquetes. Sin embargo, cada elemento del modelo pertenece directamente a un único paquete, de manera que los paquetes en un sistema deben constituir una jerarquía sensata: los elementos básicos, como las clases, se encuentran contenidas en paquetes, que a su vez se encuentran contenidos en otros paquetes, y así sucesivamente hasta que se alcanza el nivel más alto donde generalmente hay un único paquete de alto nivel. Esta vista jerárquica de un sistema puede ser particularmente útil en sistemas grandes. Un paquete se representa en un diagrama (en cualquier tipo de diagrama) con un rectángulo con una pestaña en su extremo de arriba. Los elementos contenidos en un paquete pueden dibujarse dentro del símbolo del paquete, pero no es necesario. Generalmente no se hace así, ya que la cantidad de detalle que requiere mostrar todo el contenido de un paquete normalmente lo hace inadecuado: ciertamente, el principal propósito de utilizar un paquete, normalmente es para ocultar dicho detalle. En su lugar, el interior de un paquete a menudo se dibuja en un diagrama aparte. En una herramienta CASE podría haber un hipervínculo de este diagrama al icono del paquete. Es conveniente escribir el nombre del paquete en la pestaña si se muestra su contenido, y, en caso contrario, se pondría en el cuerpo del símbolo del paquete. La Figura 14.1 ilustra toda esta notación. Como se puede observar, la jerarquía de paquetes puede aparecer incluyendo un símbolo de paquete en otro. Como alternativa, se puede utilizar una estructura en árbol; véase la Figura 14.2.
174
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
P D P
S
R
E C
A foo:??
B
Figura 14.1 Ejemplo de paquetes y visibilidad.
Arriba
P
Q
R Figura 14.2 Una jerarquía de paquetes.
14.1.1
Control del espacio de nombres
Un paquete no tiene verdadero significado por sí mismo. Todo su comportamiento lo proporcionan los elementos del modelo que hay dentro de él, y no tiene una interfaz. Lo único que puede hacer es definir el espacio de nombres de los elementos contenidos en él. La idea de un espacio de nombres es simplemente que, dado un espacio de nombres, dos cosas diferentes no pueden tener el mismo nombre. Por ejemplo, no puede haber dos clases diferentes llamadas Foo en el mismo espacio de nombres; ciertamente, no se puede tener un caso de uso llamado Foo y también una clase con el mismo nombre. En un espacio de nombres distinto, sin embargo, puede haber otro elemento llamado Foo sin causar confusión. Se pueden distinguir los distintos elementos llamados Foo especificando el espacio de nombres al que se hace referencia. Por ejemplo, un nombre más preciso para la clase A que aparece en la Figura 14.1 es P::A, y un nombre más preciso para la clase C es P::R::C.
PAQUETES Y MODELOS
175
El ejemplo más popular de un espacio de nombres es una clase. No se puede esperar que haya una operación y un atributo con el mismo nombre, y claro está, no se puede. Sin embargo, puede haber operaciones en clases distintas con el mismo nombre, sin que haya ninguna confusión: si se necesita identificar la operación de la que se está hablando, simplemente hay que nombrar la clase para resolver la ambigüedad. El control del espacio de nombres de los paquetes es una generalización de esta idea. Esto es útil cuando hay varios equipos desarrollando las diferentes partes del sistema. Podría ocurrir que dos equipos utilizasen el mismo nombre para diferentes propósitos. Al final, nadie quiere descubrir que el modelo de UML resultante de juntar las partes es ilegal. Si desarrollan diferentes paquetes, trabajan en distintos espacios de nombres, y tales clases no invalidan el modelo. De forma implícita, cada elemento del modelo tiene “nombre y apellidos”, que es su nombre dentro del espacio de nombres (por ejemplo, dentro de su paquete) más la información sobre en qué espacio de nombres se encuentra (por ejemplo, el nombre y apellidos del paquete más pequeño que lo contiene). Pregunta de Discusión 89 Si conoce algún lenguaje de programación que permita sobrecarga, tendría que preocuparse de una clase que define dos métodos con el mismo selector pero con distintos parámetros. ¿Qué pasaría?
Relaciones entre paquetes Cada elemento del modelo está, como mucho, en un espacio de nombres. En general, un elemento puede nombrar (conocer) elementos que están en su propio paquete, y elementos que están en paquetes de alrededor. Sin embargo, no puede nombrar elementos de paquetes que no le contengan. Para ver lo que esto significa, piense cuáles son las posibles clases que el atributo foo de la clase A en la Figura 14.1 podría ver. Sería legal que el atributo viese la clase B, porque A y B están en el mismo paquete. D también es una elección legal, porque D está en un paquete que contiene el paquete de A (siempre hay implícito un paquete en el más alto nivel, que en este caso contendría los paquetes P y Q y la clase D). C no es una elección correcta en UML, y tampoco lo es E, porque estas clases no están disponibles en el espacio de nombres de A. En resumen: Las cosas que están fuera de un paquete no pueden verse dentro de éste.
Si algún elemento de P necesita nombrar algo en un paquete (digamos Q) que no contiene P, esto indica que P depende de Q; es decir, es posible que un cambio en Q afecte a P. (Por ejemplo, borrar un elemento en un paquete puede afectar cualquier otro paquete que utilice el nombre de dicho elemento borrado). Tales dependencias hay que representarlas, para que los desarrolladores lo tengan en cuenta cuando tengan que hacer algún cambio. Las dependencias entre paquetes, como el resto de dependencias, se representan con una flecha punteada (- --->). Una dependencia no influye en la manera en que se nombran sus elementos; es decir, no cambia el espacio de nombres implicado. Por ejemplo, si se representa que P depende de Q, se puede utilizar la clase E como clase para el atributo foo, pero habría que referenciarlo como Q::E.
176
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
Si esto es muy tedioso, una alternativa es importar el paquete Q, para poder nombrar sus elementos directamente dentro de P. Si se pone la palabra clave <> en la flecha desde P a Q, entonces dentro de P se puede hacer referencia a la clase E como si estuviera directamente contenida en P, sin mencionar su paquete. Pregunta de Discusión 90 Piense cuándo querría acceder a un paquete y cuándo importarlo. Debe centrar su atención en el mantenimiento del modelo.
Visibilidad Hasta aquí no se tiene ninguna manera de evitar que un elemento de un paquete vea con detalle todo lo que hay en el paquete al que hace referencia. Volviendo a la analogía con las clases, se espera tener más control. Una clase tiene parte de sus atributos y operaciones públicos, para que puedan ser referidos por cualquier cosa que pueda hacer referencia a la clase completa, mientras que mantiene otros privados. Esto es, una clase puede poner restricciones en la visibilidad de los elementos que contiene. Los paquetes pueden hacer lo mismo. Algunos elementos de un paquete pueden diseñarse como públicos y otros como privados. Los que acceden o importan un paquete pueden ver los elementos públicos que hay en él, pero no los privados.
Como con los atributos y las operaciones de las clases, se puede mostrar si un elemento es público o privado poniendo o delante de su nombre. Los elementos importados de un paquete son como el resto de elementos de dicho paquete: pueden verse desde fuera si son públicos. UML2 proporciona una segunda palabra clave <> que se puede utilizar en sustitución de <> si los elementos importados deberían ser tratados como privados.
P: ¿Qué mecanismos de paquetes y espacios de nombres hay en los lenguajes de programación que conoce? ¿Cómo se compara el control del espacio de nombres a nivel de lenguaje de programación, con el control del espacio de nombres en UML? ¿Cómo implementa la dependencia <> entre paquetes? ¿Y <>?
14.2
Modelos En el Capítulo 4 se habló de la vista 4 1 de un sistema, y ahora se ha visto cómo se describen las vistas por los diversos modelos de UML. Los modelos de UML no se corresponden exactamente con las 4 1 vistas, pero cubren los aspectos necesarios. Para recapitular: • la vista de casos de uso (el 1) se obtiene del modelo de casos de uso; • la vista lógica se obtiene del modelo de clases, y de los diagramas de interacción y los diagramas de estado, que también se utilizan para especificar el comportamiento lógico del sistema.
PAQUETES Y MODELOS
177
• la vista de proceso se obtiene de los diagramas de interacción, y diagramas de estado y de actividad, que también se utilizan para determinar los hilos de control del sistema, y de los diagramas de despliegue. • la vista de desarrollo se obtiene de los diagramas de estructura de componentes, y de los paquetes donde surjan. • la vista física se obtiene de los diagramas de despliegue. En cada caso, el modelo incluye algunos elementos del modelo pero no otros. Por ejemplo, el modelo de casos de uso incluye casos de uso, actores y relaciones entre ellos, pero no clases y sus asociaciones. Formalmente, UML define un modelo como un paquete que contiene los elementos del modelo construyendo una vista particular del sistema que está siendo modelado. Un modelo para un sistema tiene que representar el sistema completo tal y como se ve desde el punto de vista elegido. Pregunta de Discusión 91 ¿Por qué cree que no se puede, en un modelo, omitir partes arbitrarias del sistema?
RESUMEN
En este capítulo se han tratado los mecanismos de UML para estructurar el desarrollo de los sistemas. El mecanismo más general es el paquete, que puede contener cualquier colección lógica de elementos del modelo. La visión de UML de un modelo formaliza la idea informal de modelo que se ha estado utilizando todo este tiempo: un modelo incluye los elementos del modelo que forman una vista particular de un sistema a un nivel de abstracción determinado. Un modelo se representa por uno o más diagramas. Esto completa nuestro estudio de UML, el lenguaje. No se ha cubierto con todo detalle; para aprender más se puede acudir a la documentación oficial [48], o a otros muchos libros, como por ejemplo a los de los “Tres Amigos”. A continuación, en la Parte III, se estudiarán tres casos concretos.
Parte
III Estudio de casos
Capítulo 15
Administración del CS4
181
Capítulo 16
Juegos de mesa
189
Capítulo 17
Simulación de eventos discretos
203
Capítulo
15 Administración del CS4
En este capítulo se tratará el estudio de un caso ligeramente más complejo que el del Capítulo 3. No se entrará en él todo con todo detalle, y no se cubrirá el paso desde el diseño detallado al código, ya que depende del lenguaje de programación, pero el código de Java para este caso está disponible en la página web de este libro.
15.1
El estudio del caso Está considerando la oferta de un contrato para desarrollar un sistema que ayude al departamento de informática de una universidad a administrar los cursos de grado del último año (“curso de diplomatura”). Se le ha dado la siguiente descripción de los procedimientos actuales del departamento como parte de la información en la que tiene que basar su oferta. Léalo cuidadosamente, considerando las preguntas que necesitaría hacer, y a quién se las tendría que hacer para clarificar los requisitos.
La situación actual Hacia el final de cada año académico, el Comité de Programa en el Departamento de Informática determina los módulos que estarán disponibles para los estudiantes CS4 en el próximo año. (Un estudiante CS4 es cualquier estudiante que está en cualquier módulo del cuarto año en el departamento de informática, tanto si el estudiante está registrado o no en un título de informática). Al final de cada año académico, el Director de Departamento asigna tareas a los miembros de la plantilla de profesorado y a otros; en particular, se asigna una persona para dar clase en cada uno de los módulos que se supone van a estar disponibles en el próximo año. (Llamaremos a estas personas profesores adjuntos, para simplificar). Cada profesor adjunto actualiza los artículos de la guía del curso para su módulo. El coordinador CS4 actualiza el resto de partes de cada guía, y comprueba los artículos del módulo realizados por los profesores adjuntos. Los artículos del módulo se escriben en el lenguaje de formato LATEX.
182
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
Alguien, en la Oficina de Enseñanza del No Graduado (a partir de ahora se denominará a tal persona “la OENG”), genera la versión en papel de cada guía del curso; el coordinador CS4 obtiene las versiones HTML ejecutando la aplicación de conversión latex2html sobre la fuente LATEX. El coordinador CS3 se supone que entrega una lista de los estudiantes que pasan a CS4 desde CS3 tanto al coordinador CS4, como a la OENG. El coordinador CS4, le dice a la OENG los estudiantes que entran en CS4, aparte de los que vienen de CS3, por ejemplo estudiantes no graduados. La OENG mantiene la lista maestra de todos los estudiantes CS4, y actualiza la lista de correo de los estudiantes que reciben los módulos CS4, que se conoce a través de la dirección de correo electrónico clasecs4. A cada estudiante le aconseja un miembro de la plantilla que actúa como Director de Estudios (DdE). Se asigna un DdE a un estudiante en su primer año de estudio y mantiene este rol hasta que termina. Los estudiantes se registran provisionalmente en los módulos rellenando unos formularios y llevándolos a la Oficina de Enseñanza del No Graduado. La OENG comprueba que cada estudiante que se registra se encuentra en la lista como un estudiante CS4, y que cada estudiante CS4 está registrado en un conjunto razonable de módulos. En los casos de duda, se consulta al DdE del estudiante, y puede tener una discusión con dicho estudiante. La OENG produce entonces, para los profesores adjuntos, las listas de estudiantes que reciben sus módulos. No se garantiza que estas listas lleguen a los profesores adjuntos antes de la tercera semana. Por desgracia, esto es demasiado tarde para que los profesores adjuntos puedan saber cuántas copias deben hacer de las cosas...
Preguntas A continuación se listan algunas posibles preguntas. Puede que encuentre más: nosotros, los autores, estamos familiarizados con la estructura universitaria descrita, y puede que hayamos asumido conocimientos que ustedes, los lectores, no tienen. Por supuesto, el predominio de tales presunciones es uno de los factores que hace que el análisis de requisitos sea tan difícil. 1. ¿Con qué estudiantes se está implicado, y es siempre el mismo conjunto? El texto hace referencia, unas veces a “estudiantes CS4” y otras veces a “estudiantes”. 2. ¿Qué es la lista de correo CS4, y cómo se actualiza? 3. ¿Hay alguna otra cosa que tenga que ser actualizada? ¿Páginas web, por ejemplo? 4. ¿Qué son las guías de curso, y cuántas hay?
P: Clasifique sus preguntas de acuerdo a si necesita conocer las respuestas ahora, si antes puede realizar la oferta, o si simplemente necesita obtener las respuestas antes de completar el sistema.
Se asume que al preguntar más, se encuentra (entre otras cosas), con que hay una guía de curso por cada curso de diplomatura —“curso de diplomatura” y “título” son sinónimos para el ámbito de esta aplicación. Los cursos de doctorado relevantes para el sistema son Informática, Informática e Inteligencia Artificial, Informática e Ingeniería Electrónica, etc. Los detalles de valoración, y las regulaciones sobre qué combinaciones de módulos son aceptables, son diferentes para cada uno de estos títulos, por lo que hay una guía aparte para cada uno. Sin embargo, muchos módulos aparecen en varios cursos de diplomaturas diferentes, y en tal caso la descripción
ADMINISTRACIÓN DEL CS4
183
del módulo es la misma en cada guía. Cada estudiante (aparte de los no graduados, quienes visitan la universidad por un solo año, no obtienen un título y pueden cursar cualquier combinación aleatoria de módulos) se registra en un curso de diplomatura, y recibe la guía del curso apropiada. El coordinador CS4 es responsable de producir todas las guías de curso. (En los casos de los títulos interdepartamentales, es normal que el otro departamento también produzca su propia guía de curso, de manera que los estudiantes de estos títulos obtienen dos guías con alguna información duplicada; pero debido a la estructura de la universidad no se considera sensato actualmente eliminar estos duplicados).
La investigación El Departamento ha solicitado investigar la posibilidad de desarrollar un sistema que automatice partes de este proceso, ya que esperan que sea posible: • reducir la carga de trabajo rutinario de toda la plantilla, especialmente del coordinador CS4; • permitir a los estudiantes registrarse en los módulos en línea (online). • facilitar la obtención de información actualizada y fiable (de la OENG). • mejorar la posibilidad de seguimiento de tal información. • proporcionar antes la información, como son las guías de curso y las listas de los estudiantes que cursan los módulos, automatizando su producción. El sistema de administración CS4 debería ser capaz de crear un informe de cualquier estudiante: por ejemplo, si es un estudiante graduado o no, qué módulos está cursando, para qué curso de diplomatura está registrado un estudiante graduado, o qué miembro de la plantilla es el DdE del estudiante. También actúa como repositorio de información sobre los módulos: quién es el profesor adjunto, de qué curso de diplomatura forma parte, y qué estudiantes lo están cursando. Pregunta de Discusión 92 ¿Cree que las expectativas del Departamento son razonables? ¿Cree que una aproximación orientada a objetos tiene sentido aquí?
En este capítulo no se tratará cómo implementar los mecanismos de consultas; éstas las puede proporcionar una base de datos comercial junto con las técnicas estándar para hacer que los objetos sean persistentes, tema que se trató en el Panel 3.2.
P: Si conoce un lenguaje de consultas de bases de datos, como es SQL, prepare las consulta que estime necesarias. ¿Qué consideraciones ha tenido en cuenta?
Con los casos de uso de las consultas eliminados, el resto de casos de uso que hay que proporcionar son: • Producir guías de curso. • Producir Lista CS4. • Registrarse en módulos.
184
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
Crer lista CS4
OrganizadorCurso CS3
OrganizadorCurso CS3 Producir guías de curso
OENG
ProfesorAdjunto CS4
Registrarse en módulos
EstudianteCS4 DirectorDeEstudios CS4
Figura 15.1 Modelo de casos de uso.
La Figura 15.1 presenta el modelo de casos de uso general. Aquí se tiene una descripción más detallada del caso de uso Producir guías de curso. Mantiene un punto de vista conservador de cómo el sistema tiene que hacer el trabajo, que probablemente sea apropiado en la primera iteración del sistema (si bien, véase la Pregunta de Discusión 93).
Producir guías de curso Este caso de uso puede utilizarse sólo cuando el comité de programa haya determinado el conjunto de módulos que estarán disponibles, y el director del departamento haya asignado tareas a los profesores adjuntos. El organizador del curso CS4 actualiza las secciones principales (independientes del módulo) de cada guía de curso mediante la obtención del texto actual del sistema, modificándolo y devolviendo la versión modificada al sistema. El profesor adjunto de cada módulo, de manera similar, actualiza la descripción del módulo obteniendo el texto del sistema, actualizándolo, y devolviéndolo al sistema. Estas actualizaciones pueden darse en cualquier orden. El sistema mantiene el seguimiento de qué actualizaciones se han hecho. Una vez que se han llevado a cabo todas las actualizaciones para la guía, el sistema envía el texto completo de la guía por correo electrónico a la Oficina de Enseñanza del No Graduado, que lo imprime y actualiza las páginas web a partir de él.
ADMINISTRACIÓN DEL CS4
185
Pregunta de Discusión 93 ¿Se proporciona valor suficiente para que alguien considere que merece la pena proponerlo como funcionalidad en una primera iteración de la entrega? ¿O debería tratarse sólo como una iteración interna? Si es el segundo caso, ¿qué funcionalidad cree que se necesitará antes de que se pruebe con los usuarios.? ¿Cómo aclararía esto?
P: Desarrolle las descripciones del resto de casos de uso. Cuando surjan preguntas que en un proyecto real tengan que ser tratadas con los usuarios, guarde cuáles son los temas y cómo ha decidido resolverlos en este ejercicio.
15.1.1
Modelo de clases
P: Dibuje un modelo de clases a nivel conceptual. Incluya las multiplicidades, pero no se preocupe todavía por los atributos y operaciones.
La Figura 15.2 es un posible modelo de clases. 1
Profesor Adjunto
enseña 0..* Módulo 6
6..*
cursa DirectorDe Estudios
dirige 1
1..* CursoDe Diplomatura
1..* 0..*
Estudiante
1 está en 0..* EstudianteNoGraduado
EstudianteGraduado
Figura 15.2 Modelo de clases.
Pregunta de Discusión 94 La Figura 15.3 es otro modelo de clases. ¿Qué opina de él?
186
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
1
Profesor adjunto
enseña 0..*
conoce
Módulo
conoce 6
UI conoce DirectorDe Estudios
6..*
cursa
dirige 1
1..*
1..* 0..*
CursoDe Diplomatura
Estudiante
1 está en 0..* EstudianteNoGraduado
EstudianteGraduado
Figura 15.3 Otro modelo de clases.
P: En la práctica, no todos los profesores adjuntos darán clase en los módulos CS4. En particular, todos los DdE no necesitan ser un profesor adjunto de CS4. ¿El diagrama está preparado para ello?
15.1.2
Dinámicas
La Figura 15.4 muestra las tarjetas CRC que se han encontrado para las clases implicadas en el caso de uso Producir guías de curso. Se pueden utilizar para examinar cómo deben Nombre de clase: CursodeDiplomatura Responsabilidades
Colaboradores
Mantener coleción de módulos
Módulo
Nombre de clase: DirectorDeEstudios Responsabilidades
Colaboradores
Proporcionar una interfaz entre el DdE humano y el sistema
Generar el texto de las guías de curso
Nombre de clase: Módulo Responsabilidades
Colaboradores
Mantener la descripción del curso Mantener Profesor Adjunto del curso
Figura 15.4
Las tarjetas CRC necesarias para Producir guía de curso.
ADMINISTRACIÓN DEL CS4
187
interactuar las clases para alcanzar ese caso de uso. Se han considerado las responsabilidades de las clases sólo con respecto a ese caso de uso. Incluso, aunque tuviera sentido darle a cada clase más responsabilidades —y probablemente será fundamental hacerlo según se consideren más casos de uso— añadir estas responsabilidades antes de identificar la necesidad de tenerlas, sería una instancia de requisitos inventados, cosa que se prohibió en el Capítulo 3.
P: Utilice las tarjetas CRC como ayuda para identificar las operaciones necesarias de las clases implicadas en el caso de uso. P: Desarrolle las tarjetas CRC para los otros casos de uso e identifique las operaciones asociadas con estas clases.
15.1.3
Diagramas de estado
No hay clases con cambios de estado interesantes.
15.1.4.
Diagramas de actividad
En el Capítulo 11, se dijo que los diagramas de actividad pueden ser útiles para modelar a nivel de negocio, para comprender mejor cómo funciona la organización, dentro de la cual el sistema debe encajar. El determinar los cursos que hay y quién los va a impartir y después, la generación de las guías del curso, no es un flujo de trabajo trivial, con sincronizaciones y dependencias de tareas que los desarrolladores tendrán que comprender, particularmente si el sistema va a ser desarrollado para ayudar a automatizar el proceso. La Figura 15.5 es el flujo de trabajo de la tarea que aparece de esta manera. Actualizar secciones centrales
Determinar módulos
Asignar tareas
Actualizar la entrada al módulo
Imprimir guía comité de programa
Figura 15.5
director de departamento
profesor adjunto
OENG
Generar versión HTML organizador de curso CS4
Un diagrama de actividad para la preparación de la guía del curso.
188
15.2
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
Discusión Sistemas con grandes cargas de datos La responsabilidad fundamental de todas las clases es encapsular los datos. Esto no debe sorprender, ¡ya que el propósito principal del sistema es mantener los datos sobre los CS4! Pero, en realidad, esto no es muy normal en los sistemas OO. A menudo, pero no siempre, las clases que no hacen otra cosa que encapsular los datos son un indicio de que todo el comportamiento está en alguna otra clase, quizá en una única clase Controladora. Esto es un diseño incorrecto porque suele significar que el uso actual del sistema (el conjunto actual de casos de uso) es difícil de codificar. Por lo tanto, cuando el diseño tiene clases así hay que comprobar que no falta ningún comportamiento que acompañe a los datos que se encapsulan. En este caso, se piensa que lo que se tiene es razonable: simplemente el sistema no tiene nada que ver con el comportamiento.
La interfaz de usuario No se ha enseñado la interfaz de usuario, es decir, cómo los actores llegan a utilizar estas funciones. Pensándolo bien, queda claro que un único objeto de interfaz de usuario no va a ser suficiente: actores diferentes necesitan acceder a distintas partes de la funcionalidad del sistema. Probablemente, se terminará con algo similar a la interfaz Modelo-Vista-Controlador, donde cada actor tiene una vista diferente (pero consistente) del sistema. Se necesita más trabajo de diseño para tratar estos aspectos, pero los dejamos caer para este ejemplo.
Capítulo
16 Juegos de mesa
Se nos pide tratar el siguiente problema. Una nueva compañía desea entrar en el mercado de los juegos de mesa por ordenador. Pretenden producir una gama de versiones de pantallas de juegos de dos jugadores muy conocidos, empezando por las Tres en Raya y el Ajedrez. Dos usuarios comparten una pantalla y realizan movimientos por turnos. El programa asegura que sólo se realizan movimientos legales y declara al ganador. Para hacer que la implementación de los nuevos juegos sea fácil, y para facilitar el trabajo de mantenimiento de varios juegos diferentes, la compañía quiere diseñar un “marco de trabajo (framework)” del juego, que pueda reutilizarse lo máximo posible. Pregunta de Discusión 95 ¿Qué preguntas haría a su cliente potencial?
Una pregunta obvia sería “¿por qué cree que alguien pagaría mucho dinero por esto?”. No es obvio que tenga más ventajas que un juego de mesa clásico, y está claro que tiene algunas desventajas.
P: Enumere algunas desventajas de este producto comparándolo con un tablero físico. ¿Puede pensar en alguna ventaja? Pregunta de Discusión 96 Suponga que usted es un proveedor de desarrollo de software para esta compañía. ¿Es trabajo suyo (es decir, su responsabilidad profesional) plantearse si el producto tendrá éxito? ¿Hasta qué punto? ¿Por qué? ¿Depende de quién o qué tipo de compañía sea usted?
190
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
Pregunta de Discusión 97 Suponga que, en realidad, la compañía espera que este “marco de trabajo” básico sea mejorado posteriormente. ¿Qué futuras mejoras ve?
16.1
Alcance y análisis preliminar Supongamos que hay alguna justificación para proceder con el “marco de trabajo” propuesto anteriormente. Una parte fundamental del trabajo implicado en el desarrollo de un juego estará relacionado con la interfaz de usuario; claro está, sólo esto sería una buena razón para elegir un lenguaje orientado a objetos. También influirá la elección del lenguaje, ya que se querrá tener disponible un paquete de interfaz de usuario gráfico adecuado. Para los propósitos de este capítulo, se supondrá que el lenguaje será Java y que el juego será implementado con un applet. Este ejemplo es más abstracto que cualquiera de los estudios de los casos previos. Se nos ha solicitado desarrollar un “marco de trabajo” para una familia de juegos, no un juego particular. ¿Qué es un “marco de trabajo”? En este caso, se refiere a una arquitectura adecuada para esta clase de sistemas, junto con cualquier funcionalidad común que se pueda encontrar. (Se tratará los “marcos de trabajo” generales, brevemente, en el Capítulo 18). Nuestro objetivo es hacer fácil (rápido, barato) la implementación de sistemas para juegos particulares. Diseñaremos algunas clases abstractas, dejando sólo los detalles específicos de cada juego para que lo complete un desarrollador más tarde, probablemente por medio de la división en subclases. El desarrollo de buenos “marcos de trabajo” (por ejemplo, aquellos que originalmente aumentan la productividad de los desarrolladores y encargados del mantenimiento) es notoriamente difícil, y es importante no perderse en abstracciones. En la vida real no es normal desarrollar un marco de trabajo para este sistema sin implementar varios ejemplos. (El escrito de James Newkirk y Robert Martín [38] —hay un enlace desde la página web de este libro— cuenta la historia del desarrollo de un marco de trabajo real). Aquí se trata de dar una idea general del proceso, pero indudablemente el “marco de trabajo” presentado en este libro puede mejorarse. Debe tenerlo presente, especialmente si lo utiliza para desarrollar un juego que no se haya tenido en cuenta en este ejemplo. Pregunta de Discusión 98 ¿Cuáles son los peligros en el desarrollo de un “marco de trabajo”?
En Internet hay disponibles muchas implementaciones de estos juegos en Java, algunos con el programa fuente. Podría ser interesante comparar algunos de ellos entre ellos mismos y con el que se presenta aquí.
16.1.1
Tres en Raya
El juego transcurre en un tablero cuadrado de 3 por 3 entre dos jugadores. El jugador que empieza (“Jugador X”) elige un cuadrado del tablero y pone una cruz en él. El otro jugador
JUEGOS DE MESA
191
Figura 16.1 Tres en raya.
(“Jugador O”) elige un cuadrado vacío y pone un círculo en él. Después los jugadores continúan alternativamente, situando sus propias marcas en los cuadrados vacíos. El ganador es el primer jugador que complete una línea recta (horizontal, vertical o diagonal) de tres de sus propios “elemento léxico (token)”. Si el tablero está lleno y ninguno de los dos jugadores consigue esto, el juego termina con empate. La Figura 16.1 presenta un ejemplo del tablero después de que el jugador X haya ganado.
16.1.2
Ajedrez
El ajedrez es un juego mucho más complicado, por lo que nos da lástima el desarrollador que tenga que implementarlo, ¡por muy bueno que sea el marco de trabajo que se le proporciona! Es suficiente mantener en mente la siguiente descripción abreviada: El juego transcurre en un tablero cuadrado de 8 por 8 con piezas de varios tipos: peones, torres, caballos, alfiles, reinas, reyes, cada uno de los cuales está en dos colores, blanco y negro. Hay dos jugadores, el “Jugador Blanco” que tiene las piezas blancas, y el “Jugador Negro” que tiene las piezas negras. La configuración inicial es la que se muestra en la Figura 16.2. Los jugadores alternan movimientos, eligiendo una de sus piezas para mover. Los movimientos legales dependen de la pieza que se mueva y puede depender también de la configuración de la otras piezas, y de la historia del juego. Las piezas se pueden comer. En ciertas configuraciones, un rey está en jaque. Si el rey de un jugador está en jaque se debe mover a una configuración en la que el rey no lo esté, si es posible. Si esto no es posible, el rey está en jaque mate y gana el otro jugador. Si la partida vuelve tres veces a la misma configuración, termina en tablas. Para desarrollar un marco de trabajo, hay que considerar lo que tienen estos juegos en común, y lo que tienen en común con el resto de juegos de mesa que hay que considerar. En ambos casos:
192
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
Figura 16.2 Ajedrez.
1. En el juego participan dos jugadores. 2. El juego transcurre en un cuadrado, de un tablero cuadrado. 3. Los jugadores mueven de forma alternativa. 4. Mover es alterar el estado del tablero añadiendo, eliminando y/o moviendo algunos “elemento léxico”, que son los elementos que hay en el tablero (marcas o piezas). 5. Cada “elemento léxico” del tablero pertenece a uno u otro jugador. 6. Toda la información relevante está disponible para ambos jugadores. 7. Los movimientos legales dependen del estado del tablero —qué “elemento léxico” está dónde— posiblemente junto con otros factores, como la historia de la partida. 8. Quién es el ganador depende de algunos factores. Supongamos que una discusión más detallada con el cliente revela que las funciones 1, 4, 6, 7 y 8 en general se tienen que cumplir, pero que las 2,3 y 5 pueden fallar. Como resumen, se indican los ejemplos dados por el cliente (no se tratan aquí otros ejemplos en detalle, y no importa si no se conoce el juego en cuestión). El tablero podría ser de cualquier forma (por ejemplo en el juego Hex, cuyo tablero es hexagonal). Su estado viene determinado por la posición de los “elementos léxicos”, pero los “elementos léxicos” no tienen que pertenecer a un jugador en particular; un movimiento (tal y como se ha descrito en el punto 4) podría ser legal para cualquiera de los dos jugadores: por ejemplo, podría haber “elementos léxicos” que se permitieran mover a cualquier jugador (por ejemplo, en el Kalaha). Puede que los jugadores no muevan siempre de forma alternativa (por ejemplo en las Damas); la historia de la partida y el estado del tablero determinan de quién es el turno.
P: Dé un ejemplo, de un juego que conozca, donde la legalidad del movimiento dependa de la historia de la partida, no sólo de la posición de los “elementos léxicos” antes y después del movimiento.
JUEGOS DE MESA
193
El análisis de los casos de uso no parece útil en el desarrollo del marco de trabajo, por lo que se omite el diagrama de casos de uso.
P: Dibuje el diagrama de casos de uso, y decida si es útil hacerlo. Procedamos a identificar las clases y las relaciones. Esto es mucho más desafiante en este caso que en los ejemplos anteriores, y sólo la técnica de identificación de nombres no será suficiente. La técnica adoptada, algo difícil de describir, consiste en empezar con unas pocas clases candidatas y preparar un borrador del modelo de clases. Después, considerando los ejemplos de los juegos reales, se piensa en algunas de las interacciones principales, iniciando un esbozo de las tarjetas CRC como ayuda. Se analizan varias iteraciones de este proceso antes de alcanzar un modelo de clases que funcione. Aquí se trata de dar una idea general del procedimiento, y después mencionar algunas de las alternativas consideradas. Nuestras clases del marco de trabajo son: • • • • •
Jugador PosiciónActual ElementoLéxico Movimiento Juego.
Los nombres, aunque son lo más sugerentes que se ha encontrado, no son suficientes para explicar lo que representan. Empecemos creando tarjetas CRC para almacenar las responsabilidades de cada clase, posponiendo la identificación de los colaboradores que necesitará un objeto de cada clase para ejecutar sus responsabilidades. Se han destacado algunas responsabilidades dudosas con signos de interrogación y anotaciones entre corchetes, algo que es útil hacer en las tarjetas CRC cuando no se está seguro de algo pero se quiere almacenar provisionalmente. Jugador Responsabilidades Mantener cualquier dato de usuario. Identificar un jugador. Proporcionar un símbolo visual para el jugador. PosiciónActual Responsabilidades Mantener los datos visibles por el usuario: posición; de quién es el turno; finalmente quién es el ganador. Aceptar un movimiento del usuario y validarlo. ElementoLéxico Responsabilidades Representa un “elemento léxico” del juego. ¿Mantener la posición del “elemento léxico”? [¿O es función de PosiciónActual?] Proporcionar un símbolo visual para el “elemento léxico”.
Colaboradores
Colaboradores
Colaboradores
194
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
Movimiento Responsabilidades Encapsular los cambios realizados durante el turno de un jugador. ¿Poder confirmarlo él mismo? Juego Responsabilidades Comprender las normas del juego: Validar movimientos; determinar el ganador; mantener cualquier información necesaria sobre movimientos anteriores.
Colaboradores
Colaboradores
A continuación, consideremos el caso particular de las Tres en Raya. Se necesitará implementar las clases que lleven a cabo las responsabilidades identificadas. Para parte o todas las clases del marco de trabajo, se creará una subclase especializada que completa, para el juego particular que se trate, las responsabilidades asignadas a esa clase del marco de trabajo. Es decir, el marco de trabajo es un ejemplo de lo que se conoce como dirigido por la arquitectura. El propósito es que una aplicación desarrollada utilizando marcos de trabajo tenga una arquitectura razonable “sin mucho esfuerzo”. Es un beneficio que merece la pena tener incluso cuando, como en el ejemplo inmaduro aquí presentado, la cantidad de código reutilizado que resulta de la reutilización de las clases del marco de trabajo es mínima. Un marco de trabajo madura a medida que se van desarrollando varias aplicaciones. La funcionalidad común, que el marco de trabajo debería proporcionar, puede identificarse cuando aparece en varias aplicaciones del marco de trabajo. Esta funcionalidad puede convertirse en la responsabilidad del marco de trabajo; las aplicaciones existentes pueden refactorizarse para aprovecharse del nuevo marco de trabajo. De esta manera, el marco de trabajo se vuelve cada vez más potente. Por lo tanto, es importante la experiencia que se adquiere al ponerlo en práctica: es difícil identificar la funcionalidad que va a ser común en la primera aplicación. Como simple convención de nombres, llamemos a la clase especializada correspondiente a ElementoLéxico, OXOElementoLéxico, y así sucesivamente. En algunos casos, puede que se necesite utilizar objetos de más de una clase para completar las responsabilidades asignadas a un objeto de una única clase del marco de trabajo. El caso más obvio es PosiciónActual; tal y como se mencionó en el Capítulo 6, hay objetos de dominio obvios de Tablero y Cuadrado que pueden colaborar para mantener la posición del juego, tal y como lo ve el usuario. Por lo que se pueden inventar dos clases nuevas y describir sus responsabilidades individuales de la siguiente manera:
Tablero Responsabilidades Mantener los datos que ve el usuario: posición; de quién es el turno; finalmente quién es el ganador. Aceptar un movimiento del usuario y validarlo.
Colaboradores
JUEGOS DE MESA
Cuadrado Responsabilidades Mantener los datos que pertenecen a un cuadrado determinado del tablero: dónde está; si contiene un “elemento léxico”; y si es así, de qué tipo. Decir si un punto está dentro del tablero.
195
Colaboradores
La clase (probablemente abstracta) PosiciónActual la realiza la clase Tablero que contiene nueve Cuadrados. (Es decir, Tablero es una subclase de PosiciónActual, y tiene un atributo no heredado que es la colección de Cuadrados).
16.2
Interacción Las tarjetas CRC proporcionan un punto de inicio para la comprensión de la interacción. Como ejemplo, se describe una interacción dentro de la aplicación Tres en Raya utilizando las clases especializadas. El jugador humano interactúa con una representación en pantalla del tablero de las Tres en Raya, controlado por el Tablero. El jugador pulsa en cualquier parte del tablero, lo que provoca (por medio de mecanismos estándares de Java) el envío de un mensaje a Tablero, con información sobre dónde se pulsó. El Tablero, con la ayuda de sus Cuadrados, deduce a partir de las coordenadas qué Cuadrado del Tablero ha sido pulsado. El Tablero sabe que es el turno del Jugador X, por lo que el significado de la pulsación es que X quiere colocar un “elemento léxico” en el cuadrado. Se crea un nuevo objeto OXOMovimiento que conoce al Cuadrado y al Jugador implicados, y pasa el OXOMovimiento a OXOJuego para que lo valide. OXOJuego comprueba que no haya ya ningún OXOElementoLéxico en el Cuadrado. No hay, por lo que es un movimiento válido. OXOJuego tiene que comprobar entonces si con este movimiento termina el juego. No es así. La respuesta de OXOJuego al mensaje validar, indica que el movimiento es correcto y que ahora tiene que mover O. El tablero le pide a OXOMovimiento que actualice la posición que se muestra al usuario —por lo que OXOMovimiento crea un nuevo OXOElementoLéxico X en el Cuadrado al que hace referencia, se olvida del objeto OXOMovimiento que no se va a necesitar más (será recogido por el colector de basura), guarda que el siguiente movimiento lo hará O, y espera a la siguiente pulsación, que representará el movimiento de O. Destacar que en el proceso se ha contestado, al menos en este caso especial, algunas de las preguntas de las que se dudaba en el conjunto inicial de tarjetas CRC. Por ejemplo, se ha decidido que no es función de OXOElementoLéxico conocer dónde está; mejor dicho, un Cuadrado sabe qué OXOElementoLéxico hay en él, y un Cuadrado conoce su propia posición. Para resolver esto con paso de mensajes, se obtiene la Figura 16.3 como el diagrama de comunicación de un movimiento correcto del Jugador X en las Tres en Raya. Se ha elegido mostrar un diagrama de comunicación en vez de un diagrama de secuencia simplemente por cambiar. Se podría dibujar el diagrama de secuencia correspondiente y ver cuál de los dos es más legible.
196
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
t:OXOToken {nuevo}
4.1:nuevo(X) m : OXOMovimiento
c:Cuadrado JugadorX : ActorJugador
4.2:añadirToken(t) 2:new(s,X) 4:confirm()
1:estáEn(x,y)
ratonPulsado(e)
Figura 16.3
:Tablero
:OXOJuego
3:validar()
Diagrama de comunicación para un movimiento de X en las Tres en Raya.
P: No se ha almacenado con detalle la manera en que OXOJuego lleva a cabo sus responsabilidades. Piense cómo se podría hacer esto. Desarrolle el nuevo diagrama de interacción. P: Piense en diferentes escenarios, y dibuje sus diagramas de comunicación. ¿En qué casos, si los hay, piensa que merecería la pena desarrollar un diagrama aparte? Por ejemplo: 1. Movimiento legal de O, no termina el juego. 2. Un movimiento ganador de X. 3. Un movimiento ilegal de O, donde O intenta colocar un O sobre una X en vez de en un cuadrado vacío.
Pregunta de Discusión 99 (Especialmente si se tiene alguna experiencia en HCI). ¿Cómo debería el sistema llevar a cabo la responsabilidad de hacer cumplir las normas del juego? ¿Este marco de trabajo permitirá una buena política? ¿Sobre quién recae cada responsabilidad?
Analizando la forma en que colaboran los objetos en este sentido, se pueden tomar decisiones sobre las asociaciones entre las clases que implementan las Tres en Raya, su navegabilidad, y las operaciones que cada clase debe proporcionar. La Figura 16.4 muestra un posible modelo de clases para las Tres en Raya. Se ha simplificado el diagrama (¡pero no el modelo!) mediante la omisión de las clases base de las clases especializadas que se almacenan, y también mediante la omisión de Jugador. La justificación del último punto tiene dos partes: en primer lugar, resulta que no se necesita una subclase especializada de Jugador, y en segundo lugar, en esta punto un objeto Jugador es fundamentalmente un elemento de datos lógico, del que depende toda clase (de manera inofensiva), por lo que si se incluye, se complicaría el diagrama innecesariamente. También se han omitido algunas operaciones privadas que se dan en la implementación real. Si se conoce Java llamará la atención algunos aspectos que son propios de este lenguaje; por ejemplo, el hecho de que Tablero tenga una operación inicializar() en vez de un constructor es debido a que se ha decidido que tablero sea un applet.
P: Analice un proceso similar basado en el Ajedrez en vez de en las Tres en Raya. ¿En qué se diferencian?
JUEGOS DE MESA
OXOToken
197
OXOMovimiento 0..1
OXOToken(j:Jugador) 0..1 OXOMovimiento(j:Jugador,c:Cuadrado)
Propietario() : Jugador Pintar(j:Jugador,x:entero,y:entero)
confirmar()
0..1
1 OXOJuego
0..1 OXOJuego(t:Tablero) validar(m:Movimiento):Resultado
1
1
Cuadrado
1
1
Tablero
Cuadrado (i:entero,j:entero,h:entero,w:entero) encontrarCuadrado(i:entero,j:entero):Cuadrado 1 estáEn(i:entero,j:entero):lógico propietario(i:entero,j:entero):Jugador 9 añadirToken(t:OXOToken) Mensaje(s:Cadena) 1 Inicializar() propietario():Jugador Pintar(g:Gráfico) pintar(g:Gráfico)
Figura 16.4
Diagrama de clases para las Tres en Raya.
Como habrá visto, los detalles de la interacción que tiene lugar cuando se realiza un movimiento son diferentes en distintas instancias del marco de trabajo. Sin embargo, hay una colaboración general en la que suceden tales interacciones. Destacar que en el caso particular de las Tres en Raya, Tablero y algunos Cuadrados desempeñan el papel de PosiciónActual.
P: Dibuje esta colaboración general. ¿Se necesita alguna anotación que no se haya tratado?
16.3
De vuelta al marco de trabajo Ahora que se comprende de forma razonable cómo encaja un juego dentro de nuestras ideas iniciales de cómo debería ser el marco de trabajo, se puede hacer un primer intento de describir el marco de trabajo con mayor detalle. Podría ser útil documentar cómo las clases del marco de trabajo trabajan juntas, para ayudar a los desarrolladores de la aplicación a planificar el uso de dicho marco de trabajo y/o a las personas que van a desarrollar el marco de trabajo en sí. Sin embargo, puede que no sea obvio que se tengan que tomar algunas decisiones sobre cómo es más útil hacer esto. Por ejemplo, teniendo en cuenta que la mayoría de las clases implementadas serán abstractas —se pueden crear objetos que pertenezcan a las subclases especializadas, pero no objetos de las clases del marco de trabajo— ¿cuáles son las asociaciones entre las clases del marco de trabajo? Sería discutible que hubiese una asociación entre dos clases del marco de trabajo cuando los objetos de las subclases especializadas deban estar asociadas, o puedan estar asociadas. En un marco de trabajo maduro se quiere asegurar que todas las asociaciones estén determinadas por el marco de trabajo, por lo que estas opciones coinciden. Se da una situación similar con las multiplicidades. Lo que es importante es que se eviten las situaciones ambiguas en las que las personas que utilicen el diagrama interpreten algo diferente de lo que el autor del diagrama pretendía. En el borrador del diagrama de clases para el marco de trabajo, Figura 16.5, aparecen sólo las asociaciones que deben existir, y se omiten todas las multiplicidades. Por ejemplo, debido a que el marco de trabajo especifica que Juego proporciona una operación validar que tiene como argumento un Movimiento y la invoca PosiciónActual, se documentan las asociaciones correspondientes. Como antes, se omite Jugador del diagrama.
198
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
Movimiento
Juego
Token
PosiciónActual
Figura 16.5
Diagrama de clases para el marco de trabajo de los juegos.
P: En ninguno de los diagramas de clase se han nombrado las asociaciones. Añada nombres o nombres de rol según se considere más útil. Pregunta de Discusión 100 ¿Qué puede decir sobre qué operaciones debería especificar el marco de trabajo? Añádalos al diagrama de clases del marco de trabajo.
Pregunta de Discusión 101 La utilización de un marco de trabajo general puede llevar a desarrollar un diseño más complejo del que sería necesario para una aplicación concreta. Por ejemplo, si se estuviera diseñando sólo las Tres en Raya, probablemente no se inventaría las clases Cuadrado, OXOElementoLéxico y OXOMovimiento, porque la relación entre OXOElementoLéxico y OXOMovimiento es muy estrecha. Piense qué habría hecho y cuáles son las consecuencias de las diferencias. ¿Cómo cree que se podrían evitar los peligros?
A continuación, se plantean algunas preguntas importantes, con la intención de dar la oportunidad de pensar en alguna de las opciones que se han rechazado (entre otras). Pregunta de Discusión 102 ¿Por qué se habla de los “elementos léxicos” en vez de generalizar más y hablar simplemente del estado del tablero? Si se tiene que hablar de “elementos léxicos”, ¿por qué un “elemento léxico” es un objeto? ¿Un “elemento léxico” tiene identidad? ¿En todos los juegos?
JUEGOS DE MESA
199
Pregunta de Discusión 103 Movimiento se añadió más tarde a la lista de clases. En un principio se rechazó como clase
candidata, debido a que era un evento. Más tarde se volvió a añadir, en parte debido a que los juegos se describen en función de movimientos y parece forzado evitar tener una clase movimiento en el modelo. Piense cómo validar los movimientos de un jugador (en la vida real) sin una clase Movimiento explícita. Desarrolle un modelo de clases que no contenga tal clase, y discuta sus ventajas con respecto al aquí presentado.
Pregunta de Discusión 104 En una de las etapas, en vez de la clase Juego había dos clases: • Jugada, que almacenaba cualquier información necesaria sobre movimientos pasados cuando PosiciónActual le proporciona tal información; • Normas, que valida los movimientos cuando se lo solicita PosiciónActual, utilizando la información de Jugada, PosiciónActual y Movimiento según se requiera. Piense en las implicaciones. ¿Qué soluciones propone?
Pregunta de Discusión 105 El concepto de Jugador es que este objeto es esencialmente datos; tiene tan poco comportamiento que quizás se podría decidir implementarlo como un simple tipo (digamos, utilizando tipos lógicos) que podrían ser atributos de “elementos léxicos” y movimientos. Esto contrasta con la situación del caso previamente estudiado donde se han utilizado clases que se correspondían con actores como mecanismo de control de acceso: por ejemplo la clase ProfesorAdjunto proporcionaba el acceso a las facilidades requeridas por los profesores adjuntos. Piense por qué se ha decidido no seguir esta aproximación.
Pregunta de Discusión 106 Aunque se muestre una asociación unidireccional entre las clases del marco de trabajo PosiciónActual y Juego, en la aplicación de las Tres en Raya hay una asociación bidireccional entre las clases correspondientes Tablero y OXOJuego. Por lo tanto, estas clases están fuertemente acopladas; no sería posible reutilizar una sin la otra. ¿Por qué existe esta diferencia en la navegabilidad? Piense diseños alternativos y si cree que esta situación es improbable.
16.4
Estados Finalmente se tratará un diagrama de estados, que podría ser útil en la implementación de las operaciones identificadas. La clase que tiene el diagrama de estados más interesante —es decir, aquella cuyo comportamiento cambiará claramente dependiendo de la historia de lo que se le ha
200
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
Mueve A
cuando(aMover = nulo)
cuando(aMover = B)
Fin del juego cuando(aMover = A)
cuando(aMover = nulo) Mueve B
Figura 16.6
Diagrama de estado de PosiciónActual.
hecho al objeto hasta ese momento— es PosiciónActual (Figura 16.6), ya que entre sus responsabilidades está el indicar el usuario a quién le toca mover, y finalmente quién ganó. Esto directamente proporciona tres estados: 1. Mueve A. 3. Mueve B. 4. Fin del juego. Hay que identificar los eventos que provocan las transiciones entre estos estados, y las acciones que debe ejecutar el objeto PosiciónActual. ¿Cuándo se pasa de Mueve A a Mueve B? Cuando ha movido A, y Juego ha validado el movimiento, y el Juego indica que a continuación mueve B. Para indicar esto se necesita utilizar la forma del evento cuando (descrito en el Capítulo 12), porque el evento que provoca el cambio de estado realmente es la respuesta a un mensaje (validar) enviado por PosiciónActual, no la recepción de un mensaje enviado a PosiciónActual. Pregunta de Discusión 107 Recuérdese la discusión en el Capítulo 11 sobre la elección del nivel de abstracción en el que se tiene que dibujar el diagrama de estado. Piense en versiones alternativas de este diagrama, y trate sus ventajas. Por ejemplo (hay otros), se ha decidido indicar un único estado Fin de juego, en vez de distinguir entre ganó A, ganó B, y Dibujar, pero se podría haber hecho de la otra manera. ¿Qué opina al respecto?
JUEGOS DE MESA
201
PREGUNTAS DE DISCUSIÓN
1. Elija sus tres juegos de mesa favoritos y piense si encajan dentro de la clase de juegos aquí tratados. Si es así, analice cómo y diseñe una aplicación del marco de trabajo para el juego. Desarrolle el código en un lenguaje adecuado. 2. Piense en los temas que surgen al realizar una versión distribuida. 3. La compañía descubre que los usuarios desean poder jugar contra el ordenador, no sólo contra otro jugador. ¿Cuáles son las implicaciones? En particular, ¿qué objeto (de una nueva clase o de una clase existente) debería tener la responsabilidad de decidir los movimientos del ordenador? 4. Piense detenidamente las interacciones entre los objetos del diseño que aparecen en este capítulo, y piense si las implementaciones de las operaciones obedecen la Norma de Demeter (véase el Capítulo 9). Si no es así, ¿puede encontrar soluciones mejores que lo cumplan? 5. Si se implementa el marco de trabajo y la aplicación de las Tres en Raya, probablemente le molestará la necesidad de utilizar “down-casts”, por ejemplo ya que el marco de trabajo especifica que cualquier Juego proporciona una operación validar que toma como argumento un Movimiento, mientras que un OXOJuego espera tener que tratar sólo un OXOMovimiento: es decir, la especialización de una clase espera tratar con las especializaciones compatibles de las otras clases. Piense las causas e implicaciones fundamentales de este problema, y cómo podría afectar al diseño y uso del marco de trabajo.
Capítulo
17 Simulación de eventos discretos
Este capítulo se centra en un paquete de aplicación significativo, que forma parte de un entorno de soporte de modelado integrado (ESMI) orientado a objetos para apoyar la simulación de eventos discretos en el estilo del proceso. Un ESMI apoya el desarrollo de aplicaciones especializadas llamadas modelos de simulación, que utilizan los que realizan las pruebas, los cuales se apoyan a su vez en la herramienta. Se tratará cómo el ESMI apoya el desarrollo de aplicaciones proporcionando un marco de trabajo para que sea utilizado por el desarrollador; no se describirá cómo funciona la interacción entre los usuarios y la herramienta. A diferencia del caso tratado en el capítulo anterior, existe una bibliografía bien desarrollada sobre qué clases se necesitan para apoyar la simulación de eventos discretos en el estilo del proceso. Un desarrollador que construye un ESMI puede aprovechar esto, por lo que este capítulo sólo habla brevemente de la identificación de clases. Se centra en la utilización de UML para comprender y documentar el comportamiento detallado de un sistema de clases. En este caso, es particularmente importante dicha documentación, debido a que un desarrollador que puede no estar familiarizado con las convenciones que se siguen necesita saber cómo utilizar de forma efectiva las clases proporcionadas por el ESMI. Las interacciones entre los objetos son bastante sutiles, y hay clases con diagramas de estado complejos (anidados), por lo que la documentación de UML detallada es probable que le sea útil a un desarrollador. Al final del capítulo se tratará un ejemplo sencillo de un modelo de simulación que puede crear un desarrollador utilizando el ESMI.
17.1
Requisitos Un modelo de simulación de eventos discretos es un programa ejecutable que simula el comportamiento de un sistema real siguiendo el patrón de eventos e interacciones que se dan en ese sistema. Siguiendo las convenciones de la vista del proceso de la simulación, los modelos
204
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
contendrán entidades activas, que se corresponderán con las partes activas del sistema que está siendo modelado, y entidades pasivas, que se corresponderán con los recursos, colas, y otras partes no activas de dicho sistema. Un modelo de simulación debe mantener el orden de ocurrencia de los eventos; los eventos simulados en el modelo deben suceder en el mismo orden que los eventos reales que simulan. Tal y como se ha dicho, el ESMI debe apoyar tanto el desarrollo de modelos de simulación de eventos discretos como su utilización en experimentos. Desde el punto de vista del desarrollador, puede verse como una herramienta CASE especializada para el desarrollo de modelos de simulación. Un buen ESMI debería minimizar la cantidad de trabajo que debe realizar un desarrollador para construir un modelo de simulación. Por ejemplo, aunque un modelo contendrá diferentes entidades, dependiendo del sistema que se represente, la mayor parte del comportamiento es común. Por lo tanto, el ESMI debería incluir las clases que proporcionen toda la funcionalidad posible de las entidades activas y pasivas. Además, debería proporcionar utilidades para recuperar las estadísticas utilizadas habitualmente, y debería mantener la planificación de los eventos y de las interacciones entre entidades de la manera más genérica posible. El ESMI también tiene que proporcionar un editor que soporte la construcción de modelos ejecutables basados en este marco de trabajo, pero aquí no se trata esta parte. Desde el punto de vista del que realiza las pruebas, la herramienta debe soportar la ejecución de un modelo de simulación de eventos discretos construido a partir de estas funciones reutilizables y de la recolección de información sobre lo que ocurre mientras se ejecuta. Esta información debe incluir un registro de los eventos que ocurren en el modelo, conocido como traza, y resúmenes estadísticos de las observaciones formadas por valores importantes durante la ejecución de un modelo.
17.1.1
Descripción más detallada
En la siguiente descripción, las cláusulas nominales están subrayadas como preámbulo para la identificación de objetos candidatos. Se ha realizado una cierta preselección, por ejemplo para evitar incluir actores obvios. Los detalles de cómo se debe ejecutar un modelo están basados en mecanismos estándares de la simulación de eventos discretos.
Usuarios Hay dos tipos de usuarios: • Desarrolladores, son las personas que construyen modelos de simulación y comprueban que funcionan sin errores: • Realizadores de pruebas, tienen los siguientes objetivos: 1. Mirar siempre el despliegue de los eventos de un modelo bajo condiciones controladas; 2. Recoger a veces estadísticas sobre lo que ocurre. Ambos objetivos implican la ejecución del modelo de simulación.
SIMULACIÓN DE EVENTOS DISCRETOS
205
Modelos de simulación La vista del proceso divide un sistema modelado en entidades activas y entidades pasivas. Las entidades activas representan los elementos del sistema modelado que ejecutan actividades, como los trabajadores en una fábrica. Cuando un trabajador de la vida real hace algo significativo que afecta a otras cosas de la vida real, la entidad activa que modela a dicho trabajador provoca un evento simulado que afecta a cualquier entidad (activa o pasiva) que modela estos elementos del mundo real. Los recursos, semáforos y buffers son entidades pasivas típicas. Representan elementos en el sistema modelado que, aunque no son activos por sí mismos, pueden afectar al comportamiento de las entidades activas de forma significativa. También observan y realizan informes estadísticos sobre su estado a lo largo del tiempo. Por ejemplo, la capacidad que tiene una entidad activa para ejecutar alguna actividad muchas veces depende de si actualmente hay disponible una cierta cantidad de algunos recursos. Si la entidad activa solicita una cantidad que está disponible, esta entidad activa pasa a un estado que indica que está ejecutando la actividad, y este recurso pasa a un estado donde hay disponibles menos recursos para siguientes peticiones. Si la cantidad requerida no está disponible, la entidad activa tendrá que esperar hasta que termine otra entidad activa con suficientes recursos que le permitan proceder; es decir, la entidad activa se bloquea debido al estado del recurso. La cantidad media del recurso utilizado es una estadística de interés. El comportamiento de un tipo de entidad activa está definido por una secuencia de eventos a simular, a menudo denominado su ciclo de vida. Esta secuencia puede incluir condicionales y repeticiones. En cualquier punto de la simulación una entidad activa está en uno de los tres estados siguientes: 1. activo, donde responde a un evento en su ciclo de vida; sólo puede haber una entidad activa en este estado en cada momento y su tiempo de evento define el tiempo simulado actual. 2. bloqueado, esperando a que una entidad pasiva satisfaga una petición. 3. esperando que el tiempo simulado alcance el siguiente tiempo de evento para dicho objeto —en este estado siempre se sabe en qué tiempo simulado se va a planificar el siguiente evento simulado y cuál será dicho evento. Los eventos simulados surgen como mensajes, bien desde un planificador que controla el avance en el tiempo simulado, o bien desde una entidad pasiva cuyo estado ha cambiado debido a, por ejemplo, la liberación de un recurso por parte de otra entidad activa. Cualquier evento simulado podría provocar el envío de un mensaje a un archivo de trazas, de manera que quien realice las pruebas pueda seguir el comportamiento interno detallado del modelo. Se necesita recopilar estadísticas mediante la actualización de información sobre entidades pasivas y sobre otros valores para los que se necesita información. Los ejemplos de la monitorización de valores y sus estadísticas derivadas son los cálculos de cuántas veces tiene lugar un elemento y el valor medio a lo largo del tiempo de un elemento, como por ejemplo puede ser la longitud de una cola.
206
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
Las condiciones bajo las cuales se ejecuta un modelo se modifican para poder observar cómo respondería el sistema. Para hacer esto más flexible, se leen los valores a modificar desde un conjunto de datos externo, que se establece antes de que se ejecute el modelo.
17.2
Esquema general del modelo de clases Si se estuviera diseñando el paquete sin ningún conocimiento previo, el siguiente paso sería encontrar las clases candidatas, pudiendo empezar con los nombres subrayados en la descripción del problema. Como siempre, se utilizarían tarjetas CRC y/o descripciones de casos de uso para evaluar la selección. En realidad, tal y como se ha dicho, la selección de clases para este tipo de aplicaciones se entiende bien, por lo que no se va a explicar. Sin embargo, se tratará el modelo de clases más tarde con mayor detalle. El diagrama de clases de alto nivel se muestra en la Figura 17.1.
P: Si se sabe sobre la simulación de eventos discretos, ¿cómo se añadirían al modelo los flujos de números aleatorios?
P: ¿La asociación EntidadPasiva actualiza Estadística es realmente una agregación o una composición? Justifique la respuesta.
1..*
1
Planificador
Informe
invoca
1
1
1 solicita resumen de
solicita tiempo de
cálculo
1..*
1
Estadística {abstract} 1
1..* media
EntidadActiva {abstract} 1
1..*
solicita a
Cálculo
dirigido por
1
1 ConjuntoDe DatosExterno
Planifica
1..* 1
0..*
EntidadPasiva
set by
Recurso
Figura 17.1
Memoria intermedia (búfer)
Diagrama de clases de un sistema de simulación de eventos discretos.
SIMULACIÓN DE EVENTOS DISCRETOS
207
Pregunta de Discusión 108 Una versión anterior del modelo de clases presentaba Estadística y EntidadPasiva como clases distintas, relacionadas por el hecho de que ambas proporcionan la interfaz requerida por Informe. En otra versión, Estadística era una generalización de EntidadPasiva. Se ha decidido mostrar una asociación actualiza, donde cada EntidadPasiva utiliza un objeto Estadística. Las últimas variantes de esta parte del modelo de clases se muestran en la Figura 17.2. ¿Cuáles son los pro y los contra de estas diferentes maneras de modelar las clases?
<> Grabador
Informe 1 Resumen
1
sumarizados para sumarizados para 1..* 1..*
Estadística
Cálculo
Resumen
EntidadPasiva
Media
Memoria intermedia
Recurso
(a) Utilización de una interfaz para mostrar el comportamiento común.
sumarizados para
Grabador 1
Informe 1..*
Estadística
Cálculo
EntidadPasiva
Media
Recurso
Memoria intermedia
(b) Una manera de utilizar la generalización para mostrar el comportamiento común.
Figura 17.2
Algunas alternativas para las clases utilizadas en la creación del informe del comportamiento.
208
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
Pregunta de Discusión 109 Pruebe a implementar el comportamiento requerido por Informe, Estadística y EntidadPasiva siguiendo las tres alternativas presentadas. ¿Qué diseño soporta mejor su lenguaje de implementación?
17.3
Casos de uso Los desarrolladores crean modelos y los ejecutan para comprobar los errores en tiempo de ejecución. Quienes realizan las pruebas desean obtener información. Ejecutan los modelos para observar el comportamiento detallado y, opcionalmente, recoger estadísticas. Antes de ejecutar un modelo, establecen siempre valores en algún conjunto de datos externo para especificar las condiciones a modelar. Esto lleva al diagrama de casos de uso que aparece en la Figura 17.3. Los tres casos de uso principales son crear modelo, observar comportamiento y recoger estadísticas. Todos ellos dependen del comportamiento definido para ejecutar un modelo, por lo que una manera natural para modelar esto es hacer que ejecutar un modelo sea un caso de uso interno independiente, que incluye tanto crear modelo como observar comportamiento. Ya que recoger estadísticas es una variante de observar comportamiento, el primero amplía el segundo.
Desarrollador crear modelo <>
observar comportamiento
ejecutar un modelo <>
<<extend>> RealizadorDe Pruebas recoger estadísticas
Figura 17.3
Diagrama de casos de uso de un sistema de simulación de eventos discretos.
SIMULACIÓN DE EVENTOS DISCRETOS
17.3.1
209
Resumen de crear modelo
El actor Desarrollador crea un modelo ejecutable inicial, del tipo que necesita el caso de uso ejecutar un modelo, mediante la interacción con las funciones de edición de la herramienta. El/ella crea entonces un conjunto de datos que permita que el modelo se ejecute de una manera controlada y se compruebe el modelo siguiendo el comportamiento definido en el caso de uso ejecutar un modelo. Si hay errores cuando se ejecuta el modelo, el Desarrollador modifica el modelo y lo ejecuta de nuevo. Una vez que no hay errores el caso de uso está completo.
17.3.2
Resumen de observar comportamiento
El actor RealizadordePruebas selecciona primero un modelo. Se supone que ya existe como salida de una instancia anterior del caso de uso crear modelo. El RealizadordePruebas selecciona o crea un conjunto de datos externo que mantiene los valores que describen las condiciones para esta ejecución particular del modelo, tales como las cantidades de recursos y las duraciones de cualquier retardo variable en el modelo. El RealizadordePruebas sigue entonces el comportamiento en el caso de uso ejecutar un modelo y, cuando el modelo se ha ejecutado, lee la traza generada para seguir la secuencia de eventos.
17.3.3
Resumen de recoger estadísticas
Es una variante del caso de uso observar comportamiento. Antes de utilizar el caso de uso ejecutar un modelo, el RealizadordePruebas establece un indicador que puede leer el modelo cuando se ejecuta, para indicar que deben recogerse las estadísticas y crear los informes de las mismas escribiéndolas en un fichero al final de la ejecución.
17.3.4
Resumen de ejecutar un modelo
Este caso de uso, se asume que el actor que lo inicia ha seleccionado previamente el modelo a ejecutar y ha creado o seleccionado un conjunto de datos adecuado, que especifica las condiciones bajo las cuales se ha de ejecutar el modelo. El actor ahora inicia el modelo ejecutable, que está formado por instancias de los objetos utilizados para modelar y observar el sistema. Cada entidad activa se inicializa a un tiempo de evento simulado y a un evento simulado siguiente, que se lee desde un conjunto de datos externo. Cada entidad pasiva lee sus configuraciones iniciales desde el mismo conjunto de datos externo. Las entidades llevan a cabo el comportamiento detallado especificado en la descripción del ciclo de vida de cada entidad activa, bajo el control del planificador, que asegura que los eventos se suceden en orden según su tiempo simulado. Las entidades pasivas aseguran que se cumplen las restricciones en la capacidad que tienen las entidades activas para actuar. La ocurrencia de eventos simulados provoca la salida de la traza de mensajes.
210
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
Donde un evento cambie un valor solicitado por un informe y con una indicación de recolección de estadísticas activada, se añadirá la información necesaria para que los mecanismos de informes lo utilicen. Puede haber un error en tiempo de ejecución en cualquier parte si el modelo contiene errores. Esto se comprueba en el caso de uso crear modelo y se trata ahí mismo. Por simplicidad, se asume que un RealizadordePruebas nunca encuentra tales errores.
P: Genere un diagrama de actividad que muestre las interacciones y dependencias entre todos los usuarios potenciales del sistema completo. P: ¿Cómo podría cambiar el modelo de casos de uso si se descubre que todos los RealizadordePruebas construyen siempre sus propios modelos cada vez que quieren ejecutar alguno de sus casos de uso actuales?
17.4
Mecanismos estándar para la simulación basada en el proceso Para soportar el diseño detallado de las clases, se describe con cierto detalle el mecanismo de la simulación de eventos discretos que se emplea. Es un estándar de la bibliografía del tema, como es [21]. El modelo se crea como una colección de entidades activas y entidades pasivas, más un planificador de control y unos objetos de recogida de estadísticas apropiados. Las entidades activas leen cualquier valor inicial, como son los tiempos de retardo de esta ejecución, del conjunto de datos externo, e introducen su estado de espera. La entrada desde el conjunto de datos externo la utilizan también las entidades pasivas para establecer sus valores iniciales —por ejemplo, especificar una cantidad inicial para cada recurso—. La entidad activa con el menor tiempo de evento simulado recibe un mensaje del planificador e introduce su estado activo. Esta entidad activa almacena el evento que se está activando, con su tiempo de evento, en el seguimiento de esta ejecución del modelo. El tiempo de evento de la entidad activa desactivada actualmente siempre define el tiempo de simulación actual. Si el evento actual implica una interacción con una entidad pasiva —por ejemplo, la obtención del uso de un recurso— la entidad activa envía un mensaje adecuado a la entidad pasiva. Si el mensaje es una petición que, dependiendo del estado actual de la entidad pasiva, podría bloquear la entidad activa: 1. o bien la entidad pasiva se actualiza para indicar cómo satisface la petición —por ejemplo, reduciendo la cantidad disponible del recurso que representa— y la entidad activa pasa a ejecutarse. 2. o bien la entidad activa espera que se cumpla la condición, introduciendo un estado bloqueado hasta que la entidad pasiva vuelva a planificar. El evento puede indicar también que una entidad pasiva debe actualizarse sin la posibilidad de que se bloquee la entidad activa actual —por ejemplo, cuando se ha liberado un recurso.
SIMULACIÓN DE EVENTOS DISCRETOS
211
Cuando se actualiza de esta manera una entidad pasiva, se debe planificar cualquier entidad activa que esté esperando por tal cambio y quién va a ser capaz en este momento de continuar, estableciendo su siguiente tiempo de evento al tiempo simulado actual. Cuando una entidad activa abandona su estado activo sin bloquearse, bien actualiza su siguiente tiempo de evento simulado y su siguiente evento, y vuelve a su estado de espera, o bien termina. La entidad activa que ahora tiene el siguiente tiempo de evento más bajo, es movida por el planificador a su estado activo por medio de mensaje. Este patrón se repite hasta que, bien no haya entidades activas sin terminar, o bien el tiempo de simulación exceda el período de simulado dentro del cual debe ejecutarse la simulación. Si la señal del informe se estableció antes de que el modelo empezara a ejecutarse, el planificador enviará entonces un mensaje a los diversos objetos que han estado recogiendo observaciones solicitándoles un informe de sus estadísticas, cada vez que haya pasado un determinado intervalo del tiempo simulado o al final de la ejecución del modelo. Por la presentación, estos objetos pueden ser recogidos dentro de sub-informes relacionados y la salida final formateada de forma correspondiente.
17.5
Asociaciones y navegabilidad ¿Qué objetos necesitarán conocer a qué otros objetos? En otras palabras, ¿cuáles son las asociaciones y su navegabilidad en el modelo de clases? Se ha empezado mostrando todas las asociaciones sin realizar ningún comentario, pero a medida que se alcanza un diseño detallado, adecuado para la implementación, hay que definir y justificar su significado y dirección. Al mismo tiempo, es necesario asegurar que sus atributos u operaciones son una manera de que los objetos conozcan a otros objetos. Tal y como se ha explicado en el Capítulo 6, la mayoría de las asociaciones son de un solo sentido. Se tienen que justificar las asociaciones de bidireccionales donde surjan. La Figura 17.4 muestra un diagrama de clases más detallado, que incluye los atributos y operaciones para soportar las asociaciones y su navegabilidad. Destacar que al mostrar estos atributos, cuyo propósito es implementar las asociaciones, se está rompiendo la norma que se nombró en el Capítulo 5 y que se ha estado siguiendo desde entonces. Se ha decidido hacer esto, a expensas de hacer el diagrama más complejo que se pueda hacer, porque el mecanismo para implementar las asociaciones es bastante sutil y se tratará en función de los atributos de las clases.
Planificador invoca Informe • Dirección Un sentido, desde Planificador hacia Informe. • Significado El Planificador tiene la responsabilidad de solicitar resúmenes de sus observaciones a los objetos Informe una vez que se ha alcanzado el final de la ejecución. • Implementación Un atributo Colección —informes— en el Planificador, y una operación —realizarInfome()— en Informe.
212
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
Planificador - informes: Collección - listaEv: ListaOrdenadaPorTiempo
1 1
+ ahora() : Real + esperar(e : EntidadActiva) + ejecutar(tiempoEjecución:Real) 1
invoca
1..*
solicita tiempo de 0..*
planifica 1
1..* EntidadActiva {abstract}
realizar peticiones a
+ seguir(m : Texto) + replanificar() + actuar() + obtenerTiempo() : Real
1 1 1..*
establecida por
0..*
1
1 ConjuntoDeDatosExternos
Estadística {abstract} # observaciones : Entero # reloj : Planificador # tiempoInicio : Real + reiniciar() + actualizar(r:Real) + realizarInforme()
actualiza
# tiempoEv : Real # eventoSiguiente : Entero # s : Planificador # entrada : ConjuntoDeDatosExterno
1..*
Informe - estadísticas: Colección + realizarInforme() 1 1..* solicita resumen de
dirigido por
Cálculo
Media
- total : Entero + actualizar(r:Real) + realizarInforme() + reiniciar()
- suma : Real + actualizar(r:Real) + realizarInforme() + reiniciar()
EntidadPasiva # bloqueado : listaFIFO # myEstadística : Estadística # entrada : ConjuntodeDatosExterno
+ darValor() : Real Recurso - útil : lógico
Memoria intermedia
+ adquirir(e:EntidadActiva) + liberar()
Figura 17.4
Diagrama de clases detallado para un experimento de simulación.
Planificador planifica EntidadActiva • Dirección Dos sentidos, ya que el Planificador necesita ser capaz de activar cada EntidadActiva cuando llega su turno, mientras que EntidadActiva también necesita encontrar el Planificador cuando tienen que volver a juntar la lista de eventos, tal y como se verá más adelante. • Significado El Planificador controla el orden de la ejecución de las EntidadesActivas para asegurarse de que siempre se ejecutan en el mismo orden de simulación. • Implementación Una colección ordenada de forma especial de las entidades activas, ListaEv: ListaOrdenadaPorTiempo, dentro del Planficador, y una operación actuar() en EntidadActiva. Un atributo p: Planificador en EntidadActiva y una operación —esperar()— en el Planificador. Para más detalles, véase más adelante.
SIMULACIÓN DE EVENTOS DISCRETOS
213
EntidadActiva dirigida por ConjuntoDeDatosExterno • Dirección Un sentido desde EntidadActiva hacia ConjuntoDeDatosExterno. • Significado Cada EntidadActiva lee su tiempo de evento inicial y el siguiente evento del ConjuntoDeDatosExterno. • Implementación El atributo entrada:ConjuntoDeDatosExterno en EntidadActiva, y la operación obtenerValor(): real en ConjuntoDeDatosExterno.
EntidadPasiva establecida por ConjuntoDeDatosExterno • Dirección Un sentido desde EntidadPasiva hacia ConjuntoDeDatosExterno. • Significado Cada EntidadPasiva lee sus valores iniciales del ConjuntoDeDatosExternos. • Implementación El atributo entrada:ConjuntoDeDatosExterno en EntidadPasiva, y la operación obtenerValor():real en ConjuntoDeDatosExterno.
EntidadPasiva actualiza Estadística • Dirección Un sentido, desde EntidadPasiva hacia Estadística. • Significado Cada vez que se realiza una petición a una EntidadPasiva, ésta puede enviar una actualización al objeto Estadística asociado. • Implementación El atributo miEstadística:Estadística en EntidadPasiva y la operación actualizar(r:Real) en Estadística.
EntidadActiva realiza petición a EntidadPasiva • Dirección Dos sentidos, ya que la EntidadActiva necesita realizar una petición inicial a la EntidadPasiva, mientras que la EntidadPasiva necesitará devolver un mensaje cuando la petición se haya satisfecho. • Significado El significado exacto de tal petición depende de la especialización de la EntidadPasiva a la que se le envía. • Implementación La del sentido desde EntidadActiva a EntidadPasiva se implementa con los atributos declarados en las especializaciones de la EntidadActiva, tal y como se requiere (que pueden hacer referencia a objetos EntidadPasiva), y por medio de operaciones de peticiones particulares en las especializaciones de EntidadPasiva. El otro sentido se implementa pasando self como parámetro con todos los mensajes de petición, por medio del atributo bloqueado en la EntidadPasiva (que lista las EntidadesActivas que están bloqueadas esperando por esta EntidadPasiva) y por medio de la operación replanificar() en EntidadActiva. Véanse más tarde los ejemplos.
Informe solicita resumen de Estadística • Dirección Un sentido desde Estadística hasta Planificador. • Significado Cuando el Planificador solicita un informe, cada objeto Informe delega la tarea de producir resúmenes individuales en los objetos apropiados que son especializaciones de Estadística.
214
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
• Implementación El atributo estadísticas:Colección dentro de Informe, y la operación realizarInforme() dentro de Estadística.
Estadística solicita tiempo de Planificador • Dirección Un sentido desde Estadística hasta Planificador. • Significado Se necesita almacenar las observaciones junto con el momento en que tienen lugar. • Implementación El atributo reloj:Planificador dentro de Estadística, que permite el envío de un mensaje ahora(). Pregunta de Discusión 110 No se ha tratado cómo se obtienen o implementan las clases colección; por ejemplo, se dio el atributo realizarInforme de la clase Planificador como Colección sin comentar nada. Supóngase que se está implementando este sistema, en cualquier lenguaje, y analice la disponibilidad de clases reutilizables que le ahorraría escribir su propia colección de clases. ¿Cómo pueden implementarse esas clases en su lenguaje, y qué diferencias provoca la técnica utilizada para el usuario de la clase? Por ejemplo, ¿se puede utilizar la construcción de clase parametrizada tratada en el Capítulo 6?
Pregunta de Discusión 111 Existen varias dependencias circulares en el diagrama de clases que aparece en la Figura 17.4. Identifíquelas, y analice qué cambios se podría hacer para eliminarlas.
17.6
Clases en detalle Hasta aquí se ha documentado la estructura estática del paquete de simulación de eventos discretos, y se han tratado las interacciones deseadas entre los objetos a alto nivel. A continuación, se debería documentar las clases principales con el suficiente detalle para que un Desarrollador pueda utilizarlo para construir un modelo. Los diagramas de estado resultarán particularmente útiles para la captura del comportamiento más o menos complicado de este diseño. También los diagramas de comunicación serán útiles, ya que el comportamiento de los objetos implica muchas interacciones entre ellos. Un Desarrollador debe crear una subclase especializada de la clase abstracta EntidadActiva, añadiendo un método que implemente la operación actuar(). Este método define el comportamiento de la entidad activa particular que el desarrollador desea modelar. De forma similar, un Desarrollador puede crear una subclase especializada de la clase abstracta EntidadPasiva. Aquí, sin embargo, son tan comunes ambas especializaciones que se proporcionan en el paquete. Por ejemplo, un Desarrollador que necesita modelar una entidad pasiva que se comporta como un único recurso discreto, que puede ser utilizado cada vez por una sola entidad activa, puede utilizar la clase Recurso directamente en vez de tener que escribir más código.
SIMULACIÓN DE EVENTOS DISCRETOS
215
Se presentará un ejemplo de un modelo de simulación construido de esta manera que implica una especialización de la EntidadActiva junto con algunas instancias de Recurso.
17.6.1
Clase Planificador
Hay una única instancia del Planificador en un modelo de simulación. Es el primer objeto que recibe un mensaje —ejecutar(tiempoEjecución:Real)— y la operación que se invoca controla la ejecución del modelo. El Planificador tiene los atributos: • Informes: la Colección de grupos de objetos de los que se puede realizar un informe; • listaEv: la ListaOrdenadaPorTiempo, que mantiene las EntidadesActivas en orden según su tiempoEvento. También soporta las operaciones: • ejecutar(tiempoEjecución:Real): la función de control de toda la simulación. Toma el primer elemento de listaEv y le envía un mensaje actuar(). Esto se repite hasta que la simulación alcanza una EntidadActiva cuyo valor de tiempoEvento excede tiempoEjecución —la duración que requiere la ejecución simulada— o hasta que no haya EntidadesActivas sin terminar. En este momento, envía los mensajes realizarInforme() a todos los objetos Informe de su colección de informes, solicitando la producción de informes en los objetos Estadística que haya. • ahora():Real: una función de consulta para devolver el tiempoEvento de la primera EntidadActiva que hay en ese momento, que define el tiempo de simulación actual. Esto permite acceder a esta información tan importante sin permitir el acceso directo a la lista propiamente dicha. • esperar(e:EntidadActiva), que proporciona el retorno a listaEv de las EntidadesActivas una vez que han abandonado su estado activo. Toma e y lo añade a listaEv en la posición correcta según el tiempoEv de e.
17.6.2
Clase EntidadActiva
La clase EntidadActiva tiene la propiedad abstracta, ya que contiene una operación ––actuar()—para la que no se proporciona ninguna implementación (ningún método) y que, por lo tanto, aparece en cursiva. No se puede generar ninguna instancia de EntidadActiva. Sólo se puede utilizar como una generalización y el modelo de simulación final utilizará las especializaciones donde se proporcione un método para actuar(). Contiene los atributos: • p: el Planificador. • entrada: el ConjuntoDeDatosExterno. • tiempoEvento: el tiempo simulado de su siguiente evento simulado; • evSiguiente: un código que representa su siguiente evento simulado. Todos ellos aparecen con una marca de protegido, el símbolo #. En C, se puede acceder a los atributos y operaciones protegidos de una clase desde los métodos de esa clase o cualquier
216
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
subclase. A veces esto es una restricción de acceso intermedia conveniente, más permisiva que la privada, pero menos que la pública; sin embargo, si no se utiliza dicho lenguaje se puede considerar que todos los atributos y las operaciones protegidos tienen acceso público, que se indica con . Tiene las operaciones: • obtenerTiempo():Real: una función de consulta, que devuelve el valor de tiempoEvento. • replanificar(): que situará esta EntidadActiva en la lista de eventos con el tiempo de simulación actual como su tiempoEvento. • actuar(): cuyo método tiene que proporcionarse en cualquier especialización y que define el comportamiento simulado de los componentes activos del sistema. La forma de este comportamiento se describe más adelante.
Estados de una EntidadActiva En general, una EntidadActiva seguirá el comportamiento sencillo que aparece en la Figura 17.5. Esto muestra el comportamiento de planificación de todas las instancias de EntidadActiva, que responden todas, en este nivel de abstracción, al estilo equivalente de los mensajes actuar() del Planificador.
actuar() en lista de eventos
activo hacer/detalleActivo
[no eventoSiguiente=Finalizado]p:esperar(self)
Figura 17.5
[eventoSiguiente=Finalizado]
Diagrama de estado de la EntidadActiva genérica.
Inicialmente, una EntidadActiva está en el estado en lista de eventos, que representa su espera en la listaEv del Planificador. Cuando recibe un mensaje actuar(), entra en su estado activo. En cuanto se termine su estado activo, bien termina o bien se envía a sí mismo al Planificador, p, en un mensaje esperar(self). Esto provoca su reentrada en la lista de eventos en la posición correcta. Una EntidadActiva tiene una máquina de estados interna, definida como detalleActivo. Esta máquina de estados está anidada dentro del estado activo de una EntidadActiva, ya que su nombre sigue la palabra clave hacer en ese estado. Representa el detalle especializado de actuar() y el Desarrollador debe definirlo en cada especialización de EntidadActiva que se vaya a instanciar en el modelo de simulación. Más adelante en este capítulo, en la Figura 17.7, se da un ejemplo típico. Una EntidadActiva siempre conoce el valor de su eventoSiguiente y lo utiliza para decidir qué camino seguir cada vez que entra en el estado detalleActivo. Cada bifurcación imprime un mensaje de seguimiento. Después de esto, el comportamiento depende del tipo de evento que se corresponde con eventoSiguiente.
SIMULACIÓN DE EVENTOS DISCRETOS
217
Simulación de los retardos en el tiempo Algunas de las transiciones sólo pasan del estado inicial al estado final, invocando acciones y enviando mensajes a otros objetos. Éstos implementan los retardos de tiempo simulados actualizando tanto el tiempo de evento siguiente (tiempoEv), como el evento que se va a dar en ese momento (eventoSiguiente).
Simulación de peticiones a EntidadesPasivas Otras transiciones envían peticiones a los objetos EntidadPasiva. Para permitir la implementación del bloqueo, todas las peticiones deben tener la EntidadActiva actual como parámetro. Todas estas transiciones llevan al estado bloqueado, que se abandona cuando se recibe un mensaje replanificar() desde la EntidadPasiva, indicando que la petición puede ser atendida. En el ejemplo de la Figura 17.7, la EntidadPasiva es un Recurso, cuyo comportamiento se explica más adelante.
P: Utilice un diagrama de comunicación para revisar a fondo la interacción de dos objetos EntidadActiva cuyo comportamiento es idéntico al mostrado pero que tienen variaciones distintas en sus tiempos de evento.
¿Cuántas secuencias diferentes de traza de mensajes pueden mostrarse dependiendo de esta combinación de tiempos de evento?
17.6.3
Clase EntidadPasiva
EntidadPasiva es una clase sin operaciones. Se ve como la generalización de todos los componentes pasivos de un modelo de simulación. Tiene los atributos:
• bloqueado: una lista primero-en-entrar-primero-en-salir que mantiene las EntidadesActivas bloqueadas. • miEstadística: una referencia a una especialización adecuada de Estadística, que puede actualizarse para almacenar cambios en el estado del objeto. • entrada: el ConjuntoDeDatosExterno, utilizado para establecer los valores iniciales de esta entidad, como es la cantidad inicial disponible en un recurso. Una vez más, los atributos están especificados como protegidos, indicando que van a estar disponibles sólo para las especializaciones de esta clase. Pregunta de Discusión 112 ¿Por qué no es apropiado definir EntidadPasiva como abstracta? ¿Es esto sensato?
17.6.4
Clase Recurso
Recurso es una subclase de EntidadPasiva. Un objeto de la clase Recurso representa algo que sólo una entidad activa cada vez lo puede utilizar como un todo. Además de heredar los atributos de EntidadPasiva, añade un atributo.
218
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
• disposición: lógico, que es verdadero si el recurso está disponible para su uso, y falso en otro caso. También entiende los mensajes: • adquirir (e:EntidadActiva): la petición para obtener un uso exclusivo de este recurso. • liberar(): el mensaje que indica que el recurso está libre de nuevo. Si una EntidadActiva quiere utilizar un Recurso cuando otra EntidadActiva lo está utilizando, el mensaje adquirir(e:EntidadActiva) llegará cuando disposición sea falso. En ese caso, el Recurso añade la entidad activa e a su lista bloqueado (heredada). Cada vez que el Recurso es liberado —es decir, recibe un mensaje liberar()— comprueba si tiene alguna EntidadActiva en su lista bloqueado: es decir, ¿hay alguna entidad activa esperando por este recurso? Si es así, se toma la primera entidad activa de la lista y le envía el mensaje replanificar() para informarle de que el recurso está disponible. Un efecto lateral de la necesidad de manejar de una manera justa una colección de EntidadesActivas que están todas ellas esperando por el mismo Recurso, es que lo que ocurre cuando una EntidadActiva quiere un Recurso cuando éste está disponible es más complejo de lo que uno puede pensar. Cuando un Recurso recibe el mensaje adquirir (e:EntidadActiva) mientras disposición es verdadero, reacciona poniendo disposición a falso y enviando el mensaje replanificar() a la EntidadActiva. Cuando recibe la respuesta a replanificar(), en su turno contesta al mensaje original adquirir(self). El tema es que la señal que le indica a la EntidadActiva que el Recurso está disponible no es el hecho de recibir la respuesta al mensaje adquirir(self) —esto ocurre rápidamente tanto si el Recurso está disponible o no— sino el hecho de recibir el mensaje replanificar(). Se puede modelar este comportamiento de un Recurso utilizando el diagrama de estado que aparece en la Figura 17.6.
adquirir(e:EntidadActiva)/e.replanificar() en uso
disponible liberar()[bloqueado.vacio()]
aquirir(e:EntidadActiva)/bloqueado.añadir(e)
Figura 17.6 Diagrama de estado de Recurso.
Considérese un ejemplo sencillo de interacción entre una entidad activa y una entidad pasiva. Supóngase que un Desarrollador está modelando a dos trabajadores que tienen que compartir una llave inglesa, que se encuentra en un banco entre ellos. El Desarrollador define una subclase especializada de EntidadActiva llamada Trabajador y escribe (posiblemente ayudándose con parte de la herramienta CASE, que aquí no se trata) el método actuar(). Debido a que un Trabajador necesita utilizar una llave inglesa, habrá también un nuevo atributo llaveInglesa en la clase Trabajador. El diagrama de estado de alto nivel de la clase Trabajador es el mismo que el de su padre, la clase EntidadActiva, que aparece en la Figura 17.5, pero el comportamiento detallado en el diagrama de estado anidado de
SIMULACIÓN DE EVENTOS DISCRETOS
219
[eventoSiguiente=1]/traza(“Inicio”) /tiempoEv.=tiempoEv+2/eventoSiguiente.=2
[eventoSiguiente=3]/traza(“Trabajo”)/tiempoEv:=tiempoEv+2/eventoSiguiente:=4 [eventoSiguiente=4]/traza(“Terminado”)/ eventoSiguiente:=Terminado/llaveInglesa.liberar()
bloqueado [eventoSiguiente=2]/traza(“Adquirir”)/ eventoSiguiente:=3/llaveInglesa.adquirir(self)
replanificar()/tiempoEv:=p.ahora()
Figura 17.7. Diagrama de estado de detalleActivo de la clase Trabajador.
detalleActivo es específico de la clase Trabajador. Se muestra un posible detalleActivo en la Figura 17.7.
Debido a que la llave inglesa no tiene ningún comportamiento especial por sí mismo, puede modelarse mediante una instancia de la clase Recurso cuyo diagrama de estado se mostró en la Figura 17.6. Uniendo los comportamientos, vemos lo que ocurre cuando un objeto ruth de la clase Trabajador quiere utilizar el objeto llaveInglesa de la clase Recurso, pero jo lo está utilizando. No se ha dado mucho detalle de la clase Trabajador, pero se puede asumir que la espera de ruth por la llave inglesa se modela con el eventoSiguiente de ruth a 2 cuando recibe el mensaje actuar() del planificador. De acuerdo con el diagrama de estado, el eventoSiguiente de ruth se pone a 3, y entonces ruth envía el mensaje llaveInglesa.adquirir(self) a llaveInglesa, pasando a su estado bloqueado. Se asume que jo está utilizando la llave inglesa, por lo que llaveInglesa está en su estado en uso. De acuerdo con su diagrama de estado, su reacción al recibir el mensaje es añadir a ruth a su lista bloqueado. Al recibir la respuesta a su mensaje llaveInglesa.adquirir(ruth), ruth contesta al mensaje actuar() que recibió del Planificador, por lo que el Planificador enviará actuar() a la siguiente EntidadActiva. Finalmente (¡esperemos!) jo termina de utilizar la llave inglesa y, como parte de su reacción ante la recepción de un mensaje actuar(), envía el mensaje liberar() a llaveInglesa. Llegados a este punto, de acuerdo con el diagrama de estado para Recurso, llaveInglesa debe obtener la siguiente EntidadActiva de su lista bloqueado y enviar el mensaje replanificar() a dicha EntidadActiva. Si se asume que ruth es la única EntidadActiva bloqueada, el resultado es que ruth recibe el mensaje replanificar(). Según el diagrama de estado de Trabajador, ruth establece su tiempoEv a p.ahora(), el tiempo actual simulado, y después alcanza el estado final de detalleActivo. Como siempre, cuando se abandona detalleActivo con un valor de eventoSiguiente distinto de Terminado, envía un mensaje esperar(ruth) al Planificador, indicando que hay que volver a juntar listaEv.
P: Dibuje un diagrama de secuencia que ilustre este escenario. Probablemente le será útil mostrar las flechas de retorno de mensaje y utilizar la notación de activaciones anidadas.
P: Examine la interacción un poco más sencilla en la que ruth quiere utilizar la llaveIglesa y está disponible.
220
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
P: Dibuje el diagrama de secuencia que muestra la interacción de dos objetos Trabajador que, al igual que el mostrado, comparten una llave inglesa, pero tienen diferentes patrones de intervalos de tiempo. ¿Cuántas secuencias diferentes de mensajes de traza pueden darse en este ejemplo? Pregunta de Discusión 113 Se ha asumido que un Recurso modela un único elemento. ¿Cómo podría ampliarse el diseño para poder representar los recursos de los que se puede solicitar o liberar más de uno? Pregunta de Discusión 114 ¿Se podría describir sistemáticamente cómo comprobar la consistencia entre diagramas de estado y diagramas de interacción en un ejemplo como este? ¿Cuánto podría ayudar una herramienta CASE?
17.7
Clase Informe Informe se utiliza para proporcionar una agrupación de informes estadísticos individuales. Cada instancia agrupará algunas instancias de la clase Estadística. Esta clase tiene un atributo:
• estadísticas: una Colección de objetos Estadística. Tiene una operación: • realizarInforme(): que invoca el Planificador; primero escribe un encabezado para este grupo de resúmenes y después solicita un resumen de cada uno de los objetos Estadística de estadísticas, enviando un mensaje realizarInforme() de uno en uno.
17.8
Clase Estadística Estadística es abstracta, ya que tiene operaciones para las que no proporciona un método. Esto se puede implementar en cualquier especialización. Proporciona tres atributos, para que se utilicen en las especializaciones: • observaciones: un entero que almacena cuántas veces se actualiza el valor observado; • tiempoInicio: un valor Real, que almacena cuándo se empieza a recoger el conjunto actual de observaciones, en función del tiempo simulado. • reloj: una referencia al Planificador, que le permite al objeto consultar el tiempo de simulación actual, enviando un mensaje reloj.ahora(). Al igual que con algunas de las otras clases, estos atributos se especifican como protegidos, ya que se proporcionan sólo para que se utilicen en especializaciones de esta clase.
Tiene tres operaciones, todas sin una implementación, por lo que se muestran en cursiva: • reiniciar(): que permite reiniciar los atributos del objeto a cero en cualquier momento; puede utilizarse para limitar el período en el que se recogen las estadísticas;
SIMULACIÓN DE EVENTOS DISCRETOS
221
• realizarInforme(): que permite al objeto Informe mantener este objeto Estadística en su colección para solicitar que se presente un resumen de sus observaciones; • actualizar(r:Real): que permite enviar el valor de una observación a este objeto, para que sea incluido en su informe.
17.8.1.
Clase Promedio
Promedio es un ejemplo de una especialización de Estadística, que en realidad se utilizó como ejemplo en el Capítulo 12. Implementa las operaciones reiniciar(), realizarInforme() y actualizar(r: Real) de Estadística. También añade el atributo:
• suma: un Real utilizado para acumular la suma de los valores de sus observaciones. Las respuestas a los tres mensajes que puede recibir son los siguientes: • Un mensaje reiniciar(), provoca el establecimiento de los valores de suma y observaciones a cero, y tiempoInicio se establece al tiempo de simulación actual, reloj.ahora(). • Un mensaje relizarInforme(), hace que imprima la media desde el último realizarInforme() o reiniciar(), y después se comporte como si hubiese recibido reiniciar(). • Un mensaje actualizar(r: Real), trae una nueva observación, en forma de un nuevo valor que se incluye en el promedio —los atributos suma y observaciones deben ser actualizados de forma apropiada, pero tiempoInicio no se ve afectado. Un diagrama de estado para este comportamiento, que se vio en el Capítulo 12, se reproduce en la Figura 17.8 1. realizarInforme()/imprimirResumen()/suma:=0/observaciones:=0
entrada / iniciarTiempo := ahora() actualizar(val : Real) / suma := suma + val/observaciones++
reiniciar() / suma := 0 / observaciones := 0
Figura 17.8 Diagrama de estado de Promedio.
1 Hay que confesar que se ha dibujado de esta manera porque en el Capítulo 12 se querían mostrar las secuencias de acciones que aparecen en las flechas de transición —probablemente sería más claro poner el reiniciado de suma y observaciones en la acción entrada.
222
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
Pregunta de Discusión 115 ¿Cómo se podría representar esta información en vez de utilizar un diagrama de estado con un único estado?
17.9
Construcción de un modelo de simulación completo Un modelo de simulación de eventos discretos completo, que se construye utilizando nuestro paquete, dará lugar a una colaboración entre un cierto número de objetos. Debe incluir un único Planificador y una o más instancias de especializaciones de la clase EntidadActiva. Cada una de éstas requiere un comportamiento totalmente definido para la máquina de estados detalleActivo siguiendo el patrón del ejemplo anterior. Si se asume que se va a generar un informe, se debe tener un objeto Informe. Los modelos más interesantes tendrán también una o más instancias de especializaciones de EntidadPasiva y, probablemente, de Estadística.
17.10
La cena de los filósofos Se llega al final de este estudio de casos con un ejemplo basado en el modelo de la cena de los filósofos. Este famoso ejemplo tiene un círculo de filósofos que pasan el tiempo primero, pensando grandes ideas y después, en intervalos variados, comiendo de un bol común de espaguetis que está en medio del círculo. Para comer, cada filósofo necesita coger dos tenedores, uno a la derecha del filósofo, otro a la izquierda. Un filósofo siempre intenta coger primero el tenedor de la derecha, y después el de la izquierda. Si él/ella consigue obtener ambos tenedores, pasa algún tiempo comiendo antes de devolver los tenedores. Esta situación la presentó el famoso informático Edsger Dijkstra como ejemplo de un sistema que puede tener interbloqueo. Puede describirse utilizando un modelo de simulación de eventos discretos, mediante la definición de una nueva subclase de EntidadActiva llamada Filósofo, y mediante el modelado de cada tenedor como una instancia de Recurso. El diagrama de comunicación, Figura 17.9, muestra el escenario en el que platón consigue obtener y liberar sus dos tenedores, y ni sartre ni hegel tienen hambre durante este período. Sólo aparece un escenario; también se omiten los objetos informe relacionados para mayor claridad. (Se añaden fácilmente: podría considerar cómo).
P: Dibuje el diagrama de secuencia que se corresponde con la Figura 17.9. P: Dibuje la ampliación del modelo si marx decide unirse al círculo. ¿Qué necesita llevar consigo? El diagrama de estado detalleActivo de un Filósofo aparece en la Figura 17.10.
SIMULACIÓN DE EVENTOS DISCRETOS
223
3.2 : liberar()
3.1 : liberar()
2.1 : adquirir(platón)
1.1 : adquirir(platón)
platón : Filósofo 1 : [platón.tiempoEv>=p.ahora()]actuar()
2 : [platón.tiempoEv>=p.ahora()]actuar()
3.1.1.1 : esperar(platón)
2.1.1.1 : esperar(platón)
1.1.1.1 : esperar(platón)
tenedor 1 : Recurso
3 : [platón.tiempoEv>=p.ahora()]actuar()
2.1.1 : replanificar() 1.1.1 : replanificar()
tenedor 3 : Recurso
p : Planificador hegel : Filósofo sartre : Filósofo
tenedor 2 : Recurso
Figura 17.9
Diagrama de comunicación de la cena de los filósofos.
[eventoSiguiente=1]/traza(“Pensar”)/tiempoEv:=tiempoEv+2/eventoSiguiente:=2
[eventoSiguiente=4]/traza(“Comer”)/tiempoEv:=tiempoEv+2/eventoSiguiente:=5 [eventoSiguiente=5]/traza(“Terminado”)/eventoSiguiente:=1/deTenedor.liberar()/lTenedor.liberar() [eventoSiguiente=2]/traza(“F1”)/eventoSiguiente:=3/dTenefor.adquirir(self) bloqueado replanificar()/tiempoEv:=s.ahora() [eventoSiguiente=3]/traza(“F2”)/eventoSiguiente:=4/dTenefor.adquirir(self)
Figura 17.10 Diagrama de estado detalleActivo de la clase Filósofo.
Pregunta de Discusión 116 Debido a que las acciones de nuestros modelos son todas secuenciales —nunca pueden darse dos acciones a la vez— se podría pensar que la cena de los filósofos nunca podría entrar en un interbloqueo. El interbloqueo requeriría que cada filósofo consiguiera obtener su tenedor de la derecha y se quedara esperando a que el filósofo de la izquierda liberase su tenedor. De hecho esto podría ocurrir aquí. ¿Cómo, y cómo podría solucionarse? Pregunta de Discusión 117 ¿Podría ampliarse el sistema de simulación para hacer frente a la ejecución del mismo modelo varias veces, bajo condiciones distintas, y recoger las estadísticas de las ejecuciones exitosas como filas de una tabla de informes? Intente modificar este diseño lo menos posible para producir tal sistema.
Parte
IV Hacia la práctica
Capítulo 18
Reutilización: componentes y patrones
227
Capítulo 19
Calidad del producto: verificación, validación y prueba
241
Calidad del proceso: gestión, equipos y GC
255
Capítulo 20
Capítulo
18 Reutilización: componentes y patrones
En este capítulo se resume lo que se ha dicho en el libro sobre reutilización, se vuelve a lo que el Capítulo 1 decía sobre componentes, marcos de trabajo y arquitectura una vez que se conoce el resto del libro, y se dirigen algunos temas no técnicos que se han ignorado hasta este momento. Hasta aquí se ha destacado la reutilización de componentes, pero hay otros tipos importantes de reutilización que se van a considerar aquí. En particular, se tratará el uso de patrones; esto se puede ver como la reutilización de buenas ideas, por ejemplo, de diseños prósperos.
18.1
Viabilidad de la reutilización En su sentido más amplio, el término reutilización cubre cualquier situación en la que se utiliza el trabajo realizado en un proyecto de software para ayudar a otro. Se dirigirán las preguntas (algunas de las cuales se empezaron a tratar en el Capítulo 1). 1. ¿Qué se puede reutilizar, y cómo? 2. ¿Por qué reutilizar? 3. ¿Por qué es difícil reutilizar? 4. ¿Qué componentes son verdaderamente reutilizables? 5. ¿Qué pasa con la construcción de componentes propios? 6. ¿Qué diferencias introduce la orientación a objetos? Una de las conclusiones es que una organización que quiera alcanzar altos niveles de reutilización debe tomarse en serio la calidad de sus productos y procesos, verificación, validación, y pruebas, y tener unas estructuras procedurales y organización correctas. Estos temas se tratarán en los dos capítulos restantes.
228
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
18.1.1
¿Qué se puede reutilizar, y cómo?
• Arquitectura, que se consideró en el Capítulo 1, incluyendo “marcos de trabajo (frameworks)”. • Código: los componentes más obvios, que ya se han empezado a tratar. • Diseños, incluso partes del análisis: véase patrones, más adelante. • Documentación: parte de los manuales de usuario y de los documentos de desarrollo tienden a ser texto “modelo”. • Pruebas: muchas son estándar, por ejemplo, qué pasaría si se agota la memoria o el disco. Algunas otras técnicas de almacenamiento de trabajo normalmente no se cuentan como reutilización. Por ejemplo, si un desarrollador aprende un nuevo lenguaje para un proyecto y ese lenguaje es el mismo que se utiliza para el siguiente proyecto, ¿es reutilización? Es uno de los aspectos beneficiosos de la reutilización de, al menos, la arquitectura: recordar que la decisión sobre qué lenguaje utilizar puede considerarse como una decisión de arquitectura. Para otro ejemplo piense en herramientas: es decir, código que no es parte directa del sistema construido, pero que puede utilizarse para ayudar en varios proyectos. El aprovechamiento de las pruebas es un ejemplo común. Pregunta de Discusión 118 ¿Qué pasa con las herramientas que son muy conocidas y fáciles de encontrar, como son los entornos de desarrollo o los sistemas de rastreo de errores? ¿Puede pensar en algún otro ejemplo de reutilización?
La reutilización puede alcanzarse de diferentes maneras. La más sencilla, se puede copiar y pegar, es decir, simplemente copiar el artefacto original. Se ahorra el esfuerzo de reescribir el artefacto reutilizado, pero los cambios que se realicen al original no se propagan automáticamente a la copia, o viceversa. A veces es sensato, pero en muchas situaciones puede traer problemas. Pregunta de Discusión 119 Piense en cada una de las áreas de reutilización antes nombradas. ¿Es deseable mantener las dos versiones sincronizadas? ¿Por qué, o por qué no?
Hoy día, la reutilización de componentes es casi el único área en el que se hace cualquier cosa, aparte de copiar y pegar. Incluso la reutilización de componentes está en pañales: es raro reutilizar componentes mayores que una clase individual. Las bibliotecas de clases, sin embargo, se utilizan normalmente. Aunque está claro que es más beneficioso reutilizar un componente mayor a una clase, tener una buena biblioteca de clases es una ventaja importante. Por ejemplo, las colecciones de diversos tipos se implementan como clases independientes, y no hay ninguna razón por la que ningún desarrollador tenga que escribirlas él mismo. Muchos lenguajes, como Smalltalk y Java, tienen bibliotecas estándar, que aumentan todavía más el beneficio. La biblioteca puede ser un producto comercial, o un recurso interno en la organización.
REUTILIZACIÓN: COMPONENTES Y PATRONES
PANEL 18.1
229
¿Qué es en realidad un componente? ¡Discutible!
“Componente” (y para este problema “arquitectura”) son palabras cuyo significado varía enormemente dependiendo de quién lo utilice. Consideremos un “componente”. La definición que se dio en el Capítulo 1 es una de las más amplias. Se dijo que un módulo es un componente si es reutilizable y sustituible; esto requiere que tenga una interfaz bien definida y que sea una abstracción cohesiva con débil acoplamiento con el resto del sistema. La definición aquí dada no es la más amplia: algunas personas piensan que cualquier objeto —o, a veces, cualquier clase— es un componente. Esto parece un mal gasto de vocabulario —¿por qué no decir simplemente “objeto” o “clase” si es lo que significa? Un candidato más interesante es la definición de Rational atribuida a Grady Booch [6]: Un componente es una parte del sistema, física y sustituible, que se ajusta y proporciona la realización de un conjunto de interfaces. Booch aclaró que “sustituible” quiere decir sustituible en tiempo de ejecución, y esta es la diferencia principal de la definición que se utiliza en este libro. Después de considerarlo, se ha preferido incluir en los módulos de definición que puedan ser sustituidos en tiempo de mantenimiento, sin requerir cambios en el resto del sistema. Ambas definiciones requieren que un componente sea conectable –la única pregunta es ¿cuándo? Mientras que UML1.x solía utilizar una definición muy similar a la de Booch, UML2 utiliza una idea de componente más amplia, como la aquí expuesta. Siempre es importante recordar que (con cualquier definición) un componente no funciona aislado; si es sustituible depende de la arquitectura, no sólo del componente. (La arquitectura es en sí, otro término discutible. Algunas personas diferenciarían lo que aquí se describe en arquitectura y estilo de arquitectura [41]). Aquí hay algunas definiciones más de componente: 1. Un tipo de alta calidad, clase u otro producto de trabajo de UML diseñado, documentado y empaquetado para reutilizarlo. Un componente es cohesivo y tiene una interfaz estable. Los componentes incluyen interfaces; subsistemas; casos de uso, tipos, actores o clases; y tipos de atributo. Los componentes también incluyen otros productos de trabajo, como son las plantillas o especificaciones de casos de prueba [29]. 2. Una parte encapsulada de un sistema software. Un componente tiene una interfaz que proporciona acceso a sus servicios. Los componentes sirven como bloques de construcción para la estructura de un sistema. A nivel de lenguaje de programación, los componentes pueden representarse como módulos, clases, objetos o un conjunto de funciones relacionadas [10]. 3. Un componente es un objeto que existe en el mundo binario. [5], como explicación de la definición de Booch [6] antes mencionada. Pregunta de Discusión 120 Piense en las definiciones y explique las diferencias entre ellas con la ayuda de ejemplos.
230
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
Pregunta de Discusión 121 ¿Puede un componente ser reutilizable sin ser sustituible, o viceversa? ¿Por qué? Pregunta de Discusión 122 ¿Un componente es algo que se puede instanciar, o algo que se obtiene después de haber instanciado otra cosa? ¿En qué caso, qué es esa otra cosa? Piense en la afirmación de Booch [6].
Para mayor información sobre componentes y desarrollo basado en componentes (dirigido a profesionales relativamente experimentados) están [44] y [16]. Para más información sobre arquitectura acudir a [41].
18.1.2
¿Por qué reutilizar?
Por supuesto, el fundamento es financiero. Algunas de las maneras con las que se alcanza altos niveles de reutilización y se permite a las empresas ahorrar dinero, son las siguientes. • La obvia: ¡el tiempo que los desarrolladores ahorran desarrollando el componente! Hay que darse cuenta que sólo una pequeña parte de este tiempo representa codificación. Análisis, diseño, revisiones, documentación, y pruebas son en general bastante más significativas. Además, sólo hay que desarrollar una buena solución una sola vez, y después lo puede reutilizar incluso gente que carece de experiencia para desarrollarlo por sí misma. • Los productos pueden ser más fiables, debido a la “depuración acumulativa” de los componentes reutilizados. Anteriormente otra persona ha quitado los problemas. Esto ahorra dinero al reducir el esfuerzo en las pruebas, depuración y mantenimiento; se puede incluso obtener una mayor fiabilidad desde el punto de vista del cliente, ya que puede verse aumentada la reputación de la organización y, por lo tanto, las ventas. • Tiempo de ciclo de mercado reducido. El hacer que el desarrollo de productos sea más rápido, no sólo ahorra tiempo a los desarrolladores, sino que puede también permitir a la organización ser la primera en explotar una oportunidad de mercado particular —aumentando beneficios. • El cambio hacia un estilo de diseño basado en componentes para alcanzar altos niveles de reutilización, puede centrar la atención de los desarrolladores en alcanzar diseños modulares; de manera que si en un producto en particular todos los módulos son nuevos, el producto puede ser aun más barato y fácil de mantener que en los días pre-CBD de la organización. • Los desarrolladores pueden perder menos tiempo realizando tareas rutinarias, y más en las tareas más interesantes e importantes para asegurarse de que se cumplen los requisitos de los clientes. De manera que los cambios en la plantilla pueden ser menores, ahorrando dinero, y puede aumentar la calidad, mejorando las ventas. (Este punto es pura especulación. Supone que los desarrolladores piensan en el cambio como en una mejora, cosa que es errónea dada la historia del desarrollo del software; probablemente se vuelve más
REUTILIZACIÓN: COMPONENTES Y PATRONES
231
significativo sólo en los niveles de reutilización mayores que los que las organizaciones alcanzan hoy día). Por supuesto, no es trivial alcanzar estos beneficios, en otro caso no tendríamos todavía una crisis del software...
18.1.3
¿Por qué es difícil reutilizar?
En este apartado, se considera la pregunta desde el punto de vista del usuario de un componente: esta es la perspectiva más importante, tal y como se discutirá más tarde. Incluso cuando un proyecto tiene acceso a una biblioteca de componentes, la reutilización no es automática. Para ver por qué, póngase en el lugar del desarrollador que busca un componente que cumpla alguna tarea, y piense las siguientes preguntas. • Incluso si existe un componente adecuado, ¿puede encontrarlo, y comprender su aplicabilidad? Una vez que las bibliotecas de componentes son mayores que unos pocos cientos de elementos, el problema de búsqueda es muy serio. Pregunta de Discusión 123 ¿Cómo se podría organizar e indexar una biblioteca de componentes, y qué facilidades de búsqueda debería haber disponible?
Una vez que se ha encontrado un componente candidato, algunas cuestiones más se vuelven relevantes: • ¿Hace lo que se quiere que haga? Si se busca un componente que realice un trabajo bien especificado, una vez que su diseño esté bien encaminado, la respuesta prácticamente segura es no. Lo ideal es analizar el problema con un cierto conocimiento de qué componentes hay disponibles, e incluso estar preparado para adaptar los requisitos para que coincidan con los componentes disponibles. Esto va en contra de varias décadas de práctica de ingeniería del software: tanto los clientes como los desarrolladores pueden estar descontentos con esto. Además, nos arroja en contra del punto anterior: ¿cómo se analizan los requisitos con un conocimiento de los componentes disponibles si hay muchos componentes? • ¿Se confía en él? Es bastante fácil reírse del síndrome del “No Inventado Aquí”, la desgana de utilizar algo que no ha sido desarrollado por uno mismo o por los compañeros —pero si se decide reutilizar un componente en vez de desarrollar un módulo nuevo, ¿a quién se le echa la culpa si no funciona? Está claro que puede no funcionar —e incluso si la fuente está disponible, si no lo ha desarrollado uno mismo, puede ser más difícil depurarlo que desarrollarlo desde el principio. O (no sería muy diferente) puede que funcionase, pero no estar documentado de forma adecuada, y puede que se malinterprete. Pregunta de Discusión 124 ¿Qué factores pueden o deben influir, cuando se decide si confiar o no en un componente?
232
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
• Un punto relativamente menos importante: puede que haya que importar cosas que no se necesiten junto con la funcionalidad que se quiere reutilizar, obteniendo un software no eficiente. Destacar que todos estos problemas (excepto el primero) pueden ser más severos cuanto mayor sea el componente —pero los beneficios fundamentales de la reutilización vienen al reutilizar grandes componentes.
P: ¿Por qué para hacer un trabajo, el reutilizar un gran componente es más beneficioso que reutilizar varios pequeños para realizar lo mismo? Pregunta de Discusión 125 Haga una lista de las características de los buenos componentes que ayudan a superar estos problemas. ¿Hay características relevantes de la organización que utiliza el componente? ¿Y de la organización que lo crea?
18.1.4
¿Qué componentes son verdaderamente reutilizables?
• Un componente debe ser suficientemente general, de una manera útil. En realidad, el error de hacer un componente demasiado general, con interfaces que son demasiado grandes y complejas —inventando requisitos— es más común que el error de no hacerlo suficientemente general. Hacer esto correctamente es difícil, y la consecuencia de hacerlo mal en cualquier dirección, es que el componente no será reutilizado. Al menos, si no se añadió ninguna característica extra y el componente nunca se reutiliza, no se ha perdido el tiempo que hubiese llevado añadirla... En caso de duda, prescinda de él. • Un componente debe estar documentado de forma adecuada con especificaciones. ¿Qué formato debería tener la documentación? Por definición, los componentes realizan interfaces y tienen dependencias de contexto; obviamente, ambos aspectos deben documentarse. En un entorno de UML, la documentación podría incluir, por ejemplo, un diagrama de casos de uso mostrando las tareas que soporta el componente. La pregunta de qué detalle debería almacenar los casos de uso es más difícil, particularmente si un caso de uso implica varios actores que deben comportarse de una manera determinada para que el componente funcione correctamente. Los diagramas de interacción pueden ayudar, al igual que los diagramas de estado, y al igual que las descripciones en español u otra lengua habitual. Realmente aquí hay un tema de caja blanca/caja negra, como en las pruebas. Estrictamente hablando, los usuarios no necesitarían —o incluso ni se les permitiría— conocer cómo está implementado el componente. Debería sólo depender de si hay alguna realización de la interfaz, siempre que sean satisfechas las dependencias de contexto. Esto sugiere, por ejemplo, que cualquier diagrama de interacción utilizado en la documentación debería mostrar el componente como una “caja negra” (realmente, como un componente UML, tal y como se describió en el Capítulo 13), que sólo interactúa con actores externos. Por otro lado, a los ingenieros del software, aun siendo personas que comprenden el diseño del software, a menudo les es más fácil comprender un componente a partir de la descripción de cómo funciona que a partir de la
REUTILIZACIÓN: COMPONENTES Y PATRONES
233
especificación de lo que hace. Siempre que el componente esté suficientemente encapsulado de manera que le sea imposible al usuario del componente confiar en una implementación particular, puede no haber ningún problema en utilizar una descripción de la implementación en la documentación. Pregunta de Discusión 126 En este caso, ¿debería ser veraz la descripción de la implementación? Por ejemplo, si el componente fue modificado más tarde de tal manera que la descripción queda anticuada, pero sin cambiar la interfaz, ¿es necesario actualizar la descripción?
• Un componente debe ser probado concienzudamente. Las consecuencias de un error en un módulo son mucho más importantes cuando el desarrollador del mismo no está cerca para ayudar a arreglarlo, y cuando el módulo se utiliza en varios lugares. Por lo que es importante que los componentes se prueben más concienzudamente que los módulos construidos para un único sistema.
18.1.5
¿Qué pasa con la construcción de componentes propios?
Hay implicados algunos riesgos extra (así como oportunidades extra) si la organización desarrolla sus propios componentes: esto afecta especialmente a las organizaciones que empiezan un programa de reutilización con poca experiencia. Tal y como se ha visto, los verdaderos componentes reutilizables tienen características que no surgen por accidente. Puede que se necesite un esfuerzo extra para construir tales componentes, comparado con el esfuerzo necesario para crear un componente de un solo uso. El componente debe documentarse y probarse de manera que no suponga, por ejemplo, que el lector o usuario esté familiarizado con las suposiciones del sistema dentro del cual se está desarrollando el componente. Si por alguna razón los componentes “reutilizables” no se reutilizan, el esfuerzo extra utilizado en construirlos se desperdicia, y va en contra de cualquier beneficio que se obtenga al realizar con éxito la reutilización. Lo más fácil de construir que sea “reutilizable”, y por lo tanto muchas veces, es lo primero que se hace, es inútil debido a que puede ser más barato obtener la funcionalidad comprándola. Por ejemplo, uno de los autores, una vez trabajó en una organización donde alguien empleó un esfuerzo considerable en desarrollar una clase lista enlazada “reutilizable” en C. Había disponible una biblioteca de clases de C comercial que compró la organización (un poco tarde) varios meses después con mejor funcionalidad. La moraleja tiene que ser que un programa de reutilización debería centrarse en aprender a utilizar componentes antes de intentar construirlos. No sólo es más difícil construir componentes, ¡sino que no tiene sentido construirlos si no van a ser utilizados! La utilización de componentes es más importante, más beneficiosa, y más fácil de hacer bien que su construcción.
234
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
La mayoría de los intereses industriales y académicos en el desarrollo basado en componentes se ha centrado en el desarrollo de componentes, no en su utilización. Esto no es sorprendente: es una antigua tradición en el software que la gente quiera construir nuevas cosas. Sin embargo, los beneficios del CBD surgen de la utilización de componentes, mientras que la mayoría de los costes se derivan de su construcción. Dado que el desarrollo de productos reutilizables requiere trabajo extra, ¿cuándo se puede hacer? La razón principal de por qué fallan los esfuerzos de reutilización es que a menudo la respuesta es “no puede” —al menos, no sin un cambio sustancial en la organización. Aquí hay algunas opciones (adaptadas a partir de [22], que a su vez cita [27]). • Al final del proyecto: ¿pero con qué recursos? • Durante el proyecto, de manera que el cliente paga el desarrollo de componentes que se utilizarán en el siguiente proyecto (y, presumiblemente, se beneficia del desarrollo de componentes en proyectos anteriores). Esto puede funcionar hasta que el proyecto se retrasa. Debido a que la mayoría de los proyectos lo hacen... • Cuando el tiempo lo permita: teniendo dos bibliotecas de componentes, una de las cuales contiene componentes “preparados” y otro contiene componentes “prototipo”, y poco a poco se van moviendo componentes de la biblioteca de prototipos a la terminada. • En paralelo con el desarrollo principal: tener un equipo permanente que haga el trabajo de reutilización. Esto requiere que la organización quiera crear este equipo; y en cualquier caso, ¿sus miembros pueden hacer el trabajo, sin ser los desarrolladores originales de los componentes en cuestión?
18.1.6
¿Qué diferencias introduce la orientación a objetos?
• OO alienta a que el estilo de fuerte cohesión/débil acoplamiento sea una buena práctica. A esto se le ha hecho demasiada propaganda, pero no es falso. • El análisis orientado a objetos se centra en los objetos del dominio del problema, que son más estables que la funcionalidad de una aplicación particular. • Un punto relacionado pero diferente, es que los objetos del dominio del problema, por su naturaleza, con frecuencia se repiten en diferentes contextos, por lo que se convierten en buenos candidatos a la reutilización. Una empresa puede desarrollar una colección de objetos de negocio que reflejan las entidades comunes de su propio negocio. • En OO reutilizar una clase puede que no signifique utilizar su funcionalidad tal y como es: una clase puede reutilizarse como clase base de una clase más especializada. Esto permite a los desarrolladores seguir la norma Abierto-Cerrado —esto es, desarrollar clases que están tanto cerradas (ahora estables y útiles), como abiertas (se pueden ampliar más tarde). Si se utiliza la idea del componente que permite que un componente herede de otro ––incluso si el componente está formado por más de una clase— entonces esta ventaja puede ampliarse a los componentes generales. Desafortunadamente, esta forma de reutilización no es tan beneficiosa como parece; y es dudosa (y discutible). El problema principal es que la herencia crea fuerte acoplamiento Por ejemplo, tal y como se verá en el siguiente capítulo, todas las funcionalidades de una subclase deben probarse, incluso
REUTILIZACIÓN: COMPONENTES Y PATRONES
235
aquellas heredadas de una superclase; hoy día no se tiene ninguna manera fiable de aprovecharse del hecho de que la superclase haya sido probada cuidadosamente. Por lo que utilizar un componente a través de la herencia muchas veces no es tan beneficioso como utilizarlo por composición. Una de las principales peticiones para la orientación a objetos es que permita mayor reutilización que los métodos de desarrollo del software tradicionales. Hay gente que cita los porcentajes de reutilización —que a veces alcanza el 70%— pero cuidado, ¡prácticamente no tienen significado sin una buena definición de lo que se está midiendo!
18.2
Patrones de diseño El uso de patrones es fundamentalmente la reutilización de buenas ideas muy sólidas. Un patrón es una buena solución a un problema común, con nombre y conocida. La idea viene realmente del arquitecto Christopher Alexander, que describió y nombró problemas de arquitectura comunes y trató sus soluciones. Por ejemplo, trata cómo la arquitectura puede soportar a una familia con un adolescente en el patrón Cabaña del adolescente. El problema es que un adolescente y su familia necesitan apoyarse unos a otros, mientras que al mismo tiempo el adolescente se vuelve más independiente; la arquitectura estática que mantiene al adolescente durmiendo en una habitación de niño mina este cambio. La solución propuesta, la “cabaña del adolescente”, es un lugar que actúa como hogar privado del adolescente. Tiene su propia entrada, pero está fuertemente ligada a la casa principal; puede ser parte de la casa principal o, por ejemplo, una pequeña construcción unida mediante un camino cubierto. La descripción de Alexander describe (con diagramas) posibles variaciones de la idea. Trata las objeciones que a veces se tiene —por ejemplo, que la cabaña sea sólo utilizada unos pocos años y que luego se quede vacía— y posibles maneras de evitar esas objeciones, como es diseñar la cabaña de forma que se pueda utilizar más tarde como estudio o despacho. Finalmente, trata otros patrones que son relevantes, por ejemplo los que pueden ayudar con el diseño detallado de la cabaña. La descripción completa (cuatro páginas) se puede encontrar en [17]. De forma similar, hay muchos problemas técnicos en el diseño del software. Los diseñadores experimentados los reconocen y saben cómo resolverlos. Sin patrones, los novatos tendrían que buscar las soluciones desde el principio. Los patrones ayudan a los novatos a aprender con ejemplos a comportarse como expertos.
El tema es que se puede perder el tiempo en construir la mejor solución, mientras que si se empieza sin nada se puede gastar mucho tiempo buscando una solución cualquiera: y puede que no se vean las desventajas de una solución hasta el final. Los patrones permiten utilizar lo que ya está creado. Los patrones, por definición, no son algo nuevo: documentan, en un nivel adecuado de abstracción, diseños que ya han sido intentados, probados y bien comprendidos. Un patrón no es una revelación a un diseñador experimentado. Sin embargo, una buena descripción abstracta de una solución en un contexto —de un patrón— puede ser útil incluso para los expertos que de alguna manera han utilizado ya el patrón. En ciertos casos, se facilita el descubrimiento de mejoras
236
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
y variantes: en otros, el tener un nombre para el patrón, con un significado más o menos acordado, facilita la discusión de diseños. En vez de dar instrucciones generales sobre lo que constituye un buen diseño, un catálogo de patrones documenta diseños particulares que son útiles en ciertos contextos. Uno de los mayores retos para el escritor de patrones es alcanzar el nivel correcto de abstracción, en el que describir el patrón tiene que ser lo suficientemente abstracto para que pueda ser utilizado en la mayor cantidad de situaciones posibles, pero no tan abstracto que peque de excesivos tópicos. Los catálogos de patrones han de ser fáciles y rápidos de consultar. Ayuda tener un formato común para la descripción de patrones. Por ejemplo, un catálogo de patrones podría describir todos sus patrones utilizando estos elementos: • Nombre y posibles alias: al igual que las clases, el nombre debe ser corto, pero lo más descriptivo posible. • Resumen: una visión general muy breve del patrón; no más de tres sentencias. • Contexto: una descripción muy breve de la situación en la que puede surgir el problema. • Problema: ¿cuál es el problema que surge en este contexto? Esto se puede explicar en función de fuerzas (muchas veces en conflicto); cosas que han de ser consideradas, como son requisitos y restricciones. • Solución: incluyendo texto explicatorio, modelos, tarjetas CRC y/o código de ejemplo según sea lo apropiado. • Consecuencias: cosas buenas y malas que pueden pasar si se utiliza el patrón, variantes del patrón, referencias a otros patrones que pueden ser relevantes. Los elementos no se nombran siempre de esta manera; por ejemplo, [23] mezcla contextos y problemas, mientras que [10] no tiene una sección aparte de consecuencias. Hay muchas opiniones de si deben incluirse ejemplos. Los patrones originales de Alexander están escritos de forma relativamente libre (lo que no quiere decir que estén escritos sin ningún cuidado: ¡ni mucho menos!). Hay un nombre, y una descripción muy concisa del problema, escrito en negrita, seguido de una discusión detallada del contexto, problema, solución y consecuencias. Después de esto, está la palabra clave Por lo tanto: seguido de una descripción de la solución muy concisa y en negrita. La descripción de cómo se relaciona con otros patrones se encuentra dividida en un breve párrafo al comienzo, sobre qué patrones pueden implementarse utilizando éste, y en un breve párrafo al final, sobre los patrones que pueden ser útiles en la implementación de éste. La ampliación de qué patrones trabajan juntos varía. Tal y como sugiere el nombre, un catálogo de patrones no es más que una colección de patrones. Lo ideal sería que hubiera un lenguaje de patrones: la diferencia es que un lenguaje de patrones describe cómo utilizar los patrones juntos (la sintaxis y la gramática del lenguaje) en vez de lo que son (las palabras) 1. En realidad, el uso de los patrones no está limitado al diseño. Ahora casi todas las áreas de la ingeniería del software tienen sus propios patrones; para tener un ejemplo relativamente confuso, los autores están interesados en los patrones para los sistemas legales de reingeniería. Muchos patrones describen el proceso por el que se puede crear, en vez del artefacto que se produce. 1
Sin embargo, comprender las interacciones entre patrones no es fácil. Hay que tener cuidado: algunos de los tan nombrados lenguajes de patrones no son más que catálogos de patrones con alguna referencia cruzada limitada.
REUTILIZACIÓN: COMPONENTES Y PATRONES
237
Además, existen varios niveles de patrones: [10] distingue entre • patrones de arquitectura (tales como la arquitectura en capas; este es otro ejemplo muy citado del estilo de arquitectura, y claro está, patrón de arquitectura y estilo de arquitectura son prácticamente sinónimos) al más alto nivel. • patrones de diseño en medio; fachada, que se estudiará, es un ejemplo. • idioms a nivel de programación. Para crear mayor confusión, estos tres niveles se incluyen en lo que la mayoría de comunidades de patrones llaman patrones de diseño; esto es para distinguirlos de las otras familias de patrones como son los patrones de proceso y los patrones de análisis. Hay muchos libros sobre varios tipos de patrones. El “libro de la Banda de los Cuatro”, [23] es, sin duda, el más conocido, seguido del “libro de Siemens” [10] y [20]. Internet es también una fuente excelente, y, como siempre, hay enlaces desde la página web de este libro. Pregunta de Discusión 127 Elija dos patrones de libros o de Internet. ¿Qué tienen en común y en qué se diferencian?
18.2.1
Ejemplo: Fachada
La descripción completa de Fachada se puede encontrar en [23]. Aquí se hace un pequeño resumen. • Nombre
Fachada.
• Resumen Fachada define una interfaz de un componente limpia, de alto nivel, ocultando su estructura para facilitar su uso. • Contexto Construir componentes fáciles de utilizar. • Problema Si un componente está formado por muchas clases relacionadas, proporcionando cada una parte de la funcionalidad del componente, los clientes del componente tienen que comprender la estructura de dicho componente para utilizarlo de forma efectiva. Esto aumenta el peso sobre los desarrolladores de los clientes. También aumenta el acoplamiento en el sistema: los cambios en la estructura del componente pueden requerir cambios en los clientes. • Solución Añadir una nueva clase Fachada que proporciona una única interfaz al componente. Un objeto de esta clase (la Fachada) acepta mensajes solicitando cualquier funcionalidad del componente. Conoce la estructura del componente, y dirige los mensajes al objeto adecuado. Las clases en el componente no dependen de la Fachada: por ejemplo, ningún objeto del componente mantiene una referencia a él. • Consecuencias Aunque a los clientes se les permite acceder a las clases de los componentes si tienen que hacerlo, se puede evitar, dependiendo de la estructura del componente. Como variante, la clase Fachada puede ser la única clase del componente accesible públicamente: en este caso, los clientes no pueden depender de la estructura del componente.
238
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
El componente se vuelve más fácil de utilizar para el cliente, y se puede mantener con menos impacto sobre los clientes. Esto es particularmente útil cuando los componentes se dividen en capas: cada capa puede tener una Fachada proporcionando acceso a su funcionalidad. Pregunta de Discusión 128 ¿Qué es lo importante en Fachada para los componentes?
18.2.2
UML y patrones
UML con respecto a los patrones es útil de dos maneras diferentes. Primero, puede utilizarse para ayudar a transmitir los patrones. Cada uno de los libros de patrones existente utiliza su propia notación, normalmente alguna forma de diagrama de clases parecida al de OMT y diagramas de secuencia como los de UML. Cada vez más, los catálogos de patrones describen los patrones utilizando UML, eliminando la barrera del lenguaje impuesta por la necesidad de comprender diferentes notaciones en los distintos libros. En segundo lugar, UML facilita el almacenamiento de cómo se utilizan los patrones en un diseño. Siempre que el lector esté familiarizado con el patrón, es más fácil para cualquiera leer un diagrama en UML que comprender cómo funciona. Incluso es más difícil que alguien, al modificar el diseño, modifique accidentalmente el diseño del patrón sin saber que lo está haciendo. En un sistema orientado a objetos, un diseño que utiliza un patrón tendrá una familia de objetos cada uno de los cuales desempeña un rol descrito en el patrón; esto es, interactúan de la manera descrita en el patrón. Por ejemplo, un diseño que utiliza Fachada tendrá un componente con un objeto que actúa como Fachada para el componente; esto es, recibe mensajes para el componente y los redirige a los objetos correctos. Su clase tiene una interfaz que incluye operaciones para todas las funcionalidades del componente, y hay asociaciones entre la clase del objeto Fachada y la clase de cada objeto del componente relevante, navegable desde la clase Fachada hacia la clase del componente, pero no en sentido contrario. Esto empieza a sonar como una colaboración que, tal y como se vio en el Capítulo 9, es un término de UML para una familia de roles de objetos relacionados. Si se considera el patrón como una colaboración, entonces la utilización del patrón en un diseño en particular es una ocurrencia de dicha colaboración. Esto se puede representar en un diagrama de clases con un óvalo punteado, con líneas discontinuas uniéndolo a los rectángulos de clase que representan las clases que son los parámetros. Las líneas están etiquetadas con nombres de rol. Por ejemplo, supóngase que en una futura iteración del caso de estudio de la Biblioteca del Capítulo 3 se vuelve necesario proporcionar otros sistemas de biblioteca con acceso a la información sobre qué libros y revistas tiene la biblioteca. Se querría permitir que los sistemas externos accediesen al sistema de biblioteca a través de una interfaz ordenada que les permitiera el acceso a la información que necesitan sobre libros y revistas, pero no a la información que no les interesa. Si se decide utilizar el patrón Fachada, se representaría tal y como se muestra en la Figura 18.1. Destacar que se ha decidido omitir las asociaciones entre la clase Fachada Biblioteca y las clases Libro y Revista del componente, debido a que pueden deducirse si se conoce el patrón, y si se añaden se complicaría el diagrama.
REUTILIZACIÓN: COMPONENTES Y PATRONES
239
Libro Clase subsistema 1 es una copia de
Biblioteca
1..*
SocioBiblioteca
tomaPrestado/devuelve 0..1
Fachada
Copia
0..* Fachada
SocioPlantilla
tomaPrestado/devuelve 0..1
Figura 18.1
Revista
0..*
Clase subsistema
El patrón Fachada aplicado a la biblioteca.
P: ¿Qué habría en la interfaz de la clase Biblioteca? Si se utilizan patrones, por el nombre sin explicarlos, como parte del diseño, ¡es importante tener un “vocabulario” de patrones acordado que pueda utilizarse de esta manera! Esto debe ser acordado a nivel de equipo o a nivel de organización. Probablemente, se nombraría uno o dos catálogos de patrones, cuyos patrones podrían utilizarse sin descripción: casi seguro uno de ellos sería [23], que ya se utiliza de esta manera. Posiblemente una organización podría construir su propia colección de patrones así como aceptar aquellos ya estandarizados: pero se tiene la sospecha que si se necesita hacer esto es porque los patrones están descritos en un nivel de abstracción demasiado bajo. Se duda de que los problemas de diseño que ocurren normalmente sean específicos del dominio. Esto queda por ver: el uso de patrones en el software es joven.
18.3
Marcos de trabajo En el Capítulo 4 se describió un marco de trabajo como “una parte reutilizable de la arquitectura”. Seamos un poco más precisos. No se puede ser mucho más preciso debido a que el significado exacto del término es todavía bastante inexacto. Un marco de trabajo es bastante parecido a la parte estructural de un patrón, solo que mayor. Describe cómo una colección de objetos trabajan juntos, normalmente definiendo clases que serán divididas en subclases cuando se aplique el marco de trabajo, y describiendo las colaboraciones entre los objetos. Para utilizar un marco de trabajo, se implementan las partes variables del marco de trabajo, por ejemplo, subdividiendo en función de lo que se proporciona. Algunas de las clases de un marco de trabajo normalmente son abstractas: es decir, hay que crear una subclase antes de poder utilizar el marco de trabajo.
240
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
El “marco de trabajo” de los juegos del Capítulo 16 es un pequeño ejemplo. El desarrollo de marcos de trabajo es muy complicado, y no se pretende ser experto: ¡envíe sus mejoras a dicho marco de trabajo! Los argumentos para comprar un marco de trabajo si es posible, en vez de desarrollarlo, son todavía más convincentes que los mismos argumentos para comprar componentes. Se ha discutido de forma convincente (por ejemplo, por el experto en marcos de trabajo Don Roberts: véanse los enlaces de la página web del libro) que la reutilización de marcos de trabajo es la clave para establecer la reutilización en los sistemas orientados a objetos.
RESUMEN
Se ha tratado la reutilización, cubriendo lo que puede reutilizarse y los problemas prácticos al obtener un programa de reutilización estable. Como caso especial, se han tratado los patrones, que pueden verse como la reutilización de los expertos. Muchas de las dificultades al alcanzar un alto nivel de reutilización, especialmente construyendo componentes propios, radican en la necesidad de reutilizar artefactos para obtener una alta calidad y para que la organización apoye el trabajo necesario para construirlos y utilizarlos. En los capítulos siguientes se tratarán estas cuestiones de forma más general. Pregunta de Discusión 129 A menudo ocurre que, cuando un objeto cambia de estado, uno o más objetos tienen que saberlo, para que sus estados permanezcan consistentes. Por ejemplo, uno o más elementos de una interfaz de usuario puede que necesiten mostrar la versión correcta de algunos datos en un objeto; quizá varias interfaces de usuario del cliente deben presentar versiones actualizadas de un precio compartido, por ejemplo. Piense cómo se puede manejar esto. Después, investigue el patrón Observador, también conocido como Editor-Suscriptor, que describe cómo mantener algunos objetos (suscriptor) actualizados cuando cambia otro objeto (el editor). Esto se encuentra tanto en [23] como en [10], y hay también fuentes disponibles desde la página web de este libro.
Capítulo
19 Calidad del producto: verificación, validación y prueba
Este capítulo vuelve a la pregunta de qué es un sistema de alta calidad. Se sugiere que para construir sistemas de alta calidad una organización necesita tanto técnicas que se centren en la calidad del producto, como técnicas que se centren en la calidad del proceso. En el resto del capítulo se tratarán las técnicas centradas en el producto para asegurar que un sistema de software es de alta calidad. Aunque a menudo se consideran juntos como “VV&P”, verificación, validación y prueba no están dentro de la misma categoría. La verificación es el proceso de asegurar que se ha construido el producto correctamente, esto es, que cumple sus requisitos iniciales. La validación es el proceso de asegurar que se ha construido el producto correcto, es decir, que el producto cumple su propósito, incluso puede tener cosas que no se han capturado en los requisitos establecidos. La manera fundamental de asegurar ambas cosas es comprobando el sistema y buscando problemas de cualquier tipo: esto es, probándolo. En las siguientes secciones se tratará cómo realizar pruebas y otras técnicas que pueden utilizarse para la verificación y la validación. Se intentará sacar a relucir lo bueno de VV&P para los proyectos que siguen un proceso iterativo, centrado en la arquitectura, basado en componentes, como se ha recomendado para utilizar UML. Como siempre, lo que aquí se dice es independiente del proceso en particular que se utilice.
19.1
Revisión de la calidad Pregunta de Discusión 130 ¿Qué quiere decir “alta calidad” en otros ámbitos de la vida?
242
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
Recordar que la definición de alto nivel de calidad de software es que un sistema tiene alta calidad si cumple los requisitos del cliente: en el Capítulo 1 se dio una breve clasificación de lo que esto significa.
P: Repase dicha clasificación, y piense si se necesita añadir algo. Para asegurar que se cumplen estos requisitos, podemos necesitar algunos atributos de los que el usuario no se preocupa directamente pero que contribuyen a aquello que pide el cliente: por ejemplo, se quiere que el sistema tenga un elevado nivel de pruebas, de manera que los desarrolladores puedan prácticamente asegurar que hay pocos errores; y se quiere que tenga un alto nivel de mantenimiento, de forma que sea posible responder rápidamente y de manera útil a informes de errores y peticiones de cambios. Destacar que estos no son aspectos de calidad en sí mismos. Por ejemplo, si se sabe seguro que un sistema sólo va a utilizarse una vez y que nunca va a haber ningún informe de errores o peticiones de cambios, no importaría si se puede mantener o no 1.
Terminología En software, como en cualquier otro ámbito, es habitual utilizar “calidad” como sinónimo de “alta calidad”.
19.2
¿Cómo se alcanza la alta calidad? Hay dos maneras opuestas de aproximarse a esta cuestión: en la práctica se necesita combinar ambas aproximaciones.
19.2.1
Centrada en el producto
Examine el producto, y los productos de etapas intermedias del proceso de desarrollo, para ver si cumplen los estándares de calidad. El examen puede hacerse mediante verificación, comprobando si el producto tiene las características que se sabe deben tener; o mediante validación, comprobando que hace el trabajo que se supone tiene que hacer. Se analizarán técnicas para ambos tipos de exámenes, de las que la más importante es la prueba.
19.2.2
Centrada en el proceso
Se puede ver que centrándose sólo en el producto no es suficiente. Por ejemplo, si las pruebas encuentran repetidamente fallos similares en productos diferentes, por supuesto se querrá descubrir cómo prevenir que esos mismos fallos se den en productos futuros. Ya se ha discutido en el Capítulo 4 que es fundamental un proceso iterativo con evaluación en cada etapa. El Capítulo 20 trata sobre cómo puede ayudar la garantía de calidad, tanto mediante el control del proceso que se sigue en cada proyecto, como mediante la mejora del proceso en sí. 1 Desde la experiencia de los autores, sin embargo, es peligroso creer a cualquiera cuando dice que el software va a tener una vida corta. El software que funciona tiende a vivir más de lo que nadie pueda esperar —¡de aquí el Error del Milenio!
CALIDAD DEL PRODUCTO: VERIFICACIÓN, VALIDACIÓN Y PRUEBA
19.2.3
243
Más información
Hay muchos recursos que tratan la calidad del software en Internet y en otros sitios. En la página web de este libro se han recogido algunos enlaces. Hay varios libros especializados sobre calidad del software: uno muy conocido es [25].
19.3
Verificación La verificación siempre implica la prueba de algo explícito (el producto) frente a otra cosa (la especificación). Sin embargo, la identidad del producto y de la especificación puede variar. El ejemplo más obvio es cuando se prueba el código (o para ser más exactos, el sistema en ejecución) frente a la especificación de requisitos, y se tratará esto con más detalle más adelante en este capítulo. En el contexto de un desarrollo en UML se puede también, por ejemplo, dividir la comprobación en etapas. 1. Verificar que los casos de uso descritos en el modelo de UML satisfacen los requisitos descritos en la especificación de requisitos (si no es simplemente una colección de descripciones de casos de uso). 3. Verificar que las clases son capaces de proporcionar los casos de uso. 3. Verificar que el código se corresponde con las clases del diseño. Por supuesto, en un desarrollo iterativo estas comprobaciones se harán para cada iteración. Ciertas comprobaciones de “sanidad” muchas veces se consideran también como parte de la verificación. Por ejemplo, se supone que todos los proyectos tienen como parte de sus requisitos que el código que producen debería compilar ordenadamente, y debería ejecutarse sin errores. (Por “error” se entiende un comportamiento inesperado del programa respondiendo correctamente a circunstancias excepcionales o errores de usuario. Se reconoce que, sin mayor explicación, esto es un concepto borroso: se tiene presente que el programa no debería tener fallos de segmentación, errores de Mensajes No Comprendidos u otras propiedades “definitivamente incorrectas”.) Los modelos de UML producidos como parte de un proyecto deberían ser sintácticamente correctos y consistentes, en el sentido de que siempre debería haber algún programa descrito por el modelo (en otro caso ¡las tareas de verificación posteriores seguro que fallan!). La verificación, por lo tanto, incluye la tarea de comprobar tales condiciones de sanidad. Aparte de probar el código, ¿cómo pueden hacerse estas comprobaciones? De manera informal, un desarrollador puede leer los dos modelos, documentos o archivos fuentes y comprobar de cabeza si hay una correspondencia apropiada. De manera formal, se podría producir una prueba de que los dos modelos, documentos o archivos fuentes se corresponden. En la práctica esto no se suele hacer, y sólo tiene sentido si las dos cosas a comparar son objetos bien comprendidos, con una semántica bien definida —es decir, con significado—. UML no tiene (todavía) una semántica formal, por lo que la verificación completamente formal de un modelo de UML (todavía) no es posible en principio. En la práctica, dado el estado actual de la tecnología, las verificaciones formales tienden a consumir mucho tiempo y por lo tanto son muy caras, por lo que sólo se hacen para sistemas críticos de seguridad. Se considerarán algunas técnicas entre estos dos extremos. La que está en todas partes, ¡es el uso de un compilador! El hecho de que el código compile sin errores, es en sí una verificación
244
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
de una de las comprobaciones de “sanidad” que se han mencionado. Algo similar, una herramienta de modelado de UML permitirá a los desarrolladores excluir algunos otros errores de rutina como son los modelos de UML cuya sintaxis está escrita de forma incorrecta. Puede ayudar en el proceso de comprobación de la consistencia entre los distintos modelos de UML, permitiendo encontrar problemas en la etapa de modelado que de otra manera podrían no aparecer hasta la implementación de la iteración relevante. Algunas herramientas son capaces de generar código a partir de modelos de UML, y viceversa. Esto facilita el mantenimiento de la consistencia entre los modelos de UML y el código, que aumenta la probable utilidad de los modelos de UML de pruebas. Hacia el otro extremo del espectro humano de la herramienta se puede pensar en maneras de mejorar la manera en que el ‘desarrollador examina los dos modelos’ para encontrar inconsistencias y errores. La más sencilla es tener otro desarrollador, aparte del que desarrolló las dos etapas, que compruebe la consistencia. Una lista de comprobación puede ayudar al desarrollador a recordar la comprobación de las fuentes de problemas más comunes. Otra es tener revisiones de algún tipo. Las revisiones, como pruebas, contribuyen tanto en la verificación como en la validación, por lo que se tratarán después de la validación.
19.4
Validación La verificación es la parte sencilla del trabajo de asegurar que el producto tiene alta calidad. Está claro: se sabe lo que se tiene que comprobar. La validación es más complicada porque no se sabe exactamente lo que se busca: se busca algo que podría hacer que el producto fuese menos útil para el cliente de lo que debería ser. Para que la validación sea efectiva, se requiere la implicación real del cliente. Es posible intentar ejecutar alguna validación teniendo a un desarrollador haciendo el papel de cliente; pero los peores errores vienen provocados por fallos de comunicación entre el cliente y el desarrollador sobre lo que se quiere. Por supuesto, es deseable encontrar problemas y malentendidos lo antes posible: tal y como se trató en el Capítulo 4, esta es la razón principal para tener un proceso de desarrollo iterativo. Hay varias maneras en las que el cliente puede estar implicado. La más sencilla es que el cliente firmara ciertos documentos, por ejemplo, las especificaciones de qué funcionalidad se proporciona en cada iteración. El problema con esto es que es difícil criticar un sistema descrito con palabras. Demasiado a menudo, el cliente sólo se da cuenta de que no tiene lo que quería cuando se enfrenta con un sistema en ejecución que está mal. Cuanto más inciertos son los requisitos, mejores son los argumentos para tener iteraciones breves que proporcionen retroalimentación lo antes posible. Al comienzo de un proyecto o de una iteración de alto riesgo, puede merecer la pena construir un prototipo desechable únicamente con el propósito de comprobar que todo el mundo comprende los requisitos de la misma manera.
19.4.1
Usabilidad
Un sistema de alta calidad no sólo debe proporcionar la funcionalidad correcta, sino que la tiene que proporcionar de manera que sus usuarios puedan utilizarla de forma efectiva. Por ejemplo, los usuarios deben ser capaces de descubrir cómo ejecutar una tarea, ejecutarla rápidamente y sin demasiada presión, y recuperar errores. Todavía este aspecto del software muchas veces está muy descuidado. Esto es debido, en parte, a que para los desarrolladores es difícil creer que
CALIDAD DEL PRODUCTO: VERIFICACIÓN, VALIDACIÓN Y PRUEBA
245
puede haber problemas con la usabilidad de su software tan serios como para reducir la satisfacción del cliente con el sistema. Hay dos razones principales por las que esto es así. 1. Si se sabe cómo se hace algo, es prácticamente imposible ponerse en el lugar de alguien que no lo sabe. Los usuarios reales, cometerán errores que nunca se les ocurrirían a los desarrolladores, aunque concienzudamente tratasen de imaginar posibles problemas. 2. ¡Sólo pocas veces los desarrolladores del software son usuarios característicos de un sistema! Los desarrolladores del software son, por ejemplo, mucho más competentes y seguros de sí mismos utilizando programas de computadora desconocidos que el usuario típico de casi cualquier sistema. Por lo que uno mismo no puede juzgar la usabilidad de su propio sistema, y los compañeros pueden ayudar poco. Otros dos recursos que pueden ser necesarios son un experto en usabilidad, y usuarios reales. Idealmente se utilizan ambos, en un ciclo iterativo que implica la investigación de qué cambios pueden hacer que el sistema sea más usable (la implicación del experto puede ayudar mucho aquí), realizando el cambio y la prueba para ver si el cambio realmente ayudó. Esta prueba de usabilidad puede implicar, por ejemplo, el solicitar a un usuario que realice una tarea con el sistema, y observar los problemas que tenga. El libro de Thomas Landauer, muy leído, El problema con las computadoras [32] analiza esta cuestión en profundidad, y propone un diseño centrado en el usuario como solución. Parte de lo que propone es una aproximación basada en tareas para la captura de requisitos, teniendo en cuenta las distintas perspectivas de los diferentes usuarios. La intensidad de la captura de requisitos utilizando casos de uso es exactamente esto. Landauer también destaca que poco trabajo bien dirigido puede traer grandes beneficios: por ejemplo, estima que un único día de pruebas de usabilidad puede tener como resultado una mejora del 25% en la eficiencia de un sistema típico (es decir, en la cantidad de trabajo que los usuarios reciben en un tiempo dado). Pregunta de Discusión 131 Lo más sencillo de hacer utilizando apenas software usable es, simplemente, documentar todas sus debilidades en el manual de usuario. ¿Hasta qué punto esto es una solución?
19.5
Prueba Las pruebas contribuyen tanto a la verificación como a la validación, probablemente más a la primero. Es muy probable que los lectores estén familiarizados con las ideas básicas y los tipos de pruebas: se resumirán brevemente, antes de tratar las características especiales de los proyectos orientados a objetos. Las pruebas pueden realizarse sobre varios artefactos diferentes, incluyendo no sólo los sistemas en ejecución, sino también diseños y partes de sistemas. Tiene tres objetivos principales: • ayudar a encontrar los errores 2. • convencer al cliente de que no hay errores (importantes). • proporcionar información que ayudará en la evolución del sistema —por ejemplo, el proceso de pruebas puede ayudar a recoger información sobre requisitos y prioridades 2
“error” aquí incluye todos los defectos, de cualquier tipo.
246
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
futuras, así como información sobre la ejecución presente que anteriormente, sólo se ha estimado. El primer objetivo es, por supuesto, el más crucial, y lleva directamente a la conclusión: Una prueba con éxito es aquella que encuentra un error.
Es fácil hacer que el sistema pase todas sus pruebas —simplemente utilice un conjunto de pruebas inadecuado—. No es fácil eliminar todos los errores. Los diferentes tipos de pruebas pueden ayudar a eliminar distintos tipos de defectos. • Prueba de usabilidad, comprueba que el sistema sea fácil de utilizar de forma efectiva. Esto puede incluir pruebas de todo el sistema, de parte de él, o de su documentación. • Prueba de módulo (o unidad), comprueba los módulos individuales del sistema; en un desarrollo OO, tal y como se verá, esto significa normalmente las clases. • Prueba de integración, comprueba que las partes del sistema de un cierto nivel funcionan correctamente todas juntas: por ejemplo, que los objetos implicados en una colaboración funcionen tal y como está planeado, o que componentes y marcos de trabajo realmente están conectados como deberían. • Prueba del sistema, comprueba que el sistema cumple sus requisitos, tanto funcionales como no funcionales. • Prueba de aceptación, normalmente la ejecuta el cliente, o algún grupo independiente que representa al cliente; valida que el sistema realmente cumple su propósito. • Prueba de ejecución, puede tener lugar a cualquier nivel (prueba de módulo, integración, o sistema) para comprobar que la ejecución del sistema es o será satisfactoria. • Prueba de carga, es un tipo de prueba de ejecución que pone al sistema bajo cargas mayores de las que normalmente se esperan, para comprobar que baja su rendimiento en vez de fallar catastróficamente. • Prueba de regresión, comprueba que aquellas funciones de un sistema que funcionaban antes de una modificación siguen funcionando después de ella. Las pruebas que se realizan son parte de las que se realizaron previamente: pruebas de unidad para cualquier unidad afectada, pruebas de integración para cualquier subsistema que incluya una unidad modificada, y alguna o todas las pruebas del sistema completo (pruebas de usabilidad, sistema, aceptación, ejecución y carga). Estas categorías no son mutuamente excluyentes, y los límites no están siempre claros: el aspecto más variable es lo que cubre la prueba de integración. ¿Debería incluir todas las pruebas de un subsistema o componente multiclase, por ejemplo, o debería sólo comprobar que todos los usos de las interfaces son sintácticamente correctos? La diferencia no es importante ¡siempre que todos los tipos de pruebas se realicen bajo algún título!
19.5.1
Selección y ejecución de las pruebas
Las pruebas pueden clasificarse, a grandes rasgos, en: • caja negra (las pruebas se eligen mirando la especificación de lo que se va a probar). • caja blanca (las pruebas se eligen mirando la estructura de lo que se va a probar).
CALIDAD DEL PRODUCTO: VERIFICACIÓN, VALIDACIÓN Y PRUEBA
247
¿Qué aspectos son más importantes probar? En [37], Petschenik identificó tres realidades sobre los tipos de prueba que son importantes para asegurar que el cliente se encuentre con un software de alta calidad: • Es más importante probar lo que hace el sistema completo que probar sus componentes. • Es más importante comprobar que el sistema puede todavía hacer lo que hacía antes, que comprobar que funcionan las nuevas características. • Es más importante probar los casos característicos que los casos de valores límite.
P: ¿Por qué? ¿Estas afirmaciones parecen análogas e intuitivas? Debido a que es fundamental volver a probar la funcionalidad existente cuando se realizan cambios —es decir, realizar una prueba de regresión— las pruebas tienen que ser: • repetibles. • documentadas (tanto las pruebas como los resultados). • precisas. • hechas sobre software controlado de configuración. Especialmente, en los proyectos iterativos la misma funcionalidad tiene que probarse varias veces; por lo que se convierte en una necesidad el tener algún tipo de automatización del proceso de pruebas. Existen herramientas de prueba especializadas que también pueden generar automáticamente varios casos de prueba. Sin embargo, un simple guión Perl [50] (o incluso guiones más antiguos, como son los archivos por lotes (batch) de DOS) puede ayudar considerablemente con: • la ejecución de un conjunto de pruebas. • el almacenamiento de qué pruebas se han realizado, sobre qué configuración, y con qué resultados. • la comparación de resultados con lo que se esperaba. Es importante recordar, por supuesto, que la automatización no puede compensar la planificación pobre de pruebas. ¿Cuándo debería escribirse la especificación de las pruebas para un artefacto, que describe las pruebas que deberían realizarse y los resultados que son aceptables? La respuesta inmediata es “lo antes posible, pero no antes”. La especificación de pruebas puede escribirse al mismo tiempo y con el mismo nivel de detalle que la especificación de requisitos correspondiente; ¡pero es inútil suponer que se pueden escribir pruebas para algo si todavía no se sabe lo que tiene que hacer! La razón por la que se debe escribir la especificación de pruebas lo antes posible es que ayudan a asegurar que los requisitos se tienen claros, y que están muy probados. Los requisitos pueden escribirse en sentencias numeradas, y se puede almacenar en una tabla qué prueba (o pruebas) comprueba qué requisito (o requisitos). Pensar cómo se puede probar un requisito, a menudo, es una buena manera de precisar lo que realmente quiere decir el requisito. A veces se defiende que las pruebas de aceptación y sus resultados deberían estar definidos de forma precisa al principio de un proyecto, y que los desarrolladores deberían estar obligados, por contrato, a producir un sistema que pase dichas pruebas. La idea es que esto beneficia tanto
248
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
al cliente, que no tiene que aceptar un sistema incorrecto o incompleto, como al desarrollador, que sabe desde el principio exactamente lo que se solicita. El problema con esto es que se puede fallar fácilmente en el “pero no antes”, si las pruebas detalladas se escriben cuando sólo se conocen los requisitos por encima. La captura de requisitos utilizando casos de uso puede ayudar a asegurar que las pruebas estén escritas a un correcto nivel, porque los escenarios en caso de uso describen los requisitos y también sirven de fuente de casos de prueba. Una buena manera de mantener claro el diseño prematuro es concentrarse en los cambios que tienen lugar entre el comienzo y el final de un escenario: por ejemplo, ¿cómo beneficia un actor? En un desarrollo iterativo a menudo es útil escribir las pruebas que el resultado de la iteración debería pasar, al principio de la misma: uno de los beneficios de la aproximación iterativa es que esto puede ser posible, incluso si los requisitos de futuras iteraciones todavía no se comprenden totalmente. Pero esto no es todo. Es muy difícil describir de forma apropiada pruebas complejas, o definir las que son correctas —pero algunos errores sólo aparecen si se realiza una secuencia compleja de acciones—. ¿Cómo se pueden detectar estos errores? • Escribiendo software bien modularizado y con módulos probados ¡de manera que es menos probable que esto sea lo primero que ocurra! • Teniendo algún software de prueba con el objetivo de romperlo, eso sí, de manera furtiva. Eso sí, aunque son fundamentales las pruebas precisas, breves, documentadas, no son suficientes. DeMarco y Lister en [13] describen el Equipo Negro de IBM, que era un equipo de controladores particularmente diabólico a los que les encantaba encontrar errores en el software. Por supuesto, el equipo fue un gran éxito para IBM —¡si el equipo encontraba los errores que no encontraba el cliente! Este tipo de prueba es particularmente importante para las interfaces de usuario gráficas, que son difíciles de probar sistemáticamente. Es prácticamente imposible automatizar las pruebas de una IGU sin una herramienta especial, e incluso con una no es fácil. Además, un elevado porcentaje de los problemas con las interfaces de usuario son fallos, bien de usabilidad —¡las pruebas de usabilidad no pueden automatizarse!— o bien de comportamientos que necesitan todas las interfaces de usuario con independencia del sistema subyacente (“condiciones sanas”). Por ejemplo, cada botón debería tener una etiqueta (o algún otro indicador visual) para mostrar lo que ocurre si se pulsa, y pulsarlo no debería colgar la aplicación. Pregunta de Discusión 132 ¿Qué otras condiciones de sanidad hay? ¿Cómo, podría documentarse en la especificación de requisitos, que los requisitos de una interfaz de usuario son correctos? ¿Cuál es la mejor manera de probar una interfaz?
19.5.2
Problemas especiales de la orientación a objetos
Hay tres problemas fundamentales extra en el nivel de pruebas de unidad.
CALIDAD DEL PRODUCTO: VERIFICACIÓN, VALIDACIÓN Y PRUEBA
249
¿Aplicar pruebas unitarias a qué? La unidad de prueba tiene que ser (al menos 3) la clase, y una clase es más difícil de probar que una función. La razón es que un objeto tiene estado, del que puede depender su comportamiento, por lo que probar cada método de forma aislada podría obviar errores provocados por un método que cambia un estado del objeto a algo que está expuesto a errores en otro método. Los diagramas de estado exponen los problemas, así como sugieren una manera de resolverlos. Tal y como se explicó en el Capítulo 11, un objeto cambia de estado en un diagrama de estado cuando su estado completo, los valores de todos sus atributos, cambia de manera que puede afectar cualitativamente a su comportamiento. Por lo que, al menos, un objeto debe probarse en cada estado. Probar un objeto en cada estado debe incluir la comprobación de todas las maneras en que el objeto podría abandonar ese estado; por lo que en realidad lo que debe probarse es cada transición de un diagrama de estado. Pregunta de Discusión 133 ¿Es esto suficiente? Si no, ¿puede formular una regla mejor?
Las clases con diagramas de estado complejos son difíciles de comprender y probar —intente evitar el diseño de dichas clases.
Encapsulación En el Capítulo 1 se dijo que la encapsulación era importante precisamente porque reduce la posibilidad de introducir errores por interacciones entre componentes mal entendidas. Esto es verdad, pero también puede dificultar la prueba de un componente encapsulado. Por ejemplo, para probar si un objeto ha pasado de un estado a otro, es necesario poder decir en qué estado se encuentra el objeto, lo que probablemente sea información encapsulada, no disponible al mundo exterior. Una solución es proporcionar un método para cada clase, para utilizarlo sólo durante la prueba, que informe de todo lo relativo al estado del objeto. Pregunta de Discusión 134 Si hay métodos de información de estado, ¿cómo se puede estar seguro de que ellos mismos son correctos?
Herencia y polimorfismo Cuando una subclase hereda de una clase probada concienzudamente redefiniendo algunos de sus métodos, ¿qué pruebas son necesarias para la nueva subclase? Se podría pensar que es suficiente con probar los métodos redefinidos; pero desafortunadamente, debido a la ligadura dinámica, este no es el caso. Supóngase que la subclase D redefine el método foo(), pero no el 3 El punto de vista más común es que la unidad de prueba sea la clase, pero algunos expertos —Martin Fowler, en [19] por ejemplo— dicen que normalmente prefieren aplicar la prueba unitaria al paquete que contiene una familia de clases relacionadas.
250
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
método bar(), de la superclase G. El resultado de enviar bar() a un objeto de la clase D podría ser diferente del resultado obtenido al enviarlo a un objeto de la clase G. Por ejemplo, la implementación de bar() podría incluir el envío del mensaje foo() a sí mismo. Si el objeto tiene la clase D , entonces se utilizará la nueva implementación de D de foo() . Si tiene comportamiento diferente del foo() de G, un efecto de choque podría ser que bar() de D se comportase también de forma diferente del bar() de G —¡aunque el código del método bar() de D sea idéntico al de G! Por lo que hay que estar seguro de que se han probado todas las posibilidades de una subclase, incluso aquellas heredadas de una superclase.
P: Escriba las clases D y G con los métodos foo() y bar() con el comportamiento tal y como se acaba de describir. Pregunta de Discusión 135 ¿También surge el problema cuando una subclase sólo añade nuevas operaciones, sin redefinir ningún método?
El polimorfismo basado en la herencia añade una capa extra de complejidad. Cuando un objeto de la clase A interactúa con un objeto de la clase B, puede en realidad estar interactuando con un objeto de cualquier subclase de B —¡que incluso puede que aún no exista cuando se prueba la clase A! Pregunta de Discusión 136 ¿Cómo se puede tratar este problema en la práctica?
Una clase está fuertemente acoplada con sus superclases, lo que crea dificultades para realizar las pruebas. Utiliza la herencia sólo cuando sus ventajas compensen las desventajas.
Por supuesto, debido a que la dependencia de estados está en los objetos, que tienen interfaces bien definidas, se espera poder ahorrar esfuerzo en las pruebas de sistema e integración, y en general. Pero esto no es automático en los programas orientados a objetos. La arquitectura de un sistema tiene un mayor efecto en su capacidad de prueba. La prueba pretende exponer imperfecciones de cualquier tipo en los sistemas; todavía los sistemas imperfectos superan frecuentemente sus pruebas y llegan a los usuarios. ¿Por qué?
19.5.3
¿Por qué las pruebas se hacen de forma incorrecta tan a menudo?
• Es muy muy aburrido, especialmente la documentación del proceso completo. La automatización puede ayudar; pero todavía queda por hacer una cantidad sustancial de trabajo para documentar cómo debe instalarse el sistema para las pruebas y qué pruebas deben realizarse. Esto es importante para asegurar que las pruebas se puedan repetir. Éste es un área en el que un sistema de gestión de calidad coordinado (véase el Capítulo 20) es fundamental: las tareas
CALIDAD DEL PRODUCTO: VERIFICACIÓN, VALIDACIÓN Y PRUEBA
251
aburridas son tolerables siempre que quede claro que son importantes, pero pueden ser extremadamente desmoralizadoras si son meramente burocráticas. • Es muy caro, en tiempo y por supuesto en dinero. (El tiempo de prueba puede exceder el 50% del tiempo del proyecto: el 30% es más o menos la media.) La alternativa —el no realizar pruebas concienzudas— puede ser peor, pero el coste puede no aparecer hasta más tarde, cuando las quejas empiecen a surgir... • A menudo, la mayoría de las pruebas se planifican al final del proyecto, y están presionadas por el tiempo que queda para la entrega del proyecto. Esto es un argumento a favor de un proceso iterativo en el que hay integración de forma frecuente; las pruebas están esparcidas a lo largo del ciclo de desarrollo y es menos probable que esté presionado. • Los clientes pueden presionar a los desarrolladores para que se dé la entrega a tiempo y sin realizar las pruebas, en vez de tarde y probado. Pregunta de Discusión 137 ¿Qué haría? Pregunta de Discusión 138 ¿Cuáles son los problemas especiales de la verificación de sistemas que implican concurrencia? ¿Cómo pueden solucionarse?
En el siguiente apartado se examina otra familia de técnicas, complementaria a las pruebas.
19.6
Revisiones e inspecciones Las revisiones e inspecciones de varios tipos pueden ayudar tanto a la verificación como a la validación, probablemente más a la última. Una revisión, inspección o repaso es fundamentalmente una reunión que trata de encontrar problemas con algún producto del proceso de desarrollo: esto podría ser código o diseño, por ejemplo. A veces se conoce como un grupo de Revisiones Técnicas Formales (RTF) 4. La inspección de Fagan [18] probablemente es la más conocida. Las RTF varían mucho en su proceso y grado de formalidad, pero en general tienen las siguientes propiedades. 1. Los participantes son parejas (no director y subordinados) e incluyen al Autor principal de lo que se está revisando, a alguien designado como Moderador, y a alguien (el Copista) para grabar los descubrimientos de la reunión. 3. El artefacto a revisar se proporciona a los participantes por adelantado, y ellos lo analizan antes de la reunión. Las áreas principales a tratar se identifican antes de la reunión. 4
“Formal” aquí contrasta con una conversación informal improvisada —¡no tiene nada que ver con la especificación formal! (Abreviatura en inglés, Formal Technical Reviews.)
252
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
Normalmente el artefacto debe satisfacer algunos criterios de entrada para que se consideren preparados para la inspección: por ejemplo, si es código puede requerirse que compile sin errores o avisos. 4. La reunión sigue una agenda predefinida, y entrega un registro de los defectos encontrados, con algún tipo de análisis sobre ellos. Debería decirse, por ejemplo, lo serio que es el defecto o cuál es su causa. 5. La reunión no trata cómo corregir los defectos. 6. Hay algún procedimiento predefinido a seguir, a menudo sólo entre el Moderador y el Autor, para comprobar que se han localizado los defectos. (No es necesario corregirlos: algunos defectos pueden considerarse demasiado pequeños para que merezca la pena corregirlos.) Por ejemplo, una revisión de diseño relativamente informal de un modelo de clases de UML para una iteración del desarrollo que se supone que tiene que entregar algunos casos de uso, podría encerrar a un grupo de entre cuatro y siete personas en una habitación durante una o dos horas. Al Autor, el desarrollador principal del modelo de UML, se le puede pedir que explique cómo el modelo de UML proporciona la funcionalidad de los casos de uso, con el resto de participantes haciendo preguntas e intentando exponer posibles problemas. El Moderador se asegura de que la reunión se mantiene en la línea fijada, y no se desvía, por ejemplo, en el diseño para corregir los defectos. El Copista registra los problemas que se descubren en la reunión, y si son serios, moderados o leves. Después de la reunión, se le proporciona al Autor una copia de la lista de defectos, y vuelve a tratar el diseño con los compañeros. Algún tiempo después (normalmente no más de una semana) hay una reunión de seguimiento entre el Autor y el Moderador para comprobar que se han tratado y corregido los defectos. En una revisión de código, tal y como sugiere el nombre, se ejecuta un proceso similar con el código.
19.6.1
Problemas de las RTF
Han pasado más de veinte años desde que fuesen descritas por primera vez las inspecciones de Fagan, y muchas de las autoridades dicen que pueden ser efectivas en el coste por encontrar defectos, especialmente defectos de diseño. Todavía las RTF no están tan extendidas como las pruebas, y, si son algo, es que son menos comunes en proyectos que siguen un proceso iterativo moderno que en aquellos que siguen el modelo en cascada. ¿Por qué? Básicamente, los problemas parecen estar agrupados en dos categorías. 1. Una RTF, cuyo objetivo es encontrar defectos en un artefacto, puede verse como un ataque sobre los desarrolladores del artefacto. 3. Las RTF consumen mucho tiempo, sobre todo cuando revisan código inadecuadamente documentado. Lo que es peor, las RTF a veces producen largas listas de defectos triviales, mientras faltan problemas más fundamentales con el artefacto. Pregunta de Discusión 139 Como Moderador de una inspección, ¿cómo se puede influir en el éxito?
CALIDAD DEL PRODUCTO: VERIFICACIÓN, VALIDACIÓN Y PRUEBA
253
Pregunta de Discusión 140 ¿Qué ventajas tienen las revisiones de código frente a las pruebas de código? ¿Qué desventajas?
Pregunta de Discusión 141 ¿Cuáles serían los criterios de entrada adecuados para un documento de diseño?
Pregunta de Discusión 142 Algunas organizaciones invitan a un representante del cliente a que diseñe las revisiones. ¿Qué ventajas y desventajas tiene hacer esto? ¿Qué factores influyen en si es una buena idea?
RESUMEN
Este capítulo ha considerado técnicas orientadas en el producto para mejorar la calidad, después de volver brevemente a la pregunta de qué es la calidad del software. Se han clasificado las actividades de mejora de la calidad del producto en verificación (comprobar si estamos construyendo correctamente el producto) y validación (comprobar si estamos construyendo el producto correcto). Se han tratado técnicas de verificación que van desde comprobaciones humanas informales, quizás guiadas por una lista de comprobaciones a realizar, hacia una prueba formal. Una parte importante de la validación es el análisis de la usabilidad, que requiere la implicación de los usuarios. La prueba es útil tanto para la verificación como para la validación; se han tratado las necesidades especiales de los sistemas orientados a objetos. Las revisiones técnicas formales también son útiles en ambas áreas. Sin embargo, se ha sugerido que centrarse en el producto puede no ser suficiente; en el siguiente capítulo se tratan las aproximaciones orientadas al proceso para asegurar la alta calidad.
Capítulo
20 Calidad del proceso: gestión, equipos y GC
En este capítulo final se trata la contribución hecha a la calidad de los sistema por parte de las organizaciones que los construyen y los procesos de garantía de calidad de dichas organizaciones. Se empieza con la gestión, líderes y equipos de trabajo, porque son determinantes en el éxito o en el fallo. Se continúa tratando lo que se entiende por garantía de calidad en el contexto del tipo de proyecto que se está tratando, y tratando el rol de un sistema de gestión de calidad.
20.1
Gestión Hay dos tipos de gestión importantes para el software: • gestión de personal. • gestión de proyectos. Ambos tipos de gestión pueden verse de dos maneras, entre las cuales hay tirantez.
Gestión como actividad de validación La función más importante del director de una tarea es permitir a la gente que está realizando una tarea avanzar en el trabajo. El director ideal minimiza, elimina, o, lo ideal, previene la aparición de obstáculos que incluyan riesgos —como es la pérdida de información, aprendizaje o equipamiento inadecuado— y otras influencias destructivas, como son las planificaciones surrealistas y la burocracia sin importancia. El director puede eliminar dichos obstáculos creando los argumentos necesarios para hacerlos desaparecer, y si es necesario haciendo él o ella el trabajo sucio 1. 1
Véase El Principio de Dilbert [1], p. 233.
256
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
Gestión como control (financiero) Quizá una visión más normal de gestión es que los directores controlan lo que ocurre, con el principal objetivo de maximizar el éxito financiero de la organización. De acuerdo con este punto de vista, la creación de productos de alta calidad, el realizar una contribución positiva a la sociedad, asegurar la felicidad de los clientes o los empleados, son todos objetivos secundarios: si importan es porque indirectamente van más lejos del objetivo financiero. Pregunta de Discusión 143 ¿Puede argumentar que las consecuencias financieras del fallo de un sistema crítico de seguridad son suficientes para hacer evitar que tal fallo sea un objetivo importante, incluso aunque el éxito financiero esté considerado como el único objetivo de alto nivel? ¿Se lo cree?
20.1.1
Gestión de proyectos
Un director de proyecto tiene toda la responsabilidad del éxito del proyecto. Las responsabilidades del director incluyen: • el análisis y el control de riesgos. • el enlace con el cliente o con otras partes de la organización. • la definición de líneas de comunicación (entre equipos del proyecto, y entre la gente y el cliente). • el asegurar que se selecciona la gente apropiada para el proyecto, y que están entrenados de manera apropiada. • el producir un plan para el proyecto, incluyendo una planificación, una estimación de costes y un plan de calidad. • la asignación de tareas (entre equipos de un proyecto grande, a personas individuales en uno pequeño). • la medida del progreso del proyecto. • el asegurar que se utilizan los componentes, herramientas y técnicas apropiadas. • el mantener el proyecto dirigido, y actuar para limitar los daños si se desvía. • el asegurar que cualquier obligación contractual, como es el cumplimiento de estándares, se cumple. • el asegurarse de que el proyecto implementa las mejoras sugeridas por la experiencia de proyectos anteriores y que la experiencia de este proyecto se vuelve a utilizar en otros proyectos de la organización. Por supuesto, esto no quiere decir que el director personalmente realice todas estas tareas. Pregunta de Discusión 144 ¿Falta algo?
CALIDAD DEL PROCESO: GESTIÓN, EQUIPOS Y GC
257
Pregunta de Discusión 144 Una clasificación de las tareas de un director de proyecto [45] es: • planificar • organizar • seleccionar personal • monitorizar • controlar • innovar • representar ¿Las tareas listadas y las que se le ocurran, se encuentran en esta clasificación? ¿Falta algo?
20.1.2
Estimación de un proyecto iterativo
Una organización que cambia a un proceso de desarrollo iterativo desde un proceso en cascada tradicional, tiene que formar nuevos expertos de estimación, planificación y organización. Por ejemplo, en un proyecto iterativo automáticamente no hay hitos como “el final del análisis” que se pueda utilizar para medir el progreso de un proyecto. Un hito apropiado podría ser, por ejemplo la entrega de una iteración que contenga una funcionalidad particular. En el Capítulo 7 se mencionó el juego de planificación de Kent Beck como técnica para la estimación de proyectos a partir de los casos de uso: es decir, las unidades de tiempo que entran dentro del calendario son los tiempos tomados para cada caso de uso (incluyendo el tiempo de diseño, pruebas y documentación para el caso de uso), en vez de para las fases del proyecto (todo el diseño, toda la documentación, etc.). Se vuelve a construir un calendario de partes más comprensibles y estimables, que ayudan a evitar hacer estimaciones irrealmente optimistas (¡o pesimistas!). Recordar (del Capítulo 1) sea cual sea la estadística que se mire, que una parte significativa de proyectos grandes llevan más tiempo que el planificado. ¿Por qué es tan difícil hacer una buena estimación? La principal razón es, por supuesto, la falta de información en la que basar la estimación; esto se aplica en particular a los sistemas grandes, todo en uno, construidos para cubrir las necesidades del conjunto de problemas de un cliente concreto. El calendario del proyecto normalmente se fija mucho antes de que nadie tenga un conocimiento detallado de los requisitos del proyecto. Por lo que se hace un calendario basándose en la ‘mejor aproximación’ de los requisitos, y los requisitos reales se encuentran a medida que avanza el proyecto. Además, los requisitos (¡incluso los “ideales” que pueden conocerse o no!) no son estáticos. Las necesidades del cliente cambian. Si un proyecto dura varios años, la naturaleza del problema a resolver puede ser muy diferente, al final del proyecto, a la del principio. Pero se debe esperar un momento. Se ha explicado que la estimación es difícil de hacer: pero si esto fuera todo, ¿no se esperaría que los proyectos se entregasen masivamente mucho antes del calendario, tan a menudo como los que se entregan después? No: las estimaciones muchas veces (no siempre) son demasiado optimistas, en vez de demasiado pesimistas. Hay mucha presión para alcanzar una estimación que sea aceptable para el cliente (o el departamento de márketing,
258
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
u otra parte de la organización que actúe como cliente). Una de las tareas más difíciles de un director de proyecto es defender el calendario del proyecto, incluso si le sienta mal al cliente: véase [34] sobre esto. La realidad de que los proyectos muchas veces se desvíen no es una excusa para complacer. Cada desvío es un fallo.
Pregunta de Discusión 146 Mire de forma crítica las estadísticas implicadas en la medida del éxito de la estimación de proyectos: realice algunos cálculos de la forma “Si hay 100 proyectos y 50 de ellos se entregan en la mitad del tiempo planificado...” y vea lo que obtiene de la principal desviación. ¿Es esta la mejor estadística para mirar? Suponga que es un director de proyecto que ha de decidir qué estadísticas debe utilizar su organización para medir cómo son sus estimaciones en la práctica, y seguir si los cambios en los procesos son mejoras o no. ¿Qué medidas se deberían tomar, y qué estadísticas serían más útiles?
Pregunta de Discusión 147 Investigue sobre una o más herramientas y modelos de estimación, como es COCOMO. ¿Cuáles serían los beneficios de utilizar tal herramienta o modelo? (Piense en los efectos sociales, así como en los técnicos.) ¿Sería adecuado para un proceso iterativo? ¿Hay alguna desventaja?
Pregunta de Discusión 148 ¿A qué aspectos psicológicos del ser humano puede afectar la estimación? Puede ser interesante el artículo de Mark Paulk [36].
20.1.3
Gestión del desarrollo basado en componentes
Parte de la responsabilidad del director del proyecto es validar que los desarrolladores hacen lo que pueden por utilizar componentes disponibles. Esto puede incluir la autorización para investigar y comprar bibliotecas de componentes, enlazar con otras partes de la organización para posibles reutilizaciones, y (quizá lo más importante) estar seguros de que el proyecto tiene mecanismos para asegurar que se cumple la reutilización interna. Esto puede ser difícil en un proyecto dirigido a los casos de uso; es fácil que diferentes equipos, encargados de implementar distintos casos de uso, vuelvan a implementar gran parte de la funcionalidad debido a que aparece en varios casos de uso y no se ha identificado como funcionalidad común. Las técnicas de solución incluyen la comprensión suficiente de lo que están haciendo los equipos para ponerlos en contacto con otro equipo cuando aparecen elementos comunes; y a nivel técnico, incluyen el uso apropiado de la relación <> cuando se desarrolla el modelo de casos de uso. Se ha dicho que el proceso de desarrollo necesita ser centrado en la arquitectura. Alguien tiene que defender la arquitectura: los Patrones de Organización de James Coplien (véase la
CALIDAD DEL PROCESO: GESTIÓN, EQUIPOS Y GC
259
página web del libro) sugieren que para asegurar la coherencia de la arquitectura de un sistema, debería haber una única arquitectura, pero que debe ser revisada por otras personas. El director no tiene que ser el arquitecto, y claro está, no suele serlo, pero se tiene que asegurar de que hay alguien responsable de la arquitectura y que el equipo del proyecto lo acepta.
Q: ¿Por qué se debería revisar la arquitectura? El director de proyectos debe asegurar, también, que el proyecto cumple cualquier responsabilidad que tiene en la estrategia de construcción de componentes de la organización; por ejemplo, que los componentes potenciales se identifiquen, documenten, prueben y se localicen en la biblioteca de componentes. Pregunta de Discusión 149 ¿Bajo qué título de los dados en la subdivisión 20.1.1 se reúnen estas responsabilidades del director de proyectos?
20.1.4
Gestión del personal
Normalmente cada empleado tiene un director de personal. Puede ser la misma persona que el director de proyecto, pero (especialmente en grandes organizaciones) puede no serlo. Aunque se puede tener un director de proyecto diferente para cada proyecto, un director de personal (a veces llamado jefe de producción) tendrá una amplia implicación. Su trabajo consiste en asegurar que progresa la carrera de los empleados, que reciben una correcta formación, que su rendimiento es correcto, etc. Probablemente él o ella tiene la obligación de decidir qué proyectos asignar a cada uno. El término gestión en matriz a veces se utiliza para la situación en la que es normal tener dos personas distintas gestionando un proyecto y dirigiendo al personal: la idea es que uno define en qué fila de una matriz se encuentran los empleados y el otro define en qué columna se encuentran. El principal motivo para tener dos personas diferentes como director de proyecto y director de personal es que hay un conflicto de intereses entre los roles, ya que el director de proyecto tiene los mejores intereses del proyecto a corto plazo, mientras que el director de personal mira más allá de los intereses a largo plazo de la compañía eligiendo mejor al personal y haciéndoles que desarrollen sus facultades. Por otro lado, una estructura en la que el personal tiene demasiados directores con diferentes responsabilidades necesita tener una definición cuidadosa para que no haya confusiones; o ni el director sabrá el trabajo del empleado como lo sabría un único director. Pregunta de Discusión 150 ¿Un jefe de producción necesita comportarse de forma diferente dependiendo de si la organización utiliza el tipo de proceso de desarrollo aquí tratado, o un método en cascada tradicional? Si es así, ¿cómo? Pregunta de Discusión 151 ¿Cuáles son las características de un buen jefe? ¿Cambia según se trate de un director de proyectos o de personal? Si es así, ¿cómo?
260
20.2
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
Equipos Pregunta de Discusión 152 Piense en los equipos de los que ha formado parte. ¿Hasta qué punto eran equipos con éxito? ¿Qué factores influyen en esto?
¿Qué hace que un equipo tenga éxito? Una respuesta parcial es que sus miembros: • tengan características personales complementarias. Por ejemplo, el equipo necesita gente (quizá gente diferente) que sea buena en: — ver el punto de vista del cliente. — tener ideas. — encontrar las imperfecciones en las ideas. — ayudar a que el equipo esté centrado en la tarea. — asegurar que todo el mundo sea escuchado. • tengan un equilibrio en su habilidad para las tareas. • sean gente que se lleva bien unos con otros: que, al menos, tengan un nivel razonable de confianza y respeto unos con otros. • formen parte de una organización más grande, que básicamente tenga éxito y esté satisfecha con los resultados. El éxito se construye sobre el éxito. Por ejemplo, cuanto más éxito tenga un equipo, es más probable tener un pequeño volumen de negocio, que probablemente lleve a un éxito mayor... Los equipos de más bajo nivel normalmente tienen entre tres y ocho personas: es lo suficientemente pequeño para controlar los problemas de comunicación, aunque por supuesto el tamaño depende del proyecto. Cada equipo debería tener una tarea claramente definida, en caso contrario la carga de comunicarse con otros equipos será arrolladora. Normalmente habrá un jefe de equipo: puede ser un miembro del equipo designado para un solo proyecto, o puede ser una persona aparte, nombrada por algún directivo. El jefe de equipo tendrá la responsabilidad fundamental de gestionar las interacciones con otros equipos. En un proyecto grande puede haber una jerarquía con grupos de equipos que forman subproyectos bajo un director de subproyecto y así sucesivamente. La gestión de comunicaciones en tales proyectos es muy difícil, y el fracaso en esta parte es la razón fundamental por la que se pierden proyectos. Hay varias aproximaciones para hacer frente a esto. En un extremo, un proyecto grande puede intentar mantener todas las interacciones entre gente de diferentes equipos para gestionar los caminos: uno habla con su director de equipo, que habla con su director de subproyecto, que habla con otro director de subproyecto, que habla con un jefe de equipo de ese subproyecto que... En el otro extremo, un proyecto puede intentar mantener abiertos todos los canales posibles de comunicación, por ejemplo teniendo reuniones de proyecto regulares a las que acuden todas las personas implicadas en un proyecto. (Si se sabe que uno trabaja mejor que otro, ¡se tendría menos crisis de software de las que se tiene!) Destacar, sin embargo, que tener una estructura conocida para permitir la comunicación es más importante que la estructura en sí.
CALIDAD DEL PROCESO: GESTIÓN, EQUIPOS Y GC
261
Los roles individuales dentro de un equipo pueden estar bien definidos o no, dependiendo del proceso seguido. Hay también un concepto más amplio de equipo de trabajo implicando al “tirar juntos” de una compañía entera —esto es más borroso, pero depende de los factores tales como el sentimiento de valorado o no que tiene un desarrollador en una organización, y que todos actúen por un objetivo común.
20.3
Liderazgo El liderazgo muchas veces se confunde con gestión (¡no menos los directores!) pero las funciones son fundamentalmente diferentes, y no deben ser realizadas por las mismas personas; claro está, es posible discutir lo contrario 2. Por lo que, ¿cuál es la diferencia? Es difícil de explicar, pero la mejor que se ha encontrado es la siguiente: Un director es alguien que transmite calma y un líder es un animador.
Un líder es una persona que sabe a dónde va y tiene ideas de cómo llegar hasta allí: una persona con visión. Los líderes se pueden centrar en los objetivos más importantes perdiendo visión periférica. No son fáciles de apartar de su propósito y, claro está, no se les puede desviar de su curso de ninguna manera. Tal y como se ve, cualquiera de estas características pueden ser fortalezas o debilidades, y cuál de ellas está bien en ciertas circunstancias ¡depende de si el observador está de acuerdo con el líder o no! Pregunta de Discusión 153 Una explicación alternativa de la diferencia (que Stephen Covey explica elocuentemente en [11], donde se lo atribuye “tanto a Peter Drucker como a Warren Bennis”) es “La dirección está haciendo las cosas correctamente; el liderazgo está haciendo las cosas correctas”. Estructuralmente esto es similar a la explicación de la diferencia entre validación y verificación, que se trató en el Capítulo 19. ¿Se pueden establecer conexiones entre los cuatro temas?
Las habilidades de gestión pueden aprenderse (hasta cierto punto), y quien sea un buen director probablemente será muy competente en la dirección de cualquier tarea que entienda lo suficiente. No está tan claro que las habilidades de liderazgo se puedan aprender. Es muy posible que alguien sea un buen líder para una tarea, y que no sirva para liderar otra, ya que es importante un compromiso personal en la tarea. Por otro lado, para el liderazgo no es básico que venga siempre de la misma persona, incluso dentro de una misma tarea. Debido a que una gran parte de la buena gestión está en la comprensión de las líneas de comunicación, se piensa que no es posible distribuir la gestión de la misma manera. Alguien puede discutir que el nombramiento dentro de una jerarquía debería buscar primero las buenas habilidades de gestión, y permitir que el liderazgo surja donde deba; esta es una de las justificaciones de la gestión matricial. 2
Los autores han observado varias veces las situaciones aparentemente exitosas donde X es un líder mientras que el sustituto de X es un director: sería interesante ver qué tienen en común.
262
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
Pregunta de Discusión 154 ¿Alguien puede dirigir y liderar a la vez? Pregunta de Discusión 155 ¿Qué hace que un líder sea bueno? Pregunta de Discusión 156 ¿Qué cree que es más probable formar un/a director/a o un/a líder? Pregunta de Discusión 157 Piense en algunas personas con autoridad sobre usted. ¿Son buenos directores? ¿Buenos líderes? ¿Cómo debería ser la persona ideal para ese puesto?
Pregunta de Discusión 158 ¿Qué cualidades personales y habilidades necesita el arquitecto del proyecto?
20.3.1
Reforma del proceso de desarrollo
Cualquier cambio importante en la manera en que funciona una organización, incluyendo un cambio a una nueva cultura de desarrollo, necesita a alguien que lidere la reforma. Los cambios, tales como el movimiento hacia un desarrollo iterativo, establecimiento de un programa de reutilización, o adopción de una orientación a objetos, son ejemplos. Es igualmente necesario que la reforma esté apoyada por una buena gestión; si falta la gestión o el liderazgo, se puede esperar que falle la reforma.
20.4
Garantía de calidad La garantía de calidad es el proceso de: • convencerse a uno mismo y al cliente de que cualquier producto que se entregue es de alta calidad. • proporcionar una base para la mejora continua. Esto se hace mediante la monitorización y la mejora del proceso de desarrollo del software con el objetivo de aumentar las posibilidades de que las personas que sigan el proceso producirán un software de alta calidad 3. 3
GC a veces se utiliza en un sentido más restrictivo incluyendo sólo la parte de análisis de su definición, en cuyo caso el término mejora del proceso puede utilizarse para el resto.
CALIDAD DEL PROCESO: GESTIÓN, EQUIPOS Y GC
263
Una organización normalmente tendrá un sistema de gestión de calidad (SGC 4: el documento a veces se llama manual de garantía de calidad, o similar). Especifica qué estructuras y procesos tiene la organización para asegurar que cada proyecto sigue un proceso de calidad apropiado, y que el proceso está en mejora continua utilizando la experiencia de proyectos anteriores. Por ejemplo, el SGC puede especificar que cada proyecto debería tener un plan de calidad y define lo que debería cubrir. El plan de calidad normalmente forma parte del plan del proyecto completo, y describe aspectos de la forma en que ha de ejecutarse el proyecto, que pretenden asegurar la alta calidad. Puede definir, por ejemplo, qué documentos de diseño se van a producir y cómo se van a revisar. El SGC también especifica qué auditorías de calidad tiene que ejecutar la organización para asegurar que sigue un plan de calidad de proyecto, y especifica cómo se proponen, validan y aplican las mejoras. Pregunta de Discusión 159 Intente acceder a un SGM real (por ejemplo, desde la página de inicio del libro) y estúdielo.
Una manera de que una organización aumente su confianza en que su control de calidad es el adecuado, es adquirir un certificado de calidad como son la ISO9001/BS5750 o TickIt. Normalmente, personas ajenas a la organización realizan varias auditorias de proyectos antes de que se autorice el certificado. En algunos contextos, particularmente en el desarrollo para defensa, u otras organizaciones gubernamentales, y de sistemas críticos de seguridad, se requiere el seguimiento de estándares particulares, o un certificado particular, bien legal o contractualmente. Pregunta de Discusión 160 Investigue sobre dos o más estándares de calidad; compárelos.
El aspecto más importante de la garantía de calidad, sin embargo, es que mediante la documentación y la medida de lo que se hace y de cómo funciona, se proporciona una base para continuas mejoras del proceso de producción de software, con el objetivo de alcanzar un nivel de mejora continua de la calidad del producto. Si se sabe lo que se ha hecho diferente en un proyecto en particular que ha tenido éxito, y se tiene la manera de realizar cambios en una organización completa, se puede difundir innovaciones exitosas a lo largo de toda la organización. Desafortunadamente, muchos sistemas de calidad realmente trabajan en contra de este objetivo, haciendo difícil y burocrático el cambio de los procesos, incluso cuando la gente está de acuerdo con que el cambio es para mejorar: véase el Panel 20.1 “Garantía de calidad: el caso contrario”. Las empresas internacionales y americanas a menudo clasifican el proceso que siguen según el “nivel n de CMM” al que pertenecen, donde n se encuentra entre 1 y 5. Esto hacer referencia al Modelo de Madurez de Capacidad desarrollado en el SEI de CMU, que también proporciona un marco de trabajo para la mejora del proceso. En resumen, la idea es mejorar la calidad del proceso de desarrollo del software mediante: • • • •
la documentación de cómo se realiza el trabajo. la medida de su ejecución. la utilización de datos para controlar la ejecución. la planificación basada en las ejecuciones históricas.
• la preparación de personas. 4
En inglés, QMS (Quality Management System).
264
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
El CMM tiene cinco niveles, desde el Nivel 1 (Inicial) donde el proceso es ad hoc y los proyectos tienen éxito gracias a la “heroicidad individual”, hasta el Nivel 5 (Optimización) donde la organización realiza medidas cuantitativas de sus proyectos y utiliza esas medidas para poner a punto los procesos. El Nivel 3 (Definido) se corresponde, más o menos, con la ISO9001; la organización tiene un proceso bien definido, pero no necesita apoyarlo con medidas cuantitativas. Pregunta de Discusión 161 ¿Qué tipos de medidas cuantitativas son relevantes, y cómo? Investigue sobre métricas, utilizando cualquier recurso disponible (como siempre, la página de inicio de este libro puede ser un punto de partida). ¿Qué cualidades debe tener una métrica para ser útil?
Pregunta de Discusión 162 ¿Cuáles son las diferencias entre un SGM adecuado para proyectos críticos de seguridad y otro para proyectos principales?
20.4.1
Garantía de calidad en proyectos iterativos
Los proyectos iterativos presentan retos a los sistemas de garantía de calidad debido a que, por ejemplo, habrá varias versiones diferentes de la documentación generada en momentos distintos del proyecto. El tener un documento revisado y firmado por la persona adecuada puede tener menos significado cuando el documento sea cambiado de nuevo la siguiente semana. Un SGC para un proyecto iterativo probablemente define, por ejemplo, lo que tiene que hacerse antes de que una iteración llegue al cliente, en vez de especificar lo que se tiene que hacer antes del final de la fase de análisis de un proceso en cascada.
20.4.2
Gestión de la Calidad Total
GCT 5 trabaja con la base de que la calidad de un producto se ve más afectada por el compromiso de alta calidad por parte de las personas implicadas en la producción del producto, que por el cuidado con que se sigue un proceso aprobado. Ken Croucher escribe en [12] que “La calidad se alcanza realizando un número ilimitado de mejoras triviales”. La idea de GCT es que todo el mundo debe contribuir en las mejoras. Apoyando este punto de vista, GCT también incluye la mejora de la calidad de las personas, para cumplir las necesidades de los proyectos en los que trabajan. Siendo conscientes de que la necesidad de preparación es fundamental en la adopción de GCT. Pregunta de Discusión 163 ¿Cuál es la relación entre GCT y un sistema de garantía de calidad formal? ¿Están en conflicto, o pueden apoyarse el uno al otro? ¿Cómo? 5
En inglés, TQM (Total Quality Management).
CALIDAD DEL PROCESO: GESTIÓN, EQUIPOS Y GC
265
PANEL 20.1 Garantía de Calidad: el caso contrario
En las universidades, y en algunos contextos industriales, se está acostumbrado a ver la garantía de calidad formalizada —el registro de la ISO9001, los altos niveles CMM, etc.— que se venden como Cosas Buenas 6. Por lo que, ¿por qué no están todas las organizaciones registradas en la ISO9001? ¿Simplemente son tontos sus directores? Por supuesto que no. Algunos de los argumentos que se pueden hacer en contra de los registros son los siguientes. • Los costes implicados superan los beneficios. Este es el núcleo del problema. Aumentar la cantidad de documentación que hay que generar y el número de reuniones es caro: y la introducción de un SGC casi siempre provoca estos efectos. El beneficio se supone que mejora la calidad de los productos, que aumentan los beneficios financieros lo suficiente como para superar los costes. Sin embargo, se necesita tener más cuidado para asegurarse de que cada aspecto del proceso de GC se financia a sí mismo. • La documentación del proceso que debe seguirse hace que el proyecto sea inflexible: no puede reaccionar de forma adecuada ante circunstancias particulares. • Desanima a que la gente piense. Si el proceso que se supone que se debe seguir para alcanzar productos de alta calidad lo establece un SGC, se tiende a dejar de pensar independientemente sobre cómo hacer las cosas, y en vez de ello se confía solamente en el SGC. Esto no funciona. • Desmoraliza a la mejor plantilla. Tener que escribir la documentación y tener muchas reuniones es menos interesante que el desarrollo de software. E introducir un SGC a menudo parece implicar documentos escritos que nadie va a utilizar y reuniones inútiles. • Controlar lo que se debe hacer realmente impide la innovación, porque si se quiere hacer algo diferente hay que escribirlo en el plan de calidad y tiene que aprobarse. La única esperanza es que esto es una crítica de la mala garantía de calidad, no de la garantía de calidad per se. Pregunta de Discusión 164 ¿(Cómo) un proceso de garantía de calidad puede evitar los problemas anteriores?
La garantía de calidad trata de asegurar la alta calidad. Si alguna actividad no ayuda a alcanzar la calidad, ¿por qué se realiza?
Por supuesto, puede haber respuestas válidas para esta pregunta retórica: la más común es ‘Porque el cliente sólo tratará con proveedores con el registro de la ISO9001’. Incluso aquí, sin embargo, normalmente no hay ninguna razón para continuar con las prácticas que no son útiles. Se intenta que los estándares sean lo suficientemente flexibles para que sean una ayuda y no un obstáculo. 6 Excepto, enérgicamente, que la gente no está siempre a gusto con la GC cuando se le aplica a sus propias actividades...
266
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
Pregunta de Discusión 165 ¿Es cierto que algunos tipos de organizaciones o proyectos (por ejemplo, las que son muy pequeñas, o las que tienen una plantilla muy experimentada) no se beneficiarán de la introducción de un SGC, por muy razonable que sea? Si es así, ¿Qué organizaciones o proyectos entran dentro de esta categoría?
Pregunta de Discusión 166 ¿Es justo agrupar la mejora de marcos de trabajo como CMM junto con la ISO9001 en este contexto? ¿O sólo surgen estos problemas si nos centramos en el proceso de monitorización sin mejorarlo?
Pregunta de Discusión 167 ¿Cuáles son las interacciones entre la cultura de la calidad y la reutilización? Se debería pensar en los posibles efectos negativos de un sistema de GC de una organización sobre su programa de reutilización, así como en los efectos beneficiosos. (Se podría consultar el artículo de Martín Griss —[26]— o véase la página de inicio del libro —que establece las relaciones entre CMM y reutilización.)
20.5
Más información Hay una gran cantidad de información disponible sobre los temas de este capítulo, incluyendo Internet. Como siempre, se puede empezar en la página web del libro.
RESUMEN
En este capítulo final se ha tratado la contribución en los sistemas de calidad de las organizaciones que los construyen, y los procesos de garantía de calidad de estas organizaciones. Se ha tratado la gestión, el liderazgo, los equipos, las organizaciones y la garantía de calidad. Se ha hecho hincapié en que la calidad y la garantía de calidad son cosas diferentes pero relacionadas. Por supuesto, están estrechamente relacionadas con otras propiedades de una organización y el software que produce.
PREGUNTAS DE DISCUSIÓN
Estas preguntas finales invitan a pensar en los temas tratados. Por supuesto, ¡no hay respuestas correctas ni respuestas incorrectas! 1. ¿Preferiría trabajar en una situación en la que tuviera una gran cantidad de autonomía y responsabilidad para tratar con clientes directamente; o preferiría centrarse en resolver problemas técnicos bien definidos, dejando que otros traten con los clientes?
CALIDAD DEL PROCESO: GESTIÓN, EQUIPOS Y GC
267
2. Promoción: algunas organizaciones que han promocionado, implican necesariamente la aceptación de más responsabilidades de gestión. En otras, hay un “historial técnico” que permite ascender a una posición de “experto técnico” disponible para consultas en su área de conocimiento, sin tener ninguna relación con la gestión. ¿Qué cree que es mejor? 3. La mayoría de las estructuras de gestión incluirán la idea de que un determinado equipo tenga un “líder de equipo”. En algunos casos, el líder del equipo será alguien con un puesto diferente del resto del equipo y experto en gestión; esto es, hay que tener una promoción formal antes de ser un líder de equipo, y de ahí en adelante (casi) siempre se liderará el equipo en el que se trabaje. En otras organizaciones, cualquier persona, por muy reciente que haya sido la designación de su puesto, puede actuar como líder de un equipo en un proyecto. En el último caso, la decisión debe tomarse en base a factores como la experiencia de las personas en el campo técnico o del negocio. ¿Cuál cree que es mejor, o cuál prefiere? 4. ¿Preferiría trabajar en un proyecto grande o en uno pequeño? 5. ¿Preferiría participar en el desarrollo de nuevos productos o en el mantenimiento de productos existentes? 6. ¿Su empresa debería emplear contratistas para equilibrar los picos y las caídas del trabajo? ¿Y consultores especialistas? ¿Cuál debería ser su actitud ante la contratación de parte de su trabajo? ¿Qué partes? Por ejemplo, ¿debería contratar a los especialistas para las partes rutinarias aburridas, o para las partes difíciles? 7. ¿Prefiere participar en productos que se entienden bien, donde los retos tienen que ver con producir exactamente la variante correcta de algo bien conocido? ¿O preferiría trabajar en algo innovador técnicamente, donde el reto está en hacer que las cosas funcionen completamente, y donde pueda parecer imposible conseguirlo? 8. ¿Preferiría trabajar en una organización que tiene un certificado como el BS5750/ISO9001, o en una que no lo tiene? 9. ¿Preferiría trabajar en la división de TI de una gran organización, satisfaciendo las necesidades de TI de dicha organización? ¿O preferiría trabajar para una empresa de software especializado que puja por contratos a terceros? ¿O para alguien cuyo negocio es vender software “fácil de obtener”?
Bibliografía
[1] Scott Adams. The Dilbert Principel. Nueva York: Harper Collins, 1996. [2] K. Beck y W. Cunningham. “A laboratory for teaching object-oriented thinking”. ACM SIGPLAN Notices, 24(10): 1-6, octubre 1989. [3] Kent Beck. Smalltalk Best Practice Patterns. Englewood Cliffs, NJ: Prentice Hall, 1996. [4] Barry W. Boehm.”A spiral model os software development and enhancement”. IEEE Computer, 21(5): 61-72, 1988. [5] Grady Booch. Component ware chat hosted by Grady Booch transcript. http://www.rational.com/connection/chats/docs/cwchat.html. [6] Grady Booch. “Rational’s definition of component”. Posting to comp.object, 11 de mayo de 1998. [7] Grady Booch. Object-Oriented Design with Applications. Benjamin/Cummings series un Ada and software engineering. Menlo Park, CA: Benjamin/Cummings Pub. Co., 1991. [8] Donald G. Firesmith, Brian Henderson-Sellers y Ian Graham. The OPEN Modeling Language (OML) eference Model. Englewood Cliffs, NJ: Prentice Hall, 1998. [9] Frederick P. Brooks. The Mythical Man-Month. Reading, MA: Addison-Wesley, 1975/1995. [10] Frank Buschmann, Regine Meunier, Hans Rohnert, Peter Sommerland y Michael Stad. Pattern-Oriented Software Architecture - A System of Patterns. Nueva York: John Wiley, 1996. [11] Stephen R. Covey. The Seven Habits of Highly Effective People. Nueva York: Simon & Schuster, 1992. [12] Ken Croucher. “Co-existence of TQM and quality mangement systems”. SQM, 8, 1991. [13] Tom DeMarco y Timothy Lister. Peopleware: Productive Projects and Teams. Nueva York: Dorset House, 1987. [14] E. W. Dijkstra. “Goto statement considered harmful”. Communications of the ACM, 11: 147-148, 1968. [15] Alan Dix, Janet Finlay, Gregory Abowd y Russell Beale. Human-Computer Interaction. Englewood Cliffs, NJ: Prentice Hall, 1993.
270
BIBLIOGRAFÍA
[16] Desmond D’Souza y Alan Cameron Wills. Catalysis: Objects, Frameworks and Components in UML. Reading, MA: Addison-Wesley, 1998. [17] Christopher Alexander et al. A Pattern Language. Oxford: OUP, 1977. [18] M. E. Fagan. “Design and code inspection to reduce errors in program development”. IBM Systems Journal, 182-211, 1976. [19] M. Fowler y K. Scott. UML Distilled: Applying the Standard Object Modeling Language. Reading, MA.: Addison-Wesley, 1997. [20] Martin Fowler. Analysis Patterns: Reusable Objects Models. Reading, MA: AddisonWesley, 1997. [21] W. R. Franta. A Process View of Simulation. Amsterdam: North-Holland, 1977. [22] Bruce F. Webster. Pitfalls of Object-Oriented Development. Foster City, CA: M & T Bokks, 1995. [23] Erich Gamma, Richard Helm, Ralph Johnson y John Vlissides. Design Patterns. Reading, MA: Addison Wesley, 1995. [24] W. Wayt Gibbs. Software’s chronic crisis. Scientif American (International Edition), 72-81, septiembre de 1994. [25] Alan Gillies. Software Quality: Theory and Management. Florencia, KY: International Thomson, 1997. [26] Martin L. Griss. “CMM as a framework for adopting systematic reuse”. Object Magazine, 10, 1998. [27] B. Henderson-Sellers y Y. R. Pant. “Adopting the reuse mindset throughout the lifecycle”. Object Magazine 3(4), noviembre/diciembre de 1993. [28] I. Jacobson, M. Christenson, P. Jonsson y G. Oevergaard. Object-Oriented Software Engineering: A Use Case Driven Approach. Harlow: Addison-Wesley, 1992. [29] I. Jacobson, M. Griss y P. Jonsson. Software Reude: Architecture, Process and Organization for Business Success. Harlow: Addison-Wesley, 1997. [30] Setrag Khoshafian. Object-Oriented Databases. Nueva York: Wiley, 1993. [31] Philippe B. Kruchten. “The 4 1 view model of architecture”. IEE Software, 12(6): 4250, noviembre de 1995. [32] Thomas K. Landauer. The Trouble with Computers: Usefulness, Usability and Productivity. Cambridge, MA: MIT Press, 1996. [33] Karl J. Lieberherr y Ian Holland. “Formulations and benefits of the Law of Demeter”. ACM SIGPLAN Notices, 24(3): pp. 67-68, marzo de 1989. [34] Steve McConnell. “Best practices: How to defend an unpopular schedule”. IEEE Software, 13(3): 118-120, mayo de 1996. [35] B. Meyer. Object-Oriented Software Construction. Nueva York: Prentice Hall, 1989. [36] Mark C. Paulk. “The rational planning of (software) projects”. SEI Report 1995. [37] Nathan H. Petschenik. “Practical priorities in system testing”. IEEE Software, 2(5): pp. 19-23, septiembre de 1985.
BIBLIOGRAFÍA
271
[38] James Newkirk y Robert C. Martin. “A case study of OOD and reuse in C”. ROAD, 1995. [39] Robert Harper, Robin Milner, Mads Tofte y David MacQueen. The Definition of Standard ML (Revised). Cambridge, MA: MIT Press, 1997. [40] J. Rumbaugh, M. Blaha, W. Premerlani, F. Eddy y W. Lorensen. Object-Oriented Modeling and Design. Englewood Cliffs, NJ: Prentice-Hall, 1991. [41] M. Shaw y D. Garlan. Software Architecture. Perspectives on an Emerging Discipline. Englewood Cliffs, NJ: Prentice-Hall, 1996. [42] S. Shlaer y S. J. Mellor. Object-Oriented Systems Analysis - Modeling the World in Data. Computing Series, Englewood Cliffs, NJ: Yourdon Press, 1988. [43] I. Sommerville y P. Sawyer. Requirements Engineering. Chichester: Wiley, 1997. [44] Clemens Szyperski. Component Software. Harlow: Addison-Wesley, 1997. [45] The Course Team. M355, Topics in Software Engineering. The Open University, Milton Keynes, UK, 1995. [46] The Course Team. M868, Object-Oriented Software Technology. The Open University, Milton Keynes, UK, 1995. [47] Harold Thimbleby. User Interface design. Reading, MA: Addison-Wesley (ACM Press). [48] UML 2.0 Superstructure Specification, OMG ptc/04-10-02 2004. [49] UML 2.0 OCL Specification, OMG ptc/03-10-14 2003. [50] Larry Wall y Randal L. Schwartz. Programming Perl. O’Reilly & Associates, Inc., 1992. [51] Jos Warmer y Anneke Kleppe. The Object Constraint Language. Addison-Wesley, 1999.
Índice analítico
A
B
abstracción, 8, 12, 24, 37 abstracto clase, 98 propiedad, 215 acción, 151 acoplamiento, 9 activación, 128 actividad, 157 actor, 33, 103 en colaboración, 127 no humano, 106 Ada, 7 agregación, 83 análisis, 37, 50, 81 de riesgos en Objectory, 50 fase del proceso en espiral, 53 argumento, 19 arquitectura, 12, 13, 55, 57, 167, 228, 229 cliente-servidor, 8 modelo de vista 4+1, 58 orientada al modelo, 58 artefacto, 170 aserción, 45 asíncrono, 143 asociación, 68, 83 calificada, 88 derivada, 89 final, 76 navegabilidad, 86 triángulo negro sobre el nombre de, 89 atributo, 18, 72 derivado, 90 auditorías de calidad, 263
Banda de los Cuatro, 237 barra de sincronización, 158 base de datos, 47, 171 Beck, Kent, 23,56, 78, 81, 257 beneficiario, 105 binario, 229 Booch, Grady, 18, 54, 55, 67 borrado de objeto, 134 Brooks, Fred, 7
C C++, 22, 29, 45, 98, 101 calidad, 3, 33, 241 calle, 159 captura de requisitos, 109 caso de uso, 33, 103 análisis, 113 detalle de, 108 para captura de requisitos, 109 problemas, 112 recorrido, 112 relación entre, 115 y reutilización, 116 catálisis, 56 catálogo de patrones frente a lenguaje de patrones, 212 centrado en arquitectura, 12, 258 clase, 21, 63 abstracta, 98 asociación, 93 base, 27
274
ÍNDICE ANALÍTICO
derivada, 27 invariante de, 45, 91 parametrizada, 100 clasificador, 98 cliente, 8 cohesión, 8, 12 colaboración, 126, 238 componente,8, 13, 116, 118, 156, 168 comportamiento, 18 composición, 83, 84 tardía, 13 concurrencia, 141 en diagramas de estado, 165 conectividad, 14 construcción fase de Objectory, 51 Coplien, James, 258 cortar y pegar, 228 creación de objeto, 22, 134 crisis de software, 7, 13, 231, 260 Cunningham, Ward, 78
D DDD, 65 decisión de arquitectura, 149 Demeter, Ley de, 130 dependencia,9, 83, 95 circular, 9, 214 desarrollo basado en componentes, 12 estimado en, 257 garantía de calidad en, 264 iterativo,31, 53, 55 movimiento hacia, 262 prueba en, 247, 251 RTFs en, 252 verificación en, 243 diagrama de actividad,147 de casos de uso, 34, 103 de clases, 63 de comunicación, 125, 127 de despliegue, 143, 167, 169 de estado, 138, 148 concurrencia en, 165 utilizado en pruebas, 224, 249 de interacción, 108, 125 de secuencia,125 de vista general de interacción, 160 diamante de decisión, 158 dirigido por casos de uso, 55, 113
diseño,49, 57 centrado en el usuario, 113, 245 dirigido a datos, 65 dirigido a la responsabilidad, 65 monolítico, 67 por contrato, 43, 74 disparador, 151 división, 158 dominio, 24, 37, 65
E Eiffel, 26, 45 elaboración fase de Objectory, 50 elemento del modelo, 173 elementos derivados, 90 en nombre del elemento, 80 encapsulación, 7-10, 12, 24, 249 enlace, 68 error, 148, 149 escalabilidad, 167 escenario, 105, 107 escritura estática, 45 espacio de nombres, 174 especialización, 27 especificación, 10 de componente, 233 de requisitos, 65 prueba, 247 verificación de, 243 estado, 17, 18, 147, 148 compuesto, 163 estereotipo, 83, 95 estilo de arquitectura, 229, 237 estimación, 257 evaluación fase del proceso en espiral, 53 evento, 148,151 cambio, 161 entrada, 152 interno, 163 llamada, 161 señal, 162 tiempo, 162 extend, 115 exterior, 38
F flecha de dependencia, 101 flujo de trabajo, 187 Fowler, Martín, 76, 107, 249
ÍNDICE ANALÍTICO
G garantía de calidad, 242, 255, 262 GCT, 264 generalización, 27, 41, 73 de actores, 121 de casos de uso, 121 implementación con herencia, 76 genericidad, 101 gestión, 6, 53, 55, 255 de configuración, 128, 223 de personal, 255 de proyectos, 6, 255 de riesgos, 53 en matriz, 259 Gestión de Calidad Total (GCT), 264 Graham, Ian, 92 Griss, Martín, 266 Grupo de Gestión de Objetos (OMG), 55 guarda, 164 en diagrama de estado, 154 en diagrama de interacción, 137 guerra de métodos, 55
H hardware, 4, 167, 169, 170 como actor, 103 herencia, 25, 26, 76 múltiple, 26 herramienta CASE, 35, 77, 123, 125, 128, 173 de diseño, 29 hilo, 43 hobbit, 107
I identidad, 18 idioms, 237 implementación de interfaz, 95 importación de un paquete, 176 incepción fase de Objectory, 50 include, 115 información oculta, 12 ingeniería fase del proceso en espiral, 53 inspección, 251 Fagan, 251
instancia, 21 instanciar, 21 interfaz, 8, 9, 83, 95, 96 de usuario, 24, 34 pública, 20 iteración, 36, 48, 53, 111, 139
J Jacobson, Ivar, 54, 103 Java, 22, 45, 101 juego de planificación, 110, 257
L lenguaje de acción, 153 de modelado, 50, 51 de patrones, 236 liderazgo, 261 ligadura dinámica, 28 límite del sistema, 108 línea de producto, 13 de vida, 129
M mantenimiento, 4 Máquinas de Estado de Protocolo, 151 marca de fin, 151, 158 de inicio, 148, 158 marco de trabajo, 190, 240 dirigido a la arquitectura, 194 Martin, Robert, 190 mejora de proceso, 262 Mellor, Steve, 67 mensaje, 17, 19 asíncrono, 143 encontrado, 136 guardado, 137 síncrono, 143 método, 21, 49 redefinición en subclase, 23, 74 metodología, 49 metodologías rápidas, 56 Meyer, Bertrand, 135 miembro datos, 18 modelo, 50, 176
275
276
ÍNDICE ANALÍTICO
de clases bueno, 64 de vista 4+1, 58 elementos, 50, 173 Modelo de Madurez de Capacidad, 263 modularidad, 7, 10, 24 módulo, 8 multiplicidad, 70 en composición, 84
N navegabilidad, 86 implementación, 211 negocio caso, 56 modelado, 93, 157 normas, 66 objeto, 234 proceso,5 Newkirk, James, 190 nodo, 169 notación conexión y bola, 97
O objeto, 17 activo, 142, 143 comportamiento de, 18 estado de, 18 identidad de, 18 persistente, 47 OODB, 47 operación, 21, 26, 71 especificación del comportamiento de, 38 especificar con diagramas de actividad, 141 orientación a objetos, 13
planificación, 33, 49, 256 de pruebas, 247, 251 en Objectory, 50 en proceso en espiral, 53 utilizando casos de uso, 109 plantilla, 100 polimorfismo, 28 política, 111 portable, 4 poscondiciones, 44 posibilidad de sustitución, 27 precondiciones, 44 prestación/rendimiento, 167, 171 privado, 176 problema de clase de base frágil, 76 procedural, 128 proceso de desarrollo, 49 en cascada, 52 en espiral, 53 unificado, 56 programación adaptativa, 132 extrema, 57 orientada a componentes, 12 propiedad, 99 protegido, 215 prototipado, 53 prueba, 242 de regresión, 246 de usabilidad, 246 público, 176 punto de extensión, 120 puntos importantes, 236
Q QMS, 263
P R paquete, 83, 173 patrón, 227, 235 de análisis, 237 de arquitectura, 237 de organización, 258 diseño, 237 fachada, 237 proceso, 237 patrones de arquitectura, 237 persistencia, 47 plan de calidad, 263, 265
RDBMS, 47 RDD, 65 realización, 97 refactorización, 112, 194 refactorizar, 80 requisitos análisis en proceso en espiral, 53 no funcional, 167 probable, 248 responsabilidad
ÍNDICE ANALÍTICO
de clase, 78 de gestión de proyectos, 256 restricción, 83, 90, 150 retorno en diagrama de secuencia, 142 reutilización, 9, 13, 100, 227 caja negra, 13 revisión, 251 código, 251, 252 diseño, 251, 252 técnica formal, 251 riesgo, 6, 55, 111, 256 rol, 33, 85 Rumbaugh, James, 54
S secuencia de acción, 163 seguimiento, 44 selector, 19 semántica, 243 señal, 162 servidor, 8 SGM, 263 Shlaer, Rally, 67 signatura, 72 Simula, 18 síncrono, 128 sistema, 57 de gestión de calidad, 52, 255, 263 operativo, 4, 171 sistemas heredados, 5 Smalltalk, 22 sobrecarga, 175 software de Rational, 55, 229 no eficiente, 232
subclase, 27 superclase, 27
T tarjetas CRC, 38, 118 tiempo en diagrama de estados, véase evento, tiempo en diagrama de secuencia, 138 tipo, 10, 45 TQM, 264 transición, 148 de actividad, 157 fase de Objectory, 51 Tres Amigos, 55, 177
U UML, xvii-xviii unión, 158 usabilidad, 244 usuario, 103
V validación, 111, 242 valor de retorno, 72 en diagrama de interacción, 132 valores etiquetados, 99 variable de instancia, 18 verificación, 243 visibilidad, 176 vista, 58, 176
277
Esquemas Clases: Capítulos 5 y 6 T
Copia
Lista
Clase simple
+ añadir(t:T,pos:entero)
Informe
Lista<Juego>
+ obtener(e:entero) : T
- estados: Colección <>〈Estudiante〉
+ informe() : void Clase con atributo y operación
1..* 1 Libro es copia de
Copia
Tablero Fila:{1,2,3} Columna:{1,2,3}
Asociación con multiplicidad y navegabilidad
Casos de uso - Capítulos 7 y 8
Cuadrado
Tablero
Agregación
Crear modelo
Composición
Estudiante
Estadística
Medio
Cuadrado
Asociación calificada
Módulo
CursoDeGrado
Clase parametrizada y sus usos
ListaEstudiante
Módulo
<>
observe behavior
Está cursando Nota:entero
Media
<>
Desarrollador
Realizador de pruebas
Ejecutar modelo
<<extend>>
Clase asociación Generalización
Registrador Estadística
Informe
Biblioteca
Clase subsistema
Fachada Interfaces
Fachada
Recolección de estadísticas
Libro
Diagramas de actividad Capítulo 11
<>
Grabador
Diagramas de secuencia Capítulos 9 y 10
[tomarPrestado]
Patrón - Capítulo 18
a:Almacen :Usuario petición() 1.1; nuevoArtículo(3)
[volver] Pegar
Localizar
:artículo consulta() i := consulta()
Marcar
Almacenar comprobar(i)
destruir()
280
UTILIZACIÓN DE UML EN INGENIERÍA DEL SOFTWARE CON OBJETOS Y COMPONENTES
Diagramas de colaboración - Capítulo 9 1.3: comprobar(i)
Tipos de mensajes utilizados en los diagramas de colaboración y secuencia
1.4: destruir() 1.2: i := consulta()
s:Store
1.1: nuevoArtículo(3)
síncrono
1: petición()
asíncrono
:artículo {transitorio}
retorno :Usuario
Diagramas de estado - Capítulos 11 y 12
Trabajando
[listo]hacerTrabajo(j:Trabajo)/p.tell(j)
entrada / i++ salida / i--
Esperando
finTrabajo()
Después(5 s) Manteniendo
Cuando(vacío) Enviando Paquetes - Capítulo 14 Confirmar()
Esperando confirmación Gráficos Diagrama de estado concurrente añadido
Gráficos Contenidos ocultos
Contenidos visibles
Diagramas de implementación - Capítulo 13
<> MotorJuego
InterfazJugador
Dependencia entre dos componentes
Shillay:Estación de trabajo
<> Craro:PC
Nodos físicos sin software
Craro:PC
<> m:MotorJuego Shillay:Estación de trabajo
p:InterfaceJugador
<>
Software desplegado en nodos