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

De Cuba-Wiki

DESIGNING REUSABLE CLASSES (JOHNSSON-FOOTE)

La Programación Orientada a Objetos promueve el reuso. Se reduce el tiempo de desarrollo y el costo de mantenimiento de los sistemas. Pero esto no es gratis, hay que trabajar para eso. Hay técnicas para producir componentes reusables.

Un objeto es similar a un valor de un TAD, en el sentido en que ambos encapsulan datos y operaciones sobre esos datos (sic). Por esto, los lenguajes orientados a objetos proveen modularidad y ocultamiento de información (information hiding). Pero hay dos ventajas clave que se agregan en la orientación a objetos: polimorfismo y herencia.


Polimorfismo.

Los objetos solo saben responder mensajes. Cuando se le envía un mensaje a un objeto, este mensaje se asocia con un método que se va a ejecutar para responder a ese mensaje. Pero esta asociación se hace en el momento de recepción del mensaje y no antes (late-binding). El método que asociado depende de cual es la clase de la que es instancia el objeto receptor. Pues allí esta definido este. El ejemplo típico es un grupo de elementos en un Array, cada uno de los cuales puede pertenecer a una clase distinta. En tanto todos los elementos entiendan los mensajes que se les mandan, otro objeto puede interactuar con el contenido del array sin preocuparse por la clase de los elementos.


Protocolo.

La especificación de un objeto esta dada por su protocolo, es decis, los conjunto de mensajes a los que sabe responder. El tipo de los argumentos también es importante, pero es importante referirse al “tipo” como el protocolo y no la clase. Objetos con protocolos idénticos son intercambiables. Es por est que la interface entre los objetos está definida por el protocolo que cada una de ellas espera que la otra sepa entender. Los protocolos ademas de ser importante por definir las interfaces en los programas, ellos son aún más importantes como medio de comunicación entre los programadores. Los protocolos compartidos crean un vocabulario que los programadores pueden reutilizar para facilitar el aprendizaje de nuevas clases. Así como los matemáticos reutilizan los nombres de las operaciones para matrices, polinomios, y otros objetos algebraicos, de la misma manera los programadores de smalltalk usan los mismos nombres para las operacions en muchos clases. De esta manera, un programador sabrá el significado de muchos componentes de un nuevo programa la primera vez que lo leen. La utilización de protocolos estándares favorece el polimorfismo.

Herencia.

La herencia tiene algunas ventajas: en principio promueve el reuso de código y provee de un modo de organizar y clasificar las clases, ya que clases con la misma superclase suelen estar bastante relacionadas. Otra ventaja es que la herencia promueve el desarrollo de protocolos estándares. También permite la extensión de clases existentes dejando el código existente intacto. De esta manera los cambios hechos por un programador difícilmente afecten a otro.

Clases Abstractas.

Comúnmente los protocolos estándares se representan en clases abstractas. Por lo general, conviene subclasificar de una clase abstracta, ya que una clase concreta tiene definido que colaboradores internos conforman su estado, es decir, como estarán representadas sus instancias, y es común que algunas subclases puedan necesitar una representación diferente. Por otro lado, las clases abstractas no necesitan tener colaboradores internos definidos ya que estas pueden estar definidas en sus sublases según la conveniencia de cada una de ellas. Esto evita estos conflictos.

Toolkits y Frameworks.

Los frameworks son diseños reutilizables, comúnmente formados por varias clases abstractas y algunas clases concretas de componentes que se pueden reutilizar. Definen la arquitectura de la aplicación (o parte de ella). Los Toolkits, en cambio, son independientes de la aplicación, pueden ser reutilizados en dominios totalmente diferentes y no definen la arquitectura. La principal diferencia entre ambos es que en los frameworks hay inversión de control. Es decir, el usuario del framwork define objetos con los que el framework colabora. Algo que claramente no sucede en Collection por ejemplo. Por otro lado, los framworks pueden verse como algo concreto freente a los patrones de diseños ya que son diseños implementados y no ideas abstractas.


While-Box vs Black Box Frameworks.

  • White-Box: El usuario debe subclasificar de clases abstractas. Por lo tanto, esto requiere que conozca como el framwork está construido. Esto puede resultar difícil de aprender a usar para un nuevo usuario.
  • Black-Box: A diferencia del caso anterior, a este tipo de frameworks se le provee con componentes con comportamientos específicos de la aplicación. Estos componentes deben respetar ciertos protocolos. El framework también provee de muchos componentes. El usuario solo necesita conocer y entender las interfaces externas para utilizarlo. En general son más fáciles usar pero menos flexibles.

Ciclo de Vida.

Crear clases abstractas y frameworks es una forma de hacer componentes reutilizables y un modo de limpiar el diseño. Dado que los White-Box frameworks son una convención de redefinición de métodos, no hay una línea fina entre lo que es un White-Box framework y una simple jerarquía de clases. Por lo general es difícil encontrar clases abstractas al principio. El humano piensa mejor sobre ejemplos concretos para luego generalizar. Por eso es común que se encuentren más y mejores abstracciones a medida que el dominio se va conociendo con más detalle.

Reglas para encontrar protocolos estándares.

1) Introducción de recursión: si un objeto receptor de un mensaje X, al ejecutar el metodo correspondiente realiza operaciones similares sobre sus colaboradores, entonces los mensajes a los colaboradores tambien deberían llamarse X (aún cuando la cantidad de parámetros no coincida). De esta manera el lector del programa notará la conexión. Aún cundo no haya realmente recursión, el método parecerá recursivo.

2) Eliminar los Case análisis.

3) Reducir el número de argumentos.

4) Reducir el tamaño de los métodos: es más facil subclasificar y redefinir métodos pequeños que redefinir uno muy grande que haga muchas cosas. Por lo general, de esta manera todos los métodos heredados son correctos pero una parte debería ser cambiada en lugar de tener que redefinir todo el método grande.


Reglas para encontrar clases abstractas

5) Jerarquias profundas y angostas.

6) El tope de una jerarquía debería ser abstracto.

7) Minimizar acceso a variables: de esta manera las clases se hacen más abstractas eliminando su dependencia con la representación concreta. Una forma de hacerlo es acceder a los colaboradores internos mediante mensajes.

8) Las subclases deben ser especialización.

Reglas para encontrar frameworks.

9) dividir clases grandes en más pequeñas.

10) Factorizar las diferencias de implementaciones en subcomponentes (?).

11) Separar Métodos que no se comunican en diferentes clases.

12) Enviar mensajes a componentes en lugar de a self: un framework basado en herencia puede ser convertido en uno basado en componentes reemplazando métodos sobrescritos con mensajes a componentes.

13) Reducir el número de parámetros implícitos: A veces es difícil dividir una clase en dos o más porque los métodos que deben ir en diferentes clases acceden a las mismas variables de instancia.


Evolving Frameworks - A Pattern-Language for Developing Object-Oriented Frameworks.

Los frameworks son diseños reutilizables de partes o la totalidad de un sistema de software descrito por un conjunto de clases abstractas y el modo en que las instancias de estas clases colaboran. Desarrollar uno es costoso, debe ser simple para aprender, debe soportar extensiones para ser reutilizado en varios contextos diferentes y debe poderse usar rápidamente. Debe comprender una teoria de un dominio de problema y es siempre el resultado de un analisis.

A continuación se define un lenguaje de patrones que describe las etapas por las que pasa un framework comúnmente. No necesariamente pasan por todas todos los frameworks, algunos mueren o necesitan permanecer como White-box.

Three examples.

Se requiere realizar un framework para un dominio en particular. Las personas necesitan generalizar de ejemplos concretos. Por esto es difícil desarrollar un framework desde cero. La solucion propuesta es desarrollar tres aplicaciones diferentes del mismo dominio y de estas deducir las partes del diseño reutilizable. Luego, este framework debería seguir evolucionando a mewdida que se lo utiliza en mas aplicaciones. Esto se puede llevar a cabo de dos maneras:

1) Aplicaciones desarrolladas por el mismo grupo una después de la otra: esto permitiría comenzar a reutilizar el diseño lo antes posible (por ejemplo, al desarrollar la segunda se intenta reutilizar parte del diseño de la primera).

2) Aplicaciones desarrolladas por diferentes grupos en paralelo: permitiría diversidad y diferentes puntos de vista, pero requeriría un mayor tiempo para unificar las soluciones en un framework.

White-Box

La herencia resulta en mayor acoplamiento entre los componentes, pero permite modificar los componentes que se están reutilizando. Se podrían modificar cosas que el diseñador original nunca imaginó que se iban a cambiar. La composición es una técnica poderosa de reutilización, pero es difícil de entender al examinarse el texto estático de un programa. La composición puede cambiarse en tiempo de ejecución y la herencia no.

Component Library

Es común que se necesiten implementar los mismos objetos cada vez que se instancia el framework. Sin embargo, es difícil saber a priori cuales serán estos objetos. La solución que se propone es empezar con los objetos obvios e ir agregando nuevo a medida que se necesitan. En el largo plazo solo deberían quedar en la library los objetos que se reutilizan y no son específicos de una única aplicación.

Hot Spots

A medida que se desarrollan aplicaciones basadas en un framework, hay código que se vuelve a escribir. Estos lugares son “hot spots”. La solución es separar el código que cambia del que no. El que cambia puede encapsularse en objetos, de esta manera se pueden lograr variaciones por composición en lugar de subclasificar y escribir métodos.

Pluggable Objects

Muchas vece se subclasifica una clase y se redefine un método o muy poco de ella. Lo que termina sucediendo comúnmente es que se crean muchas clases que cambian en forma trivial. Esto incrementa la complejidad del sistema. La solución que se propone es crear subclases adaptables que para crear instancias reciben mensajes a enviar, clases de objetos a crear, índices a acceder, bloques a evaluar, etc. De esta manera se crean instancias para cada caso sin necesidad de subclasificar. Esto, sin embargo, puede causar que las clases sean más difíciles de entender y usar.

Fine-Grained Objects.

Para hacer la library de componentes más reutilizables, lo que se sugiere este patrón es dividir los objetos en otros más pequeños hasta que no tenga sentido la división en el dominio. Lo ideal sería que el framework se utilice componiendo objetos que implementan las funcionalidades deseadas sin necesidad de programar. Esto reduciría la duplicación de código y la necesidad de crear nuevas clases para las nuevas aplicaciones.

Black-Box Framework

Se propone favorecer la composición frente a la herencia cuando no está claro cual de las dos utilizar. Usar la herencia para organizar los componentes y la composición para combinarlos en aplicaciones. Los patrones anteriores están para ello.

Visual Builder

Con un black-box framework, la aplicación se puede hacer conectando objetos de clases existentes. El comportamiento de la aplicación está determinado entonces por como estos objetos están interconectados. Una aplicación de este tipo consiste comúnmente de dos partes:

1) El script que conecta los objetos del framework y los pone en funcionamiento.

2) El comportamiento de los objetos individuales. El framework provee la mayoría de la segunda parte, pero el programador debe aún proveer la primera.

La solución que se propone es crear una herramienta visual que permita llevar a cabo la primera parte de manera sencilla.

Language tools.

Una vez que creamos un visual builder, podría ser útil contar con inspectores y debuggers especializados para manipular mas fácilmente objetos del framework, que son más específicos del dominio y pueden presentarse de manera mas adecuada.