Ingeniería del Software 1 Curso 2010-2011 Diseño de responsabilidades con patrones (GRASP) ATENCIÓN ESTE DOCUMENTO ES UN MATERIAL DE APOYO PARA LAS CLASES DE TEORÍA, NO ESTÁ DISEÑADO COMO MATERIAL DE ESTUDIO. SI QUIERES USARLO PARA ESTUDIAR DEBES COMPLEMENTARLO CON LAS EXPLICACIONES DEL PROFESOR Y/O LA BIBLIOGRAFÍA RECOMENDADA
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Ingeniería del Software 1Curso 2010-2011
Diseño de responsabilidades
con patrones (GRASP)
ATENCIÓN
ESTE DOCUMENTO ES UN MATERIAL DE APOYO PARA LAS
CLASES DE TEORÍA, NO ESTÁ DISEÑADO COMO MATERIAL DE
ESTUDIO. SI QUIERES USARLO PARA ESTUDIAR DEBES
COMPLEMENTARLO CON LAS EXPLICACIONES DEL PROFESOR
Y/O LA BIBLIOGRAFÍA RECOMENDADA
Índice
Introducción
Patrones GRASP Básicos
Creador
Experto (en Información)
Bajo Acoplamiento
Controlador
Alta Cohesión
Patrones GRASP Avanzados
Polimorfismo
Fabricación Pura
Indirección
Protección de Variaciones
Objetivos del tema
Comprender el uso del diagrama de clases para reflejar
conceptos a distintos niveles de abstracción
Ser capaz de realizar un diseño software que sea
respetuoso con los principios de asignación de
responsabilidades de GRASP
Introducción
Perspectiva de análisis vs. perspectiva de diseño en
diagramas de clase UML
Introducción: Usos del diagrama de clases
UML incluye los diagramas de clases para ilustrar clases, interfaces
y sus asociaciones.
Éstos se utilizan para el modelado estático de objetos.
Los diagramas de clases se pueden usar tanto desde una
perspectiva conceptual (para el modelo del dominio) como desde una
perspectiva software (para el modelado de la CAPA DE DOMINIO).
Es común hablar de “Diagrama de clases de Análisis” para referirse al diagrama
de clases que representa el modelo de dominio, y a “Diagrama de Clases de
Diseño” para hablar del diagrama de clases que representa a las clases software,
es decir, a las clases que finalmente van a ser implementadas.
EN LA ASIGNATURA NOS VAMOS A CENTRAR EN EL MODO DE DEFINIR LOS
MODELOS DE DISEÑO Y, DENTRO DE ELLOS, EL DIAGRAMA DE CLASES DE
DISEÑO
Introducción: Diseño de objetos
El diseño de objetos se describe como:
Tras haber identificado tus requisitos, y haber creado un modelo de dominio,
añade operaciones a las clases y define las secuencias de mensajes entre
objetos para cubrir los requisitos
Estas recomendaciones no son especialmente útiles, ya que hay
principios fundamentales de diseño, nada triviales, que deben ser
tenidos en cuenta a la hora de:
Decidir qué operaciones hay que asignar a qué clases
Cómo los objetos deberían interactuar para dar respuesta a los casos de uso
De hecho, el artefacto más importante del flujo de trabajo de diseño
es el Modelo de Diseño, que incluye el diagrama de clases
software (no conceptuales) y diagramas de interacción.
Introducción: Diseño de objetos
Register
...
endSale()
enterItem(...)
makePayment(...)
Sale
time
isComplete : Boolean
/total
makeLineItem(...)
Register
...
Sale
time
isComplete : Boolean
/total
Captures
1
11
Domain Model
conceptual
perspective
Design Model
DCD; software
perspectivecurrentSale
Meta: Low Representational Gap
Introducción: Diseño de objetos
the association name, common when drawing a
domain model, is often excluded (though still legal)
when using class diagrams for a software
perspective in a DCD
Register
id: Int
...
Sale
time: DateTime
...
1
currentSale
Register
id : Int
Sale
time : DateTime
Captures-current-sale1 1UP Domain Model
conceptual perspective
UP Design Model
DCD
software perspective
Introducción: Del DC de Análisis al DC de Diseño
GENERALIZACIÓN VS HERENCIA
En el modelo de dominio implica que la superclase es un
superconjunto y la subclase un subconjunto.
En un DCD implica la relación de herencia de los lenguajes de
programación OO de una superclase a una subclase.
¡ATENCIÓN! Las relaciones de generalización no tienen
por qué traducirse en relaciones de herencia en el DCD.
P.ej. en C++ el uso de templates puede a veces reducir el número
de clases (alternativa a la herencia).
Introducción: Del DC de Análisis al DC de Diseño
DCD: APARICIÓN DE NUEVAS CLASES (1/2)
Clases de Utilidad: clases que encapsulan algoritmos genéricos
que pueden ser accedidos por más de una clase
Como veremos más adelante, añadir esos algoritmos a clases
existentes disminuiría la cohesión de esas clases
Las clases de utilidad también se pueden utilizar para encapsular
clases de librería o aplicaciones/funciones que no son OO
(proporcionan una interfaz OO a dichos módulos)
Librerías: referencias a librerías proporcionadas por el entorno
(p.ej. STL)
Introducción: Del DC de Análisis al DC de Diseño
DCD: APARICIÓN DE NUEVAS CLASES (2/2)
Interfaces: abstracción de comportamiento mediante interfaces
que realizan las clases
Clases de ayuda: clases que asisten a una clase existente para la
realización de una tarea, al mismo tiempo que permiten aumentar
la cohesión de la clase origen.
Ejemplo: ManageEmployeeForm: clase que gestiona cosas como
formateo de números de identidad. ¿Pero quién gestiona el control de
que el número de la Seguridad Social es un número válido de la
empresa?
Introducción: Responsabilidades
UML define una responsabilidad como “un contrato u
obligación de un clasificador”.
Obligaciones de un objeto en términos de su comportamiento.
Las responsabilidades se asignan a clases software durante el
diseño de objetos
Introducción: Tipos de responsabilidades
Hacer:
Hacer algo él mismo (e.g. crear un objeto, realizar un cálculo)
Iniciar la acción en otros objetos.
Controlar y coordinar actividades en otros objetos.
Ejemplo: Un tablero es responsable de crear sus casillas”
Saber o Conocer:
Conocer sus datos privados (encapsulados).
Conocer los objetos con los que se relaciona.
Conocer las cosas que puede derivar o calcular.
Ejemplo: “Un barco es responsable de conocer cuándo está hundido”
Las responsabilidades relacionadas con „conocer‟ son normalmente
inferibles del Modelo de Dominio (debido a los atributos y
asociaciones que éste ilustra)
Introducción: Responsabilidades vs. métodos
La complejidad del proceso de traducción de
responsabilidades a clases y operaciones (implementadas
mediante métodos) está influenciada por la granularidad de la
responsabilidad.
Ejemplo: la responsabilidad “proporciona acceso a BD relacional”
puede involucrar docenas de clases y cientos de métodos, mientras
que “crear Venta” podría involucrar sólo uno o un pequeño número de
métodos.
Una responsabilidad no es un método, pero los métodos se
implementan para cubrir responsabilidades.
Los métodos pueden colaborar con otros métodos y objetos
para cubrir una determinada responsabilidad.
Introducción: Responsabilidades y D. Interacción
Podemos pensar sobre cómo
asignar responsabilidades
cuando modelamos o cuando
codificamos
Dentro de los artefactos UML,
un contexto común donde estas
responsabilidades
(implementadas como
métodos) se consideran es
durante la creación de
diagramas de interacción.
:Pieza
:Casilla
getCasilla(…)
get(…)
Las piezas tienen la
responsabilidad de saber en
qué casilla están colocadas, y
la manejan con el método
getCasilla().
El cumplimiento de esta
responsabilidad requiere llamar
al método get del atributo
Casilla
Patrones GRASP
BÁSICOS AVANZADOS
Creador
Experto (en Información)
Bajo Acoplamiento
Controlador
Alta Cohesión
Polimorfismo
Fabricación Pura
Indirección
Protección de Variaciones
GRASP: Introducción
GRASP es un acrónimo para General Responsibility Assignment
Software Patterns.
Describen 9 principios fundamentales del diseño de objetos y de
la asignación de responsabilidades, expresado en términos de
patrones.
Se dividen en dos grupos:
GRASP: Introducción
Para ilustrar los patrones vamos a suponer que queremos modelar un
monopoly
Dibujad su modelo de dominio (perspectiva conceptual)
Nota: asume dos dados
Nota: comienza modelando el tablero, las casillas, y el acto de hacer una
tirada y mover las piezas por parte de un jugador
Patrones GRASP básicos
Creador
Experto (en información)
Bajo acoplamiento
Controlador
Alta cohesión
GRASP: Creador
En el Monopoly, ¿quién debería ser el responsable de
crear Casillas?
GRASP: Creador
Problema: ¿Quién debería ser el responsable de crear
una nueva instancia de alguna clase?
Solución: Asigna a la clase B la responsabilidad de crear
una instancia de la clase A si una o más de las siguientes
afirmaciones es cierta:
1) B agrega objetos de tipo A.
2) B contiene objetos de tipo A.
3) B graba objetos de tipo A.
4) B tiene datos inicializadores que serán pasados a A cuando sea
necesario crear un objeto de tipo A (por tanto B es un Experto
con respecto a la creación de A).
GRASP: Creador
Discusión
“Creator” busca el objeto creador que requiere estar conectado
al objeto creado en cualquier evento.
Esto soporta “bajo acoplamiento”.
Es común que el “Creator” se encuentre buscando la clase que
tiene los datos de inicialización que serán pasados como
parámetro al objeto creado. Tablero tiene los datos de posición
para el objeto Casilla.
Contraindicaciones
La creación podría ser compleja (por ejemplo “creación
condicional”, “reciclamiento de instancias”, o que la creación de
una instancia sea a partir de una familia de clases similares
basadas en una propiedad externa). En este caso use el patrón
“Factory” (GoF).
Beneficios
Favorece el “bajo acoplamiento”, implicando con ello un nivel
bajo de dependencias de mantenimiento y alta oportunidad de
reuso.
GRASP: Experto (en información)
En el Monopoly, imaginad que necesitamos ser capaces de
referenciar una casilla particular, dado su nombre (la calle que
representa). ¿A quién le asignamos la responsabilidad?
GRASP: Experto (en información)
Problema: ¿cuál es el principio general de asignación de
responsabilidades a objetos?
Solución: Asigna cada responsabilidad al experto de
información, i.e. la clase que tiene (la mayor parte de) la
información necesaria para cubrir la responsabilidad.
GRASP: Experto (en información)
¡ATENCIÓN!
A veces hay que aplicar el
Information Expert en cascada.
Ejemplo: Casa de apuestas
Suponed que queremos asignar
la responsabilidad de calcular la
ganancia/pérdida neta de un
usuario en el siguiente diagrama.
Dibujad el diagrama de
secuencia e indicad los cambios
en el diagrama de clases.
GRASP: Experto (en información)
Discusión
Los objetos hacen tareas relacionadas con la información que
poseen.
Principio de “animación”. (caricatura animada).
Tal como en el mundo real: “damos responsabilidades a quien
tiene la información”.
Contraindicaciones
Problemas de cohesion y acoplamiento: ¿Quién debería salvar
en la db las apuestas? No debería ser la clase Apuesta
(incohesivo).
Beneficios
Mantiene encapsulamiento de información (bajo acoplamiento)
Es necesario guardar instancias del Monopoly en una base de datos
relacional. ¿Quién debería tener esa responsabilidad?
Por Expert la clase Monopoly debería tener esta responsabilidad, sin
embargo:
La tarea requiere un número importante de operaciones de base de datos, ninguna
relacionada con el concepto de Monopoly, por lo que Monopoly resultaría
incohesiva.
Monopoly quedaría acoplado con la interface de la base de datos (ej.- JDBC en
Java, ODBC en Microsoft) por lo que el acoplamiento aumenta. Además el
acoplamiento sería a una tecnología específica de base de datos, no a otro objeto
del dominio (que sería menos grave).
Guardar objetos en una base de datos relacional es una tarea muy general para la
cual se requiere que múltiples clases le den soporte. Colocar éstas en Monopoly
sugiere pobre reuso o gran cantidad de duplicación en otras clases que hacen lo
mismo.
GRASP: Fabricación pura
Solución: Crear una clase (PersistentStorage) que sea
responsable de guardar objetos en algún tipo de
almacenamiento persistente (tal como una base de datos
relacional).
PersistentStorage
insert( Object )
update( Object )
...
By Pure Fabrication
GRASP: Fabricación pura
Problemas resueltos:
La clase Monopoly continua bien definida, con alta cohesión y bajo
acoplamiento.
La clase PersistentStorage es, en sí misma, relativamente
cohesiva, tiene un único propósito de almacenar o insertar objetos
en un medio de almacenamiento persistente.
La clase PersistentStorage es un objeto genérico y reusable.
GRASP: Fabricación pura
Problema: ¿Qué objeto debería tener la responsabilidad, cuando no
se desean violar los principios de “Alta Cohesión” y “Bajo
Acoplamiento” o algún otro objetivo, pero las soluciones que sugiere
Expert (por ejemplo) no son apropiadas o cuando no es apropiado
asignarlo a una clase software inspirada a partir de una clase
conceptual?
Solución: Asigne un conjunto “altamente cohesivo” de
responsabilidades a una clase artificial conveniente que no
represente un concepto del dominio del problema, algo producto de
la “imaginación” para soportar “high cohesion”, “low coupling” y reuso.
Éste es precisamente el ppio que se aplica cuando se introducen clases
„Helper‟ y clases „Utility‟
GRASP: Fabricación pura
En sentido amplio, los objetos pueden dividirse en dos grupos:
Aquellos diseñados por/mediante descomposición
representacional. (Ej.- Monopoly representa el concepto
“partida”)
Aquellos diseñados por/mediante descomposición
conductual. (Ej.- Para agrupar comportamientos o
algoritmos; clases sin nombre ni propósito relacionado con el
mundo real, e.g. TableOfContentsGenerator). Este es el caso
más común para objetos Fabricación pura.
GRASP: Fabricación pura
El principio de descomposición conductual para objetos
Fabricación Pura en ocasiones es sobreutilizado por
novatos en diseño y tienden a dividir el software en
términos de funciones.
Exagerando: las funciones se convierten en objetos.
(Clases “functoides”).
¡Tened cuidado con esto si continuamente estáis pasando
objetos como parámetros para que sean procesados por
métodos!
GRASP: Indirección
Ejemplo: Cubilete
El objeto Cubilete que hemos comentado cuando hablábamos de
Bajo Acoplamiento es un ejemplo de indirección: un elemento que
no existía en el juego real pero que introducimos para aislarnos del
número de dados que usa el juego.
GRASP: Indirección
Problema:
¿Dónde asignar una responsabilidad para evitar acoplamiento
directo entre dos o más cosas? ¿Cómo desacoplar objetos de tal
manera que el bajo acoplamiento se soporte y el reuso potencial
se mantenga alto?
Solución:
Asignad la responsabilidad a un objeto intermedio que medie
entre otros componentes o servicios, de tal manera que los objetos
no estén directamente acoplados. El objeto intermedio crea una
indirección entre los componentes.
GRASP: Indirección
“Muchos problemas en ciencias de la computación
pueden resolverse mediante otro nivel de indirección”
es un viejo adagio con relevancia particular en diseño
orientado a objetos (David Wheeler).
Así como muchos patrones de diseño son especializaciones de
Fabricación Pura, muchos otros también lo son de
Indirección (Adapter, Facade, Observer, entre otros).
Además muchas Fabricaciones Puras son generadas por
causa de Indirección.
La motivación principal es el bajo acoplamiento; por lo que
un intermediario se agrega para desacoplar otros componentes
o servicios.
GRASP: Protección de variaciones
Imaginad que sois una empresa de creación de juegos de
mesa por internet. Os han pedido que implementéis el
Monopoly, pero sabéis que en breve os van a pedir
también „La isla del tesoro‟, y posiblemente más adelante
otros, como „En busca del imperio cobra‟.
Cada juego utiliza distintos tipos de casilla (variación
entre juegos).
¿Cómo protegeríais el diseño de esa variación?
GRASP: Protección de variaciones
Solución: Usar polimorfismo para abstraer los tipos de casilla
ilustraría el concepto de Protección de Variaciones.
El punto de inestabilidad por variaciones son las diferentes interfaces
o APIs de los distintos tipos de casillas de los distintos juegos.
Mediante un nuevo nivel de Indirección, una interfaz y usando
polimorfismo con varias implementaciones Casilla, se logra la
protección de variaciones dentro del sistema a partir de las
variaciones en las APIs externas.
Las partes del sistema comunes colaboran con una interfaz estable;
las implementaciones de las casillas encapsulan las variaciones en
función del juego.
GRASP: Protección de variaciones
Problema:
¿Cómo diseñar objetos, subsistemas y sistemas de tal
manera que las variaciones o inestabilidad en estos
elementos no tenga un impacto indeseable sobre otros
elementos?
Solución:
Identifique los puntos de variación o inestabilidad;
asigne responsabilidades para crear una interfaz
estable a su alrededor.
Principios de diseño motivados
por la protección de variaciones
Robert C. Martin. Agile Software Development,
Principles, Patterns, and Practices. 2002.
Principios de diseño motivados por PV
Encapsulación
Diseñar operaciones de manera que consultan o modifican, pero no hacen ambas cosas a la vez
Separación Modelo-Vista: objetos del modelo no deberían conocer objetos de presentación, para promocionar bajo acoplamiento de otras capas hacia la capa de interfaz (que es la que más cambia)
Principio de Sustitución de Liskov: Una instancia de unaclase derivada debe ser capaz de tomar el lugar de unainstancia de una clase base. Por ejemplo, si un métodotiene un objeto de una clase como argumento, el mismométodo debe ser capaz de trabajar con una instancia de una clase derivada.
Principios de diseño motivados por PV
Principio OPEN-CLOSE (Bertrand Meyer)
Los módulos software (paquetes, métodos, clases, etc.) deberían
estar ABIERTOS a la extensión y CERRADOS a la modificación
Diseña el software de manera que puedas extender su capacidad
(añadir nuevas funcionalidades) tocando lo menos posible el código que
ya existe. La mayoría de los cambios se materializan en la adición de
métodos o clases al sistema
No siempre es posible seguir este principio de manera completa,
pero mientras más lo sigas más fácil será después acomodar
nuevos (y quizás inesperados) requisitos.
Principios de diseño motivados por PV
PRINCIPIO DE “INVERSIÓN” DE DEPENDENCIA
Los módulos de alto nivel no deberían depender de módulos de bajo nivel.
Tanto unos como otros deberían depender de abstracciones
Las abstracciones no deberían depender de detalles, sino al contrario: los
detalles deberían depender de las abstracciones.
Esto implica que el acoplamiento entre los objetos que usan y los objetos que
son usados se debería hacer siempre a nivel conceptual (en términos de
„servicios‟ que unos requieren de los otros), sin tener en cuenta los detalles
concretos de las implementaciones.
Ojo! Este principio “invierte” la idea convencional de que módulos de alto nivel
deberían depender de módulos de bajo nivel.
Llevar este principio al extremo supondría asumir que…
No deberían existir variables que referenciasen clases concretas
Ninguna clase debería derivar de una clase concreta
Ningún método debería sobreescribir un método implementado de una de
sus clases base
En la práctica no es útil llegar a este extremo, pero sí ser consciente de que
cuando se violan estos principios estamos añadiendo acoplamiento
Principios de diseño motivados por PV
Interface Segregation Principle: Clients should not be forced to
depend upon interfaces that they do not use. Many client-specific
interfaces are better than a general-purpose one.
Reuse/Release Equivalency Principle: The granule of reuse is the
same as the granule of release. Only components that are released
through a tracking system can be effectively reused.
Common Reuse Principle: Classes that are not reused together
should not be grouped together.
Common Closure Principle: Classes that change together, belong
together.
Stable Abstractions Principle: The more stable a category of classes
is, the more it should consist of abstract classes. A completely stable
category should consist of only abstract classes.
Principios de diseño motivados por PV
Least Astonishment Principle: When two elements of an interface
conflict or are ambiguous, the behavior should be that which least
surprises the software engineer at the time of the conflict, because
the least surprising behavior must be usually the correct one.
Deep Abstract Hierarchies Principle: Class hierarchies should be
deep and abstract.
The Acyclic Dependencies Principle: There should be no cycles in the
dependency graph.
The Stable Dependencies Principle: Depend in the direction of
stability. The dependencies between components in a design should
be in the direction of stability. A component should only depend upon
components that are more stable than it is.
Principios de diseño motivados por PV
Don’t Talk to Strangers (Ley de Demeter): Cada unidad(clase, método) sólo debería utilizar un conjunto limitado de otras unidades, y sólo entre aquéllas que están fuertementerelacionadas con la unidad actual
Este principio restringe a qué objetos debería enviárseles mensajes dentro de un método:
Al objeto this (o self).
A un parámetro del método.
A un atributo de this.
A un elemento de una colección que es un atributo de this.
A un objeto creado dentro del método. (local al método).
La intención es evitar acoplamiento con objetos indirectos. Los objetos directos son los “familiares del cliente” los indirectos son los “extraños”.
El cliente debe hablar con los “familiares” no con los “extraños”.
Protege contra cambios estructurales
Principios de diseño motivados por PV
ID to Objects: convertir claves e ID‟s de objetos en objetos verdaderos lo más pronto posible (normalmente en cuanto el ID entra en la capa de dominio del modelo de diseño), para a partir de ahí trabajar con el objeto.
¿Por qué?: Tener un objeto real, con información y responsabilidades (y no simplemente un ID) flexibiliza la aplicación según el diseño crece, ya que es probable que necesidades no percibidas originalmente surjan durante la evolución del sistema.
Pasar objeto agregado como parámetro: cuando una operación requiere como parámetros objetos que están agregados dentro de otros, pasar el objeto agregado, y no los objetos „hijos‟.
¿Por qué? Pasar el objeto agregado aumenta flexibilidad del sistema, al permitir que la operación colabore con el objeto agregado en modos que en un principio no habíamos previsto.
EJERCICIO
Volved al ejercicio que realizasteis en el tema anterior, en
el que identificasteis, sobre un diagrama de clases de
vuestra elección, el conjunto de elementos que podrían
ser patrones (una buena práctica que deberíais repetir si
os encontráseis ante un problema similar)
Revisar vuestra conclusión inicial a la luz de los patrones
Larman
EJERCICIO
EJERCICIO PARA ENTREGAR (OBLIGATORIO)
Bajáos el documento con las reglas completas del Monopoly
Realizad un Modelo de Dominio y un Modelo de Diseño (al menos
el diagrama de clases de diseño) del juego que permita implementar
dicho juego en modo simulación.
Realizad un comentario de vuestro modelo indicando qué principios
GRASP habéis aplicado en las distintas decisiones de asignación de
responsabilidades
A partir de ahora, tened esta versión del juego siempre a mano,
para poder irla flexibilizando según vayamos aprendiendo nuevos