17 de enero de 2015

Patrones de diseño en Visual FoxPro (Parte 2/2)

Texto original: Design Patterns in Visual FoxPro
http://www.tightlinecomputers.com/Documents/Des_Patterns.ZIP

Autor: Andy Kramek
Traducido por: Ana María Bisbé York

...continuación de (Patrones de diseño en Visual FoxPro (Parte 1/2))

¿Qué es un mediador y cómo lo utilizo?

El mediador describe una solución como una llave, que todos hemos encontrado siempre que tratamos de diseñar una clase nueva. Cómo tratar con situaciones donde un objeto tiene que responder a cambios, o controlar el comportamiento de otro.

¿Cómo reconozco donde necesito un mediador?

La definición formal del mediador, y dada por "GoF" es:

"Define un objeto que encapsula cómo interactúan un conjunto de objetos. El mediador estimula la pérdida de acoplamiento manteniendo objetos explícitamente, a partir de la referencias de otro, y le permite variar su interacción independientemente."

Este dirige uno de los problemas fundamentales que tenemos que afrontar en cualquier tarea de desarrollo de aplicaciones, que asegura que los objetos pueden comunicar con otros sin tener realmente que incluir referencias de código duro en sus clases.

En ningún lugar es esto más crítico, que al crear la interfaz de usuarios compleja que la generación actual usuarios finales de PC, no esperan; pero exigen. Típicamente, tenemos que hacer que toda la interfaz de usuario, responda como una única entidad, habilitando e inhabilitando funciones y controles como respuesta a las acciones del usuario u opciones, o, de acuerdo con sus derechos y permisos. Al mismo tiempo, queremos diseñar y generar clases genéricas, reutilizables. Los dos requerimientos están, aparentemente en conflicto directo uno con el otro.

Dele un vistazo al formulario de ejemplo el que hemos utilizado para ilustrar la cadena de responsabilidades en la sección precedente (Figura 6). Si ejecuta este ejemplo, verá que el botón de comandos "Add Tax" está activo cuando el formulario es instanciado, incluso si no existe la propiedad Price (precio). Por supuesto, al hacer clic en el botón con el precio de $0.00 simplemente retorna cero $0.00 y aparentemente no ocurre nada más. Pero, ¿qué pasa si se introduce un valor negativo? La respuesta corta es, que esto siempre puede ocurrir y funcionará, de tal forma, si introduce $ -29.95 como el precio y hace clic en el botón "Add Tax" el formulario le dirá que la tasa es $ -1.722 y el precio total es $ -31.67.

Podría ser mejor, si el botón "Add Tax" estuviese sólo habilitado cuando el precio fuera mayor que cero. Por supuesto, la solución más simple es justamente agregar una pareja de líneas de código al Valid() del cuadro de texto (textbox) de este formulario que implemente esta funcionalidad, como, por ejemplo:

IF This.Value > 0
  ThisForm.cmdCalc.Enabled = .T.
ELSE
  ThisForm.cmdCalc.Enabled = .F.
ENDIF 

Este tipo de "acoplamiento ligero" (Tight coupling) puede ser aceptable cuando estamos tratando con un cuadro de texto (textbox) y un botón de comandos en un único formulario. Sin embargo, rápidamente tendremos problemas si tratamos de adoptar esta solución al tratar con controles múltiples que tienen que interactuar en combinaciones diferentes y complejas. Incluso, encontrando dónde se especifica el código que controla una interacción particular, puede haber problemas, y simplemente cambiar el nombre de un objeto provoca daños mayores. Es aquí donde juega su papel el patrón de mediador.

La idea básica es, que cada objeto se comunica con un "mediador" central, el que conoce de todos los objetos que están al alcance actual, y cómo manipular su estado cuando un evento dado es reportado. De esta forma, evitamos todas las cosas asociadas con colocar código específico dentro de un método asociado con el evento Valid(). En su lugar, podemos escribir completamente código genérico con su clase padre. Entonces, si utilizamos un objeto mediador, podemos reemplazar el código específico en la instancia del cuadro de texto precio con el código algo así como lo siguiente:

This.oMediator.StateChange( This ) 

Como ha visto, el cuadro de texto no tiene idea de qué hará el mediador con la información, o incluso qué información desea. Todo lo que tiene que hacer es llamar al método "StateChange" y pasa una referencia por si misma. Cualquier acción subsiguiente es para especificar la implementación del mediador.


¿Cuáles son los componentes de un mediador?

Un mediador tiene dos requerimientos esenciales. Primero, necesitamos una clase "mediadora" que define la interfaz, y funcionalidad genérica, para el "mediador concreto" que son subclases que definen varias posibles implementaciones. El segundo es, que todos los objetos Colleage deben estar basados en clases que pueden comunicarse con su mediador. La estructura básica del patrón de mediador se muestra en la Figura7.


Figura 7. El modelo básico de patrón mediador.

Puede ver de este diagrama, que las clases que necesitan trabajar con el mediador necesitan guardar una referencia al objeto mediador. Está pensando probablemente que, en Visual Foxpro, tenemos una clase nativa, candidata para mediadora en el Form - formulario, (en el contexto de cualquier interfaz de usuario). Utilizando el formulario como un mediador es bueno porque está siempre disponible a cualquier objeto a través de la referencia ThisForm sin que necesitemos preocuparnos sobre sus propiedades específicas. Sin embargo, cuando trabajamos con clases no visuales y puede no ser la mejor solución, entonces, es probable definir mejor una clase mediadora genérica, no visual.
Lo segundo que debe observar desde el diagrama es, que el objeto mediador necesita guardar una referencia a todos los objeto colegas (colleagues). De aquí surge la pregunta de cómo adquirir y guardar la referencia – la que tiene diferentes respuestas. Posiblemente, lo más sencillo de implementar es que cada control define un método "RegisterMe()", que es creado en cualquier instancia de clase, y chequea la presencia de un mediador y pasa una referencia de si mismo, al mediador. Otra posibilidad es que el mediador define el método "GoFindEm()", que busca el entorno para descubrir objetos. Otra vía de clase mediadora necesitará también mantener una colección (array) para guardar las referencias a sus colleagues.

¿Cómo puedo implementar el mediador? (Ejemplo: FrmMed.scx, CH15.vcx)

Para implementar un mediador para nuestro pequeño ejemplo de índices de ventas tenemos que hacer una pequeña preparación. Primero, necesitamos una clase form que puede manipular un mediador. La clase frmMidiated en CH15.vcx tiene tres propiedades personalizadas (Tabla 3), y el siguiente código es agregado al evento Load(), para asegurar que el mediador está definido antes de que sea instanciado ningún otro control form.

PropiedadDescripción
cmedclassNombre de la clase mediadora para instanciar por este formulario
cmedlibBiblioteca de clases de la clase mediadora para instanciar por este formulario
omediatorPropiedad expuesta para guardar la referencia en el objeto Mediador
Tabla 3. Propiedades de la clase Form mediadora


LOCAL lcMClass, lcMLib
DODEFAULT()
WITH ThisForm
  *** Si es identificada una clase mediadora, la instancia
  lcMClass = ALLTRIM( .cMedClass )
  lcMLib = ALLTRIM( .cMedLib )
  IF NOT EMPTY( lcMClass ) AND NOT EMPTY( lcMLib )
    .oMediator = NEWOBJECT( lcMClass, lcMLib )
  ENDIF
ENDWITH 

Por supuesto, debemos ser cuidadosos de limpiar las referencias de objetos, por eso, la clase form incluye el siguiente código en su evento Destroy(), para asegurase de que ocurra la limpieza.

IF VARTYPE( This.oMediator ) = "O"
  This.oMediator.Destroy()
ENDIF 

Nota: Esto es necesario, para evitar el problema que surge porque la secuencia normal de los eventos es que los objetos son destruidos en orden inverso a su creación. Debido a que el mediador es creado primero, normalmente será destruido el último, pero, debido a que guarda las referencias de otros objetos y no pueden ser destruidos mientras este existe. Entonces, necesitamos forzar la destrucción del mediador "fuera de turno" cuando el formulario es liberado, y que los objetos puedan liberarse por si mismos.

Lo siguiente que necesitaremos definir es, una nueva subclase para cada uno de los controles que tendrá que trabajar como mediador. Una nueva propiedad (oMediator) puede ser definida para guardar una referencia de objeto mediador, junto con 3 nuevos métodos:
  • Regyster() Llamado desde el INIT() del control. Verifica la presencia del mediador. Si es encontrado uno, guarda la referencia local y entonces llama al método mediador Register(), pasando la referencia a ellos.
  • Notify() Método que llama al método Notify() en el mediador y pasa una referencia al control actual.
  • UnRegister() llamado desde el método Destroy() del control. Verifica la referencia local del mediador. Si encuentra, llama al método UnRegister(), pasando una referencia al mismo y luego libera la referencia local.
Además, los siguientes eventos (cuando se aplican) tienen que ser modificados para llamar al método Notify() del control. Los métodos InterActiveChange() y ProgrammaticChange() para listas o Valid() para cuadros de textos.

Entonces, necesitamos una clase mediador abstracta, la que define la interfaz, y el comportamiento genérico para el método Register() y una colección aObjects. Esta clase también define StateChange() como un método plantilla, para implementar en la subclase. Podemos entonces, crear una subclase específica a partir de esta clase, para implementar el comportamiento específico que deseamos en nuestro formulario, (que era hacer clic al único botón habilitado "Add Tax" cuando el textbox "Price" tiene un valor mayor que cero 0).

Finalmente tenemos que recrear el formulario utilizando las clases nuevas.

¡Vaya! Parece que es mucho trabajo para hacer. Sin embargo, notará que todo, con excepción de la creación del archivo mediador que implemente el comportamiento específico, es enteramente genérico y es, por tanto, completamente re-utilizable. Un otras palabras, sólo tiene que hacerlo una vez.

Es importante enfatizar en que hagamos este sencillo formulario lo más flexible posible. Todo este código que controla la interacción entre este control está confinado en una sencilla sub-clase, la cual está especificada en el formulario, pero puede ser mantenida desde fuera de el. Para cambiar el comportamiento, puede modificar la subclase existente, o crea uno nuevo e implementa pero sólo cambia un par de propiedades en el formulario. La figura 8 muestra el formulario justo después de instanciarlos ahora y los implementará el mediador – observe que el botón "Add Tax"


Figura 8. El Mediador en uso (FrmMed.scx)

La única instancia del nivel del código que necesitamos en el formulario es en el evento INIT(), porque queremos forzar los controles para inicializarlos correctamente. Este no puede estar hecho antes del Init() del formulario porque este es el primer momento en el cual podemos estar seguros de que todos los controles del formulario sean instanciados y registrados con el mediador.

La clase mediadora abstracta está definida en el Ch15.vcx as "xMediator" con una subclase concreta (llamada medTaxForm), que es utilizada en un formulario de ejemplo. El código en el mediador abstracto es directo y se ocupa de la generación y manipulación de la colección de objetos registrados. El único aspecto a observar aquí, es que nosotros estamos utilizando una colección de tres columnas (nombre, padre y referencia de objeto), aceptada, por el hecho de que un formulario complejo puede tener más de un objeto con el mismo nombre; pero en diferentes contenedores.
El código en el método medTaxForm.Notify() es, por supuesto, específico para este formulario particular, como se muestra debajo. Este primer aspecto que hace para determinar que objeto lo llama. Si es el control combobox (el que también es un mediador disponible), no sucede nada. Sin embargo, si la llamada es desde el textbox Price, entonces el código recupera, desde el mediador de la colección, la referencia de objeto en el botón "Add Tax", el cual se le llama en este formulario "cmdCalc".

LPARAMETERS toObjRef
LOCAL lcName
*** Guarda el nombre en una variable local
lcName = UPPER( ALLTRIM( toObjRef.Name ))
*** Este es el mensaje desde el cuadro de texto Price IF lcName = 'TXTPRICE'
  *** Necesitamos una referencia al botón de comandos 'Add Tax'
  lnRow = This.FindInCollection( 'CMDCALC' )
  IF lnRow > 0
    *** ¡Lo tiene! Agarre su refrencia
    loTarget = This.aRegistered[ lnRow, 3 ]
    *** Habilita el botón, en dependencia del valor
    loTarget.cObjmode= IIF( toObjRef.Value > 0, "E", "D" )
    loTarget.Refresh()
  ENDIF
ENDIF
*** A partir de aquí, solo devolver (return)
RETURN 

La referencia del objeto está utilizada para establecer el modo propiedad del botón en correspondencia con el valor actual en el textbox precio, y llama es el método Refresh() para implementar el cambio.

Esto es todo lo que necesita en este ejemplo (admisiblemente sencillo). Sin embargo, una de las clases necesarias han sido estabilizadas, incluso muy complejas interacciones pueden ser controladas utilizando el método Notify() como un método control y agrega métodos adicionales para la subclase como necesaria.

Resumen de patrón mediador

Implementar el patrón mediador indudablemente requiere más planificación y mucho más preparación que cualquiera de los patrones que hemos discutido antes. Sin embargo, en situaciones donde tenemos que controlar complejas interacciones, esto compensa ampliamente el esfuerzo en tres vías: Primero, minimiza el tener que subclasear, al localizar su comportamiento que puede, y por otra parte puede esparcir entre diferentes clases en un objeto sencillo. Segundo, evita la necesidad de acoplar los objetos. Tercero, esto simplifica la lógica al reemplazar las interacciones "muchos a muchos" entre controles individuales con interacciones "uno a muchos" entre el mediador y los colegas. El bosquejo principal del patrón es que, especialmente al tratar con gran número de interacciones, el mediador por sí mismo se puede tornar muy complicado y dificultar su mantenimiento.

¿Qué es un decorador y cómo se utiliza?

El decorador describe una solución al problema de agregar una funcionalidad a un objeto sin cambiar realmente el código en el objeto.

¿Cómo reconozco cuándo necesito un decorador?

La definición formal del decorador, dada por el "GoF" es:

"Adjuntar responsabilidad adicional a un objeto dinámicamente."

La necesidad de cambiar dinámicamente la funcionalidad o comportamiento de un objeto surge típicamente en una de estas dos situaciones. Primero, cuando el código fuente del objeto está no disponible, puede ser que el objeto es un control ActiveX o una biblioteca de clases de terceras partes está utilizada. Segundo, cuando la clase que es ampliamente utilizada para la responsabilidad específica, se necesita sólo en una o más situaciones particulares y puede ser inapropiado agregar simplemente el código de la clase.

En la sección precedente hemos visto varias vías para determinar la cantidad de la tasa para aplicar el precio en dependencia de la localidad. Nuestra objeto base de cálculo (CH15.vcx:: cntTaxRoot) trabaja exponiendo un método llamado CalcTax(), que toma dos parámetros, un valor y un índice y retorna la tasa calculada. Afrontamos el problema básico de tratar con la tasa en el formulario utilizando un patrón de puente para separar la implementación de la interfaz. Sin embargo, como hemos visto rápidamente, esta fue no suficientemente flexible para acoplar con diferentes índices de tasa en diferentes localidades. Ambos, los patrones de estrategia y de cadenas de responsabilidades pueden manipular este aspecto, sin embargo, ambas soluciones implican subclasear nuestra clase base de cálculos de tasas.

El patrón de decorador nos permite solucionar el mismo problema sin necesitar subclasear el original. En lugar de definir un nuevo objeto el que tiene exactamente la misma interfaz como el cálculo de la tasa, pero el que incluye el código necesario para determinar la tasa apropiada para una localización dada. El objeto "mira" justo como el cálculo de la tasa como su cliente; pero, porque también guarda una referencia al objeto de cálculo de tasa, puede "pre-procesar" cualquier requerimiento de un índice y luego, simplemente puede la real implementación cuando esté lista.

¿Cuáles son los componentes del decorador?

Un decorador, tiene dos requerimientos esenciales. Primero, necesitamos la clase implementación que define la interfaz y el núcleo de funcionalidad. Segundo, nosotros necesitamos un decorador que reproduce la interfaz de implementación, y guarda una referencia a el. El objeto cliente ahora dirige las llamadas que puede ir directamente a la implementación, en su lugar, del objeto decorador. La estructura básica del patrón decorador se muestra en la figura 9.


Figura 9. El patrón decorador base.

Este patrón es en realidad un puente extendido. Debido a que el cliente está afectado, puede dirigir el objeto decorador como si realmente fuera la implementación al final del puente estándar, porque la interfaz de los dos es la misma. Debido a que la implementación está afectada, la petición mira exactamente igual que si fuera directamente desde el cliente. No necesita nunca conocer que incluso que el decorador existe.

¿Cómo implementar un decorador? (Ejemplo: frmDecorator.scx, CH15.vcx)

Una nueva clase, nombrada "cntDecorador" ha sido definida para implementar el ejemplo del decorador. Tiene tres propiedades y un método propios como sigue:

NombrePEMDescripción
cImpclassPropiedadNombre de la clase para instanciar para este decorador.
cImplibPropiedadPropiedad Biblioteca de clases para la clase implementación para instanciar.
oCalculatorPropiedadPropiedad Referencia de objeto para instanciar la clase que es el implementador real al método CalcTax(). Este objeto es el instanciado en el Init() del decorador.
CalcTaxMétodoLa implementación del decorador para el método equivalente en el implementador "real"
Tabla 4. Propiedades, método y eventos propios para la clase decorador.

La clase decorador es realmente muy simple. Al crearse, instancia una clase implementación real, la cual está definida por sus propiedades: nombre de clase y biblioteca. El objeto, por supuesto debe implementar un método CalcTax() que devuelva un valor numérico.

El código en el método CalcTax() del decorador es muy directo. Espera recibir dos parámetros, el ID de localización como una cadena, y el precio para el que la tasa es requerida. Observe que éstos no son los mismos parámetros que se requieren por el método CalcTax() en el método real. Aquí necesitamos pasar un precio y el índice de la tasa para aplicarle. El método CalcTax() del decorador determina el índice adecuado basándose en el ID de localización y luego llama al método CalcTax() de su objeto de implementación pasando el parámetro real. El valor devuelto es justamente pasado al cliente sin ninguna modificación adicional.

LPARAMETERS tcLocation, tnPrice
LOCAL lnRate, lnTax
STORE 0 TO lnRate, lnTax
*** Determine el índice correcto
DO CASE   CASE tcLocation = '01'
    lnRate = 5.75
  CASE tcLocation = '02'
    lnRate = 5.25
  CASE tcLocation = '03'
    lnRate = 0.00
  OTHERWISE
    lnRate = 0
ENDCASE
*** Ahora, pase la llamada al objeto real
lnTax = This.oCalculator.CalcTax( tnPrice, lnRate )
*** Y devuelva el resultado
RETURN lnTax 

El formulario utilizado de ejemplo (figura 10) muestra el decorador en uso.


Figura 10. Utilizar un decorador (frmDecorator.scx)

El ejemplo del decorador utiliza una copia de un formulario que fue creado para ilustrar el patrón de mediador en la sección previa. El único cambio son dos líneas de código en el método del formulario DoCalc() donde, en lugar de instanciar el primer miembro de la cadena de responsabilidades y llamar su método ProcessRequest(), ahora instanciamos el decorador y llamamos al método CalcTax()

LPARAMETERS tcContext, tnPrice
LOCAL lnTax
WITH ThisForm
  *** Verifica que tenemos disponible el Decorador
  IF VARTYPE( This.oCalc ) # "O"
    *** We don't so create it
    .oCalc = NEWOBJECT( 'cntDecorator', 'ch15.vcx' )
  ENDIF
  *** Ahora, sólo llamamos su método CalcTax() y pasa ambos parámetros: localidad y precio
  lnTax = .oCalc.CalcTax( tcContext, tnPrice )
  IF ISNULL( lnTax )
    *** No puede procesar la petición
    MESSAGEBOX( "No puede procesar la localización", 16, 'Error' )
    lnTax = 0
  ENDIF
  RETURN lnTax
ENDWITH 

Aunque es un ejemplo muy sencillo, puede ver qué fácil puede ser extender la funcionalidad del decorador. Un problema muy frecuente, en la vida real, en que este patrón puede ser utilizado, es cómo proporcionar la implementación de validación específica a una rutina genérica de guardado.

Resumen de patrón de decorador

El patrón de decorador es utilizado cuando deseamos modificar el comportamiento básico de una instancia específica sin la necesidad de incluso crear una nueva sub clase, o cambiar el código en el original. El decorador, esencialmente actúa como un pre-procesador para su implementación interponiendo su cliente y la implementación.

¿Qué es un adaptador y cómo lo utilizo?

El adaptador, como implica su nombre, describe una solución a un problema de tener dos interfaces disparejas actuando como un mecanismo de traslación entre ellas. La distinción de clave entre un adaptador y un decorador es, que mientras un decorador modifica su comportamiento, el adaptador sólo modifica la interfaz.

¿Cómo yo reconozco dónde necesito un adaptador?

La definición formal del adaptador fue dada por "GoF" es:

"Convertir la interfaz de una clase en otra interfaz de cliente esperada."

La necesidad de un adaptador generalmente resulta a partir de las modificaciones o mejoramientos de las bibliotecas de clases o componentes COM, Por ejemplo, re-factorizar puede frecuentemente resultar en cambios significativos a la vía en la cual la interfaz para una clase necesita ser definida. Esto a su vez, puede necesitar más cambios en el código de la aplicación que llama al servicio de su clase. Hacer los cambios es siempre una opción de alto riesgo que puede ser evitada siempre que sea posible.

Una solución cuando el código fuente está disponible, es guardar los métodos en la interfaz original a la clase; pero eliminar la implementación en los métodos nuevos. Los métodos viejos no hacen más el trabajo real, en su lugar, ellos controlan las llamadas a otros métodos. Sin embargo, esto requiere aún, que el código existente sea modificado.

La alternativa es crear una clase nueva la cual replica la interfaz esperada y traduce las llamadas a la interfaz en el formato correcto para reemplazarlo. Al tratar con componentes en los cuales el código no está disponible, es el único método de tratar con los cambios en su interfaz.

¿Cuáles son los componentes de un adaptador?

Un adaptador tiene dos componentes esenciales. Primero, necesitamos una clase que defina la interfaz que tiene que ser adaptada, el "adaptador". Segundo, necesitamos la clase "adaptador" que defina la interfaz esperada y los mapas de métodos en la interfaz a aquellos definidos por el adaptador. El cliente llama uno de los métodos esperados en el adaptador. La estructura básica para el patrón adaptador se muestra en la figura 11.


Figura 11. El patrón básico de adaptador

Este patrón es realmente otra variante del puente básico. Debido a que el cliente está ocupado puede dirigir el objeto adaptador como si realmente fuera la implementación al final del puente estándar. Debido a que el adaptador soporta la interfaz esperada, el cliente mantiene inconsciente que la implementación es realmente adaptada por otro método como parte de una interfaz diferente.

¿Cómo implemento un adaptador?

Un adaptador es típicamente implementado por la creación de una sub clase del adaptador y permitiéndolo heredar los métodos de interfaz esperada. Entonces, puede ser el código con la llamada apropiada al método definido por el adaptador.

Esto presenta problemas en Visual FoxPro, porque no soporta múltiples herencias, la cual es requerida para hacerla de esta vía. Una nueva característica que fue introducida en Visual FoxPro 7.0 es la palabra IMPLEMENTS que permite una clase Visual FoxPro, definida en código utilizando el comando DEFINE CLASS, para heredar una interfaz que está definida en un tipo de biblioteca. Sin embargo, debido a que depende del tipo de biblioteca puede ser utilizada solamente con objetos COM, y no con clases nativas de Visual FoxPro, tales que es no realmente mucha ayuda en este concepto.

La mejor vía de implementar un adaptador en Visual FoxPro es, crear una clase la que expone el método de interfaz, y guarda una referencia de objeto al adaptador. El objeto cliente pueden llamar sus métodos esperados que a su vez llaman los métodos apropiados del adaptador. El resultado está tan parecido a la implementación de un decorador ilustrado en la sección precedente, que no he creado un ejemplo específico para el. La única diferencia desde el decorador es que en lugar de modificar su comportamiento, el adaptador simplemente re-ruta la llamada del método.

Resumen del patrón de adaptador

El patrón de adaptador está utilizado para evitar la necesidad de un código cambiante cuando una interfaz, o permitir futuras modificaciones o implementaciones cuando diseña clases genéricas. Sin embargo, cuando Vidual FoxPro no admite herencia múltiple, el único mecanismo de implementación disponible es idéntico que utiliza el Decorador.

¿Qué es una envoltura (wrapper), y cómo la debo utilizar?

Esta es una buena pregunta, Si estudia "GoF" verá que no encuentra un patrón llamado "wrapper" en ningún lado en esta lista de patrones primarios. Sin embargo, con una búsqueda más diligente de estos recursos verá que realmente está enumerada como un sinónimo para ambos el patrón Adaptador y Decorador.

Ahora, esto no tiene mucho sentido para nosotros. Si el patrón envoltura es realmente un sinónimo de ambos, entonces, lógicamente ambos son solo sinónimos de cada otro y así, los tres términos pueden referirse al mismo patrón. Claramente no es el caso. Como hemos visto, el adaptador y el decorador son diferentes en su intento y algunas similitudes en su implementación son, como siempre, irrelevantes en el contexto de la definición de patrones. Mientras no desee discrepar del "GoF", sentimos, que en el caso particular al menos ellos lo tienen mal.

Nos parece que la palabra "wrapper" realmente significa una cobertura que describe o al decorador o al adaptador como "envolturas" es también lingüísticamente incorrecto. Ambos patrones confían en engañar al cliente en su comportamiento creyendo que un objeto interpuesto es realmente un origen que tiene que mirar justo como el destino. Ellos pueden, sin embargo, ambos, ser mímicos, o impersonales; pero ellos no son realmente wrappers.

Entonces, ¿dónde puedo utilizar un wrapper?

Tomamos una vista (y esto es pura especulación) que un intento de wrapper es controlar el contenido. Como parte de esta función puede; pero no tiene que incluir funciones provistas por ninguno, o ambos, el Decorador o Adaptador. Un buen ejemplo surge cuando es necesario integrar entradas que no son objetos, sino entornos orientados a objetos, de tal forma que permita la interfaz de la entrada para ser dirigida como si realmente no existiera un objeto.

Por ejemplo, una DLL wrapper que proporciona acceso a las funciones de la DLL puede ser vista que actúa como un adaptador; pero la distinción es tal que el verdadero wrapper es también responsable de manipular las funciones básicas de administración, las que no son parte del patrón adaptador. Por eso el wrapper típicamente incluirá métodos para chequear que la requerida DLL es actualmente disponible, que es la versión correcta y es apropiadamente registrada. El wrapper es también responsable de liberar la DLL cuando finaliza.

En un entorno puramente orientado a objeto, nos parece que la mayoría de las clases que vemos se refieren como su "managers" son realmente wrappers. Por ejemplo, un administrador de formularios que crea y libera formularios, controla una colección de formularios y proporciona una interfaz para el acceso al ejecutar formas es actualmente un wrapper. Esta distinción de nombre puede ser, porque está tratando con objetos pero que no altera el intento, y se debe a que nosotros vemos como implementaciones del mismo patrón.

Resumen del patrón Wrapper

La tabla resume las diferencias que hay entre el desempeño de Wrappers, Decoradores y Adaptadores.

PatrónObjetivo
DecoradorModifica el comportamiento, sin modificar una interfaz existente.
AdaptadorModifica la interfaz sin modificar el comportamiento.
WrapperProporciona la interfaz para, y sirve para, comportamiento que se define en cualquier otro lado.

Conclusiones

En esta sesión hemos tratado de cubrir lo que consideramos más importante, y lo más comúnmente encontrado, patrones. Sin embargo, existen muchos otros patrones bien conocidos y reconocidos que nosotros no hemos siquiera mencionados. No quiere decir que no son valiosos, o incluso interesantes; pero nuestra intención no fue escribir un resumen abarcador del diseño de patrones – existen otros muchos que están mucho mejor cualificados que nosotros en esta área. Existen muchas fuentes para mayor información en diseño de patrones; pero recomendamos que, si está interesado en este tema, lea "Design Patterns, Elements of Reusable Object-Oriented Software" de Erich Gamma, Richard Helm, Ralph Johnson y John Vlissides como el punto de partida para sus investigaciones.

No hay comentarios. :

Publicar un comentario