Clase del 25/09/2007 (Diseño Avanzado con Objetos)

De Cuba-Wiki
(difs.) ← Revisión anterior | Revisión actual (difs.) | Revisión siguiente → (difs.)

Polimophic Hierarchy - Subimplementor methods are the key

La utilización consistente de métodos polimórficos lleva a las clases polimórficas y finalmente a la jerarquía polimórfica. Reuso de descripciones de métodos: Cuando se está redefiniendo un método definido en su jerarquía de clases, sus descripción debería ser “See superimplementor”, ya que el comportamiento de dicho método será igual al de su superclase, aunque su implementación sea distinta. Además debería ser definido en el mismo protocolo que en su jerarquía. De esta manera cuando se escriben todas las implementaciones de dicho mensaje, habrá solo una descripción, y la misma se encontrará en el método de la superclase que define la jerarquía. El mismo principio se aplica al habito, en Smalltalk, de que un método simple llame a otro método, que usualmente tendrá el mismo nombre, pero con parámetros extras. En dichos casos no es necesario describir el propósito de todos estos métodos.

Anatomía de la descripción de métodos:

La descripción de un método debería dividirse en el propósito del método y los detalles de su implementación. El propósito explica lo que el método hace. Los detalles de la implementación son opcionales y se utilizan para describir ciertas decisiones de implementación. El propósito del método es reusable para todas las implementaciones del mensaje en la jerarquía, mientras que los detalles de implementación no lo son, ya que si dos implementaciones del mismo método tuvieran los mismos detalles de implementación, estarían duplicando códigos.

Reuso de la descripción para el polimorfismo:

No se puede pensar en una clase sin entender a su superclase. La superclase debe hacer casi todo, la subclase debe conocer cómo hacerlo. Cada método que subimplementa la subclase debe hacer lo mismo que hace el método en la superclase. No debe hacerlo de la misma forma, pero debe producir el mismo resultado. Es decir debe tener el mismo propósito. Cuando todas las implementaciones en la jerarquía tienen el mismo propósito, ellos son polimórficos, y tenemos una jerarquía polimórfica. Para que dos métodos sean polimórficos, no solo deben tener el mismo nombre sino también deben comportarse de la misma manera, es decir que deben producir los mismos efectos, tener los mismos tipos de parámetros y el mismo tipo de retorno. Dos clase polimórficas entienden los mismos mensajes, y sus implementaciones son polimórficas. Dos clases no siempre comparten la misma interfaz completa, pero pueden compartir la interfaz central (the core interface) y seguir siendo polimórficas. La interfaz central (core interface) es la interfaz que comparten algunas clases, por la cual pueden ser intercambiables. Mientras se usen las colaboraciones de dicha interfaz, se puede usar cualquier instancia que posea dicha interfaz. Cuando las jerarquías son polimórficas, son más flexible, extensibles, reusables y fáciles para entender.

Template Class Pattern:

La clase abstracta que define una jerarquía polimórfica, es lo que se llama “Template Class”. El patrón Template Class es utilizador para crear jerarquías polimórficas. La clase Template define la interfaz para una clase (nuevo tipo) y deja los detalles de implementación para sus subclases.

Patterns Generate Arquitectures:

Los patrones de diseños fueron propuestos como una manera de comunicar decisiones de diseño. Una arquitectura es la manera en la cual las partes trabajan en conjunto para formar el todo. Un framework es un diseño reusable para un sistema o parte de un sistema expresado en un conjunto de clases abstractas y la manera en que colaboran las instancias de dichas clases. Es una solución de diseño/arquitectura para un problema específico. Los frameworks son una manera particular de representar arquitecturas. Ambas ideas están referidas al reuso de diseños. Otra de las ideas referidas al reuso de diseños, son los patrones. Los patrones proveen un lenguaje común para los diseñadores que facilita hablar sobre diseños y documentarlos. Otra de las ventajas que poseen es que los diseñadores menos expertos pueden usarlos y proveer una base lógica de sus diseños. Algunos patrones pueden ser genéricos y otros pueden ser específicos para un dominio de aplicación.

Cada patrón tiene el siguiente formato:

  • Precondición: Los patrones que deben ser satisfechos antes que dicho patrón sea válido.
  • Problema: Un resumen del problema al cual está dirigido el patrón. El problema debe ser usado por el lector, para ver si el patrón es aplicable o no.
  • Restrincciones (constraints): Las restricciones describen el conflicto de cualquier solución al problema.
  • Solución: Un resumen de la solución al problema. La solución generalmente está acompañada por diagramas.

Describir una arquitectura con patrones es como el proceso de división de células y especialización que conducen al crecimiento del organismo biológico. El diseño comienza como una nube confusa que representa el sistema a realizar. Cuando los patrones se comienzan a aplicar a dicha nube, se comienza a focalizar las distintas partes del sistema. Cuando no hay más patrones a ser aplicados, el diseño está terminado. El paper da como ejemplo de derivación de arquitecturas a través de patrones, la arquitectura de HotDraw, un framework para estructurar editores gráficos. Para dicha arquitectura, utiliza los siguientes patrones:

  • Model – View – Controler: para mostrar y manipular los dibujos en la pantalla.
  • Composite: para representar el concepto de figuras.
  • Objects of State: para representar que herramienta es seleccionada en la paleta (ya que dicho comportamiento depende del input).
  • Editor: para representar que diferentes tipos de dibujos necesitarán diferentes conjuntos de herramientas.
  • Observer: para actualizar la imagen en la pantalla cuando las figuras cambiaron su apariencia.
  • Collect Damage: para representar que cuando algunas partes del dibujo cambian simultáneamente, queremos que se actualicen como una unidad.
  • Update as User Speed: para asegurarnos que se muestren los cambios colectados con el patrón Collect Damage (región damaged).
  • Wrapper: para manejar los “handles” de la figuras.

Conclusiones:

La derivación de una arquitectura a partir de patrones es como la presentación de un teorema matemático. Comenzando con un problema a resolver, se van usando pasos conocidos para ir refinando la solución. E resultado es que no solo se entiende el sistema final sino también se entienden las razones que conducen a él. Por todo esto en sencillo para los programadores usar y extender sistema documentados con la derivación a través de patrones.

Introducción Design Patterns: Elements of Reusable Object-Oriented Software

Una cosa que saben los diseñadores experimentados es no resolver cada problema desde el principio. En lugar de eso, reusan soluciones que les funcionaron en el pasado. Cuando encuentran una buena solución, la usan una y otra vez. Esta experiencia es parte de lo que los hace expertos. Consecuentemente, encontraremos patrones recurrentes de clases y objetos comunicándose en muchos sistemas orientados a objetos. Estos patrones resuelven problemas de diseño específicos y hacen a los diseños más flexibles, elegantes y, en definitiva, reusables. Ayudan a los diseñadores a reusar diseños exitosos basando los nuevos diseños en la experiencia previa.

Un patrón de diseño describe un problema que ocurre una y otra vez en nuestro ambiente, y luego describe el núcleo de una solución al problema, de modo que dicha solución pueda usarse nuevamente. En general un patrón tiene 4 elementos:

  • Nombre: En una o dos palabras se usa para describir el problema de diseño, las soluciones y las consecuencias. Nombrar un patrón incrementa nuestro vocabulario de diseño y nos permite diseñar a un mayor nivel de abstracción. Tener un vocabulario de patrones nos permite conversar sobre ellos con nuestros colegas y en la documentación.
  • Problema: Describe cuando aplicar el patrón. Explica el problema y su contexto.
  • Solución: Describe los elementos que hacen al diseño, sus relaciones, responsabilidades y colaboraciones. La solución no describe un diseño concreto o una implementación, ya que un patrón es como una plantilla que puede aplicarse en muchas situaciones diferentes. El patrón provee una descripción abstracta de un problema de diseño y como un conjunto general de objetos lo resuelve.
  • Consecuencias: Son el resultado y los trade-offs de aplicar el patrón. Son críticas para evaluar alternativas de diseño y para entender los costos y beneficios de aplicar el patrón.

Descripción de los Patrones de Diseño

Para describirlos se usa un formato consistente:

  • Nombre y Clasificación: El nombre del patrón transmite suscintamente la esencia del patrón. Un buen nombre es vital porque se volverá parte de nuestro vocabulario.
  • Intención (Intent): Una breve oración que responde a las siguientes preguntas: ¿Qué hace el patrón? ¿Cuál es su razón e intención? ¿Que problema de diseño particular ataca?
  • Also Known as (También conocido como): Otros nombres bien conocidos para el patrón.
  • Motivación: Un escenario que ilustra el problema y como los objetos en el patrón lo resuelven.
  • Aplicabilidad: En que situaciones puede aplicarse el patrón, que son ejemplos de diseño pobre que el patrón puede resolver, como reconocer estas situaciones.
  • Estructura: Representaciones gráficas del patrón usando diagramas de clases, objetos, colaboración, etc.
  • Participantes: Los objetos y clases participantes del patrón y sus responsabilidades.
  • Colaboraciones: Como colaboran entre si los participantes para llevar a cabo sus responsabilidades.
  • Consecuencias: ¿Como soporta el patrón sus objetivos?
  • Implementación: Ayudas y técnicas a tener en cuenta para la implementación del patrón.
  • Código de Ejemplo
  • Usos conocidos: Ejemplos de uso encontrados en sistemas reales.
  • Patrones relacionados: ¿Qué patrones están relacionados con este? ¿Cuales son las diferencias? ¿Con que otros patrones puede usarse?

¿Como resuelven los problemas los patrones de diseño?

  • Encontrando los objetos apropiados: La parte complicada del diseño orientado a objetos es descomponer un sistema en objetos. La tarea es dificil porque intervienen muchos factores: encapsulamiento, granularidad, dependencias, flexibilidad, performance, evolutibilidad, reusabilidad, etc, etc. Los patrones ayudan a identificar abstracciones no tan obvias y los objetos que pueden modelarlas.
  • Determinando la granularidad
  • Especificando las interfaces entre los objetos
  • Especificando las implementaciones de los objetos: Es imporante entender la diferencia entre la clase de un objeto y su tipo. La clase de un objeto define como se implementa el mismo. Define su estado interno y los métodos que implementan los mensajes. En contraste el tipo de un objeto solamente se refiere a su interface. Un objeto puede tener muchos tipos y objetos de clases diferentes pueden tener el mismo tipo. Cuando la herencia se usa cuidadosamente (algunos dicen que en forma apropiada), todas las clases derivadas de una clase abstracta comparten la misma interface. Hay dos beneficios de manipular los objetos únicamente en términos de interfaces definidas por clases abstractas: Los objetos cliente no se preocupan de los tipos específicos de objetos que usan mientras los mismos adhieran a la interfacen que esperan. Y tampoco se preocupan de las clases que implementan esos objetos. Esto reduce sensiblemente las dependencias de implementación entre subsistemas que lleva al siguiente principio de diseño: Programar para una interface y no para una implementación.
  • Poniendo en práctica mecanismos de reuso: Las dos técnicas más comunes de reusar funcionalidad son la herencia de clases y la composición de objetos. El reuso por clasificación generalmente se denomina de "caja blanca". La composición de objetos es una alternativa al reuso por herencia. En este caso la nueva funcionalidad se obtiene componiendo objetos para obtener funcionalidad más compleja. La composición requiere que los objetos que se componen tengan interfaces bien definidas. Este estilo de reuso se denomina de "caja negra". La herencia de Clases tiene ciertas desventajas. Primero, no se puede cambiar la implementación heredada de clases padre en run-time porque la herencia se define en tiempo de compilación. Segundo, y generalmente peor, las clases padre a menudo definen parte de la representación de sus subclases ya que la herencia expone a las subclases detalles de la implementación de sus padres. Por otra parte la composición de objetos se define en run-time a través de objetos que obtienen referencias a otros objetos. Como los objetos se acceden únicamente a través de sus interfaces no estamos rompiendo el encapsulamiento. Cualquier objeto puede ser reemplazado por otro mientras que el mismo tenga el mismo tipo. La composición tiene otro efecto en el diseño de sistemas. Favoreciendo la composición por sobre la herencia ayuda a mantener cada clase encapsulada y enfocada en una tarea. Las clases y jerarquías se mantendrán pequeñas y será menos probable que se conviertan en monstruos inmanejables. Además un diseño basado en composición tendrá más objetos y el comportamiento del sistema dependerá de sus relaciones en lugar de estar definido en una clase. Estos nos lleva al segundo principio de diseño: Favorecer la composición de objetos por sobre la herencia de clases.