sábado, 22 de septiembre de 2007

Sigamos solucionando nuestro diseño con los patrones

Siguiendo con nuestra charla de diseño, determinemos la granularidad de los objetos intervinientes .
Los objetos varían enormemente en tamaño y número, y representan cualquier cosa desde hard a sistemas completos, por lo que como decidir que debería ser un objeto?
Veamos algunos patrones:
  • Fachada (GoF) describe como representar subsistemas ó procesos completos como objetos.
  • FlyWeight(GoF) describe como permitir un gran número de granularidad muy fina.
  • Abstract Factory (GoF) produce objetos cuya única resp. es crear otros objetos.
  • Visitor y Command (GoF) generan objetos cuya resp. es implementar una petición en otros objs. ó grupo de objs.

Especificar las interfaces de los objs.

Los patrones nos ayudan a definir interfaces identificando sus elementos clave y los tipos de datos que se envían a la interfaz. También especifican relaciones entre interfaces, ya que: requieren que algunas clases tengan interfaces parecidas ó les imponen restricciones; por ej:

Decorador y Proxy (GoF) requieren que las interfaces de sus objs. sean idénticos a los objs. deocrado y representado, respectivamente.

Especificar las implementaciones

Herencia de clases vs. herencia de interfaces, muchos de los patrones de diseño dependen de esta distinción. Por ej:

  • Los objs. de una cadena en Chain of Responsability (GoF) deben tener un tipo en común, pero normalmente no comparten la misma implementación.
  • En Composite (GoF), el componente define una interfaz común mientras que el compuesto suele definir una implementación en común.
  • Command, Observer, State y Strategy (GoF) suelen implementarse con clases abstractas que son interfaces puras.

En este tema, debemos tener en mente uno de los principios fundamentales del diseño OO: programar para interfaces, no para una implementación. Por que?

  • Ventajas de manipular objs. solamente en términos de la interfaz definida por las clases abstractas. Los clientes no tienen que conocer los tipos específicos de los objs. que usan, basta con que estos adhieran a la interfaz que esperan que esperan los clientes.
  • Reducción del acoplamiento entre subsistemas.
  • No se deben declarar variables como instancias de clases concretas, se ajustan simplemente a la interfaz definida por una clase concreta.
  • Para crear instancias de clases concretas usamos los patrones de creación; estos ofrecen distintos modos de asociar una interfaz con su implementación de manera transparente, y aseguran que el sistema se codifique en términos de interfaces, no de implementación.

Por último, pongamos a funcionar los mecanismos de reutilización:

Herencia vs. Composición

Son las 2 técnicas más comunes para reutilizar funcionalidad en sistemas OO.

La composición requiere que los objs. tengan en cta. las interfaces de otros, lo cual requiere interfaces cuidadosamente diseñadas, que no impidan que un obj. sea utilizado por otros. Optar por la composición ayuda a mantener cada clase encapsulada y centrada en una sola tarea. En los patrones de diseño veremos esta técnica aplicada una y otra vez...otro principio fundamental: favorecer la composición de objetos frente a la herencia de clases.

Pero qué es la composición?: método por el cual una nueva funcionalidad se obtiene mediante la creación de un objeto compuesto por otros objetos, ésta se logra delegando funcionalidad a uno de los objs. de la composición.

Mientras que la Herencia es un método por el cual la nueva funcionalidad se obtiene mediante la extensión de la implementación de un objeto existente. La superclase (generalización) captura los métodos y atributos comunes, y las subclases (especializadas) extienden la implementación con métodos y atrib. adicionales. Es más sencilla de llevar a cabo que la composición, pero...

Desventajas de la herencia:

  • Rompe la encapsulación, ya que expone la subclase a los detalles de implementación de su superclase
  • Reutilización de caja blanca, porque los detalles internos de la superclase son visibles a las subclases
  • Las subclases han de cambiar si la implementación de la superclase cambia

Entonces que usamos?, según Coad, solo utilizar herencia cuando:

  • una subclase expresa "es un tipo de" y no "es un rol desempeñado por".
  • una instancia de una subclase nunca necesita convertirse en un obj. de otra clase
  • una subclase extiende las resp. de su superclase, no las redefine ni las anula.
  • para una clase del problema del dominio actual, la subclase especializa un rol ó una transacción.

Por último utilicemos la Delegación cuando no existan relaciones estáticas entre clases, cuando la herencia no tenga sentido, por ej. para gestionar Roles.

Bien, como vemos obtener un buen diseño es difícil; lo primero que deberíamos saber es : que es un buen diseño?, los conceptos que detallamos antes nos ayudan a dilucidar esta pregunta. Lo segundo será aplicar los principios de diseño de forma sistemática, una buena solución es copiar a los que saben: aplicar los patrones, donde puedan aplicarse.

sábado, 8 de septiembre de 2007

Siguiendo con nuestra charla sobre como los patrones pueden socorrernos en nuestros difíciles momentos de diseño, veamos como "encontrar los objs. apropiados para asignarles responsabilidades".
Coincidamos en que lo más complicado del DOO es descomponer un sistema en objetos.
Para lograr este objetivo, resumamos las tareas del análisis:
  • Partimos de encontrar los conceptos de nuestro dominio de problema y así efectuar un modelo conceptual donde identificamos las clases que surgen del negocio (nuestros objs. de negocio) y sus asociaciones.
  • Luego deberíamos obtener diag. de secuencia del sistema (basados en nuestros CU) donde determinemos los eventos y operaciones del sistema
  • En este punto sería importante obtener los "contratos" de los CU, donde se responderá: que hacen las operaciones del sistema?. Mediante la descripción de las postcondiciones de los contratos podemos describir cómo cambia el sistema tras cada operación; esta descripción se realiza en términos de : creación y destrucc. de objetos, asociac. formadas y canceladas y modificac. de atributos.
Con toda esta documentación podremos comenzar nuestra tarea creativa más importante en el desarrollo de un sistema: el DISEÑO.
Ahora asignemos responsabilidad a nuestros objetos, aunque aclaremos que existen 2 categorías de responsabilidades: de Conocer y de Hacer.

Conocer

  • los datos privados encapsulados
  • los objetos relacionados
  • las cosas que puedan derivarse ó calcular

Hacer

  • algo el mismo: crear un obj. ó hacer un cálculo
  • iniciar una acción en otros objs.
  • controlar y coordinar actividades en otros objs.

Para asignar responsabilidad a nuestros objs. , hablemos de 3 patrones Grasp: Experto en información, Creador y Controlador. (es.wikipedia.org/wiki/Grasp)

experto en informac.: asignar la resp. a la clase que tiene la informac. necesaria para realizarla. Esto debe hacerse sin generar nuevos acoplamientos, manteniendo la cohesión alta.

creador: asignar a la clase B la resp. de crear un obj. de la clase A si se cumple que:

  • B agrega objs. de A
  • B contiene objs. de A
  • B registra objs. de A
  • B usa objs. de A cercanamente (directa)
  • B tiene los datos de inicialización que se pasan a un obj. A cuando es creado

Debemos agregar que muy a menudo la creación de un obj. es de alta complejidad, por lo que sería aconsejable delegar la creación a clases auxiliares llamadas "Fábricas" (factorys)

controlador: asignar la resp. de recibir ó gestionar un mensaje de evento del sistema a una clase que:

  • represente el sistema global ó subsistema (Fachada)
  • represente un escenario de CU en el que tiene lugar el evento (controladora de CU ó sesión)

Tener en cuenta que un controlador debería delegar en otros objs. el trabajo que se necesita hacer, sólo coordina y controla la actividad (compuesta de varias tareas).

Seguimos en el próximo....



viernes, 7 de septiembre de 2007

Principios de diseño - Como llegamos a los Design Patterns??



En una charla con mis alumnos de último año, intenté describir el proceso de pensamiento que nos lleva a la aplicación de los patrones de diseño.


Para empezar debemos recordar los conceptos básicos del diseño (no exclusivos del DOO), nuestro ying y yang : Acoplamiento y Cohesión, el 1ro. que determina el nivel de dependencia y el 2do. el nivel de unión interno (realizar una única función)

OK, teniendo en mente aquello, intentemos comprender los términos: Responsabilidad e independencia, con un diagrama UML de paquetes.

Por ej.:
Un paquete X del cual dependen muchos paquetes debe ser muy estable. No debe cambiar porque es Responsable de los 3 paquetes que dependen de él.
  • X es Independiente, no depende de nadie.
  • Su Estabilidad = Responsabilidad + Independencia


Existe alguna medida sencilla para saber que tan estable es un paquete?

Factor de Inestabilidad (I) => I=(Ce / (Ca + Ce)). Si I[0,1]=0 será totalmernte estable.

Donde : Ca (cuan responsable soy?) son clases fuera del paquete que dependen del él. Ce (cuan dependiente soy) son clases del paquete que dependen de clases externas a él.

"La estabilidad de un paquete debe ser proporcional a su nivel de Abstracción" (R.Martin-1996); esto implica que los paquetes con máxima estabilidad deben ser abstractos; y los inestables concretos.

Esto nos lleva a entender que aquellas decisiones de diseño y arquitectura de alto nivel de abstracción no suelen cambiar, así que los situaremos en paquetes estables. Aquí surge una pregunta interesante: como puede un paquete totalmente estable (I=0) ser lo suficientemente flexible como para soportar los cambios?. O sea, mejorarlo sin cambiarlo.
Respuesta, otro principio : Abierto-Cerrado. Clases que puedan ser extendidas sin modificar lo ya existente: clases abstractas.

Ahora bien, cuando intentamos seguir estos principios básicos del diseño, descubrimos que las estructuras que desarrollamos para cumplirlos, se repiten una y otra vez; esto es lo que conocemos como "Patrón de diseño". Estos deben ser soluciones probadas a los problemas de diseño más comunes, resolviéndonos los sig. puntos:

  1. Encontrar los obj. apropiados para asignarles responsabilidad.
  2. Determinar la granularidad (su nivel de abstracción) de los objetos.
  3. Especificar sus interfaces.
  4. Especificar las implementaciones que deben tener los objs. involucrados.
  5. Poner a funcionar los mecanismos de reutilización

Ahora debemos diferenciar 2 viejas categorías de patrones de diseño: GRASP y GoF

Los GRASP no implementan las soluciones, más bien nos llevan a pensar en el diseño, a nivel de principios generales (más principios de DOO...); mientras que los GoF nos dan una solución implementable con su propio diagrama de clases que nos muestra la forma en que debemos usarlos.

En próximas notas iremos viendo los patrones relacionados con cada uno de los 5 puntos que nos resuelven los patrones (patterns).