27 de febrero de 2015

Pasar múltiples valores

Artículo original: Passing Multiple Values
http://weblogs.foxite.com/andykramek/2005/07/19/passing-multiple-values
Autor: Andy Kramek
Traducido por: Ana María Bisbé York


Pasar múltiples valores

Una de las preguntas que veo una y otra vez en los foros técnicos que frecuento es "¿Cuál es la mejor forma para pasar múltiples valores de un objeto a otro?" Comencemos por un caso sencillo, cuando deseamos llamar a un procedimiento (o método) que devuelve un único valor. Obviamente podemos diseñar un procedimiento que devuelva el resultado deseado y dentro de un método de un formulario (o un objeto), podemos llamarlo como una función y capturar el valor devuelto en una variable local, de esta forma:

*** Llama a un programa externo/procedimiento y atrapa el resultado
luCalcValue = MyProcedure()
This.Control.Value = luCalcValue

Usar una referencia

Esto está bien cuando todo lo que queremos es pasar solamente un valor; pero por supuesto, existen muchos casos en los que necesitamos devolver múltiples valores desde un procedimiento. Probablemente, lo primero que salta a la mente es pasar una referencia al objeto llamado a un procedimiento hijo de tal forma que pueda acceder a las propiedades del objeto y métodos llamados directamente, de esta forma:

luCalcValues = MyProcedure(THISFORM) && o luCalcValues = MyProcedure(THIS)

Mientras esto se ve atractivo, rompe la encapsulación y, por consiguiente, no es una "buena práctica OOP." Pero si "MyProcedure" necesita una llamada a un método en un formulario, entonces es realmente la única forma de hacerlo. (He visto sugerir que nombre explícitamente la instancia del formulario y luego tenga el procedimiento para un objeto con ese nombre - pero esto sólo funciona si está utilizando el comando DO FORM, y esto sólo funciona si hay una única instancia para el formulario. ¡Esto no es una buena solución!)

Por otro lado, tener un objeto o procedimiento externo que depende de que pueda llamar a un método de otro objeto es mal diseño porque el procedimiento es por consiguiente indivisiblemente acoplado al objeto invocado. Es una regla fundamental de OOP que ningún objeto debe depender de una implementación externa u otra - debido a que tales objetos no e pueden utilizar sin que esté involucrado ese otro objeto (con el método asociado). Si se encuentra en ese caso, debe pensar en re-diseñar algunas cosas.

Por ejemplo, una mejor opción puede ser tener en la devolución del procedimiento un indicador de que el proceso llamado debe iniciar algunas acciones apropiadas. Esta vía de decisión de cómo hacer en la situación dada, llamar un objeto, y no el procedimiento hijo - es una vía mucho más obvias y flexible.

Utilizar variables públicas

Si no vamos a permitir que el procedimiento manipule el objeto, entonces, que tal si utilizamos variables Públicas (o declarar variables Privadas en la llamada a los métodos de tal forma que estén en el alcance del procedimiento) y teniendo el conjunto de procedimientos sus valores. Pero esto es exactamente una variación no POO al pasar una referencia y es un diseño peor por las mismas razones.(ahora el procedimiento es dependiente incluso de la existencia de variables nombradas adecuadamente las que deben estar en el alcance). Peor, si empleamos variables públicas hay siempre un riesgo de "colisión" donde las mismas variables se han cambiado por más de un lugar que el valor referenciado no es necesariamente el que se está esperando.

Utilizar un objeto parámetro

Entonces, si no podemos emplear estos métodos, ¿qué podemos hacer? Afortunadamente VFP permite crear y emplear objetos parámetros y esta es realmente la mejor forma de actuar. Entonces, lo primero que debemos decidir es que nuestro parámetro debe ser un objeto.

Si está utilizando VFP versión 8.0 o superior puede utilizar la clase base "empty" (que es la misma utilizada por SCATTER NAME) la que puede ser instanciada y liberada rápidamente debido a que no hay propiedades eventos o métodos nativos. Para agregar estas propiedades debe utilizar la función ADDPROPERTY().

Si está empleando una versión anterior de VFP entonces puede emplear una clase base ligera, garantizando que tenga el método AddProperty(), incluso "relation", la cual puede ser definida solamente en código, o "line", si desea definirlo en una clase visual, lo hará muy bien.
Este es uno de aquellos casos donde podemos utilizar realmente una clase base (otras clases empleadas comúnmente son SESSION y COLLECTION) - aunque si utiliza habitualmente el mismo conjunto de valores en una aplicación, puede que valga la pena crear una subclase estándar para evitar la necesidad de repetir el código y para el mantenimiento de las propiedades. Entonces ¿cómo hacemos esto?

Versión 8.0 o superior

*** Crea una instancia de la clase base Empty
loParams = CREATEOBJECT( 'Empty' )
*** Agregar e inicializar una propiedad tipo cadena
llOk = ADDPROPERTY( loParams, 'cName', 'Andy Kramek' )
*** Agregar e inicializar una propiedad tipo matriz - Array
llOk = llOk AND ADDPROPERTY( loParams, 'aList[2,2]', '' )
IF llOK
  ** Llenar la matriz
  WITH loParams
    .aList[1,1] = 'Akron'
    .aList[1,2] = 'Ohio'
    .aList[2,1] = 'Phoenix'
    .aList[2,2] = 'Arizona'
  ENDWITH
ENDIF

Versión 7.0 o anterior

*** Crear una instancia de una relación
loParams = CREATEOBJECT( 'Relation' )
*** Agregar e inicializar una propiedad tipo cadna
llOk = loParams.AddProperty( 'cName', 'Andy Kramek' )
*** Agregar e inicializar una propiedad tipo matriz - Array
llOk = llOk AND loParams.AddProperty( 'aList[2,2]', '' )
IF llOK
  ** Llenar la matriz
  WITH loParams
    .aList[1,1] = 'Akron'
    .aList[1,2] = 'Ohio'
    .aList[2,1] = 'Phoenix'
    .aList[2,2] = 'Arizona'
  ENDWITH
ENDIF 

Tenemos nuestro objeto parámetro, y lo llenamos, podemos sencillamente pasarlo como si fuera un valor único. Entonces, en nuestro procedimiento, en lugar de devolver un sencillo valor, simplemente creamos y devolvemos un parámetro objeto.

RETURN loParams

¿Cómo sabemos lo que estamos devolviendo?

Ahora se puede preguntar sobre cómo sabemos lo que estamos devolviendo. Bueno, eso también es muy simple. Existen dos métodos, podemos utilizar la función AMEMBERS() para tomar la lista de las propiedades del objeto parámetro. Esto está bien al utilizar la clase base empty() pero nos podemos desorientar si estamos utilizando Relation o Line. He aquí un ejemplo de este método.

*** Llama al procedimiento que devuelve un objeto como parámetro
loResults = MyProcedure()
*** Utiliza AMEMBERS() para obtener la lista de nombres de propiedades
lnProps = AMEMBERS( laProps, loResults, 0 )
*** Toma el nombre y el valor
FOR lnCnt = 1 TO lnProps
  *** Toma el nombre de la propiedad
  lcName = laProps[lnCnt]
  *** Y su valor
  luVal = EVAL( “loResults.” + lcName )
  *** Utiliza el resultado donde sea necesario…
  ? lcName, luVal
NEXT

Alternativamente, podemos utilizar PEMSTATUS() para determinar si existen las propiedades adecuadas en el objeto parámetro (esto es más sencillo cuando solamente buscamos una propiedad):

*** Llama al procedimiento que devuelve el objeto parámetro
loResults = MyProcedure()
*** Verifica la propiedad y asigna un valor predeterminado si no la encuentra
lcName = IIF(PEMSTATUS( loResults, 'cName', 5 ), loResults.cName, "" )
*** Utiliza el resultado donde se necesario…
? lcName 

Recuerde que trabaja en ambos sentidos ...

He estado hablando sobre cómo utilizar un objeto parámetro para tener múltiples valores devueltos; pero por supuesto no existen razones por las que no debamos emplear un objeto parámetro para PASAR también múltiples valores. Es mucho mejor hacerlo así, porque nos permite nombrar los parámetros más explícitamente. En otras palabras, en lugar de confiar en la posición del valor en la lista de parámetros, podemos sencillamente nombrarlos. En cuanto un resultado no sea necesario, no necesitamos contar de nuevo los parámetros - piense en la cantidad de veces que tuvo que hacer esto:

IF PCOUNT() = 3
  *** Ha pasado LastName, FirstName e Initial
ELSE
  IF PCOUNT() = 2
    *** Se asume que ha pasado solamente LastName y FirstName
  ELSE
    IF PCOUNT() = 1
      *** Se asume que ha pasado solamente LastName
    ELSE
      *** No pasó nada
    ENDIF && PCOUNT() = 1
  ENDIF && PCOUNT() = 2
ENDIF && PCOUNT() = 3 
Substituya ahora por:
WITH loParameters
  lcFirstName = IIF(PEMSTATUS( loParameters, 'FirstName', 5 ), loParameters.FirstName, "" )
  lcLastName = IIF(PEMSTATUS( loParameters, 'LastName', 5 ), loParameters.LastName, "" )
  lcInitials = IIF(PEMSTATUS( loParameters, 'Initials', 5 ), loParameters.Initials, "" )
ENDWITH

Es mucho más sencillo para trabajar y para mantener. Si no es por otra razón que no sea facilitarnos la vida, espero que adopte los objetos como parámetros, con el mismo entusiasmo con que los he adoptado yo.

14 de febrero de 2015

Controlando dispositivos TWAIN desde VFP

Artículo original: Controlling TWAIN devices from within VFP 
http://www.ml-consult.co.uk/foxst-29.htm
Autor: Mike Lewis 
Traducido por: Carlos A. Miranda



¿Necesita manejar un escaner o una cámara de video desde su aplicación?. Aquí le decimos como hacerlo.

Recientemente escribimos una aplicación FoxPro que manejaba el registro de delegados atendiendo a una conferencia internacional. el Cliente quería que la aplicación fotografiara a cada delegado que llegara, y también guardar la imagen digitalizada de las tarjetas de negocios de los delegados. Debido al gran número de delegados involucrados, la fotografía y el proceso de digitalización tenia que ser los más libre de problemas y fácil posible. Era particularmente importante para el operador ser capaz de controlar la cámara y el escanner mientras estaba sentado en su PC.

En este artículo, nosotros le diremos como desarrollamos este proyecto. La estrategia que adoptamos es razonablemente genérica y no es específica de ningún escanner en particular. Usted no debería tener dificultades en aplicar nuestras técnicas en sus propias aplicaciones si lo desea.

Primer paso: escoger el equipo


Para la fotografía, nosotros desacartamos una cámara digital estandar, principalmente porque no encontrabamos un método de transferir las imágenes sin utilizar las manos hacia nuestra aplicación. En vez de eso, nosotros escogimos una Philips ToUcam web camera (izquierda). Este tipo de dispositivo es utilizado usualmente para video conferencias y como una cámara on-line web cam, pero también puede capturar un solo frame. Este tiene la ventaja de ser un dispositivo TWAIN-compliant y puede ser controlado enteramente desde la PC.
El escanner que nosotros escogimos fue un Targus Mini Business Card Scanner (izquierda). Como su nombre sugiere, este está diseñado especificmente para digitalizar tarjetas de negocios. Como la cámara, esta es TWAIN-compliant también.

A pesar de que nosotros estamos contentos de recomendar ambos dispositivos, la mayoría de los modelos de cámara web o escanner habrían servido para nuestros propositos. El código que nosotros mostraremos en este artículo es capaz de capturar imagenes desde cualquier dispositivo compatible con TWAIN.




 ... Y el software

Hay muchos productos de sofware disponibles que le permiten a uste controlar un dispositivo TWAIN de forma programatica. El que nosotros optamos fue EZTWAIN, de Dosadi. Nos gustó este producto por las siguientes razones:
  • Fácil de programar. Nosotros teníamos media docena o algo así de funciones que preocuparnos de llamar.
  • Fácil de distribuir. Porque es un DLL que a diferencia de un control ActiveX, no tenemos que preocuparnos de registrarlo en el sistema del usuario.
  • Bajo costo. Dependiendo de las necesidades y del tipo de aplicaciones que escribas, el precio varia de nada a alrededor de US$200.
  • Excelente soporte del autor del producto, Spike McLarty.
Declarando sus función

El EZTWAIN DLL tiene alrededor de 70 funciones, pero para muchas aplicaciones usted nunca utilizará más de 7 u ocho de ellas. Aquí estan las declaraciones de las funciones más comunes:

DECLARE INTEGER TWAIN_SelectImageSource ;
  IN Eztw32.DLL INTEGER hWnd
DECLARE INTEGER TWAIN_GetSourceList ;
  IN Eztw32.dll
DECLARE INTEGER TWAIN_GetNextSourceName ;
  IN Eztw32.dll STRING @cSourceName
DECLARE INTEGER TWAIN_OpenSource ;
  IN Eztw32.DLL STRING cSourceName
DECLARE INTEGER TWAIN_AcquireNative ;
  IN Eztw32.DLL INTEGER nAppWind, INTEGER nPixelTypes
DECLARE INTEGER TWAIN_WriteNativeToFilename ;
  IN Eztw32.DLL INTEGER nDIB, STRING cFilename
DECLARE INTEGER TWAIN_FreeNative ;
  IN Eztw32.DLL INTEGER nDIB
DECLARE INTEGER TWAIN_SetMultiTransfer ;
  IN Eztw32.dll INTEGER nFlag

Capturando una imagen

Si uste solo tiene un dispositivo TWAIN device instalado, simplemente llame a la función TWAIN_AcquireNative() para capturar la imagen. Esta función inicia el proceso de captura. Cuando este ha finalizado, la imagen será presentada en memoria, en formato "device-independent bitmap (DIB)". La función utiliza dos parámetros de tipo integer; in la mayoría de casos estos serán cero. Retorna un manejador ( handle ) para la imagen.

En el caso de nuestra camara Web ToUcam, llamando a TWAIN_AcquireNative() lanza el visor de la cámara en pantalla (Figura 1). Esto despliega una alimentación continua de la imagen. En cualquier momento, el usuario puede hacer click en el botón de Captura para tomar la fotografía.


Figura 1: Esto es lo que el usuario ve cuando usted empieza el proceso de captura desde la cámar web.

Una vez que la imagen DIB esta en memoria, usted puede llamarl a la función TWAIN_WriteNativeToFilename() para escribir a un archivo que uste escoja. Por defecto, este será un BMP file, pero otros formatos también son soportados. Usted pasa dos parámetros para esta función: El primero es el manejador (handle) DIB retornado por TWAIN_AcquireNative(), y el segundo el nombre calificado del archivo destino.

Finalmente, llamar al TWAIN_FreeNative() para borrar de memoria la imagen DIB. Si usted no hace esto, usted rápidamente perdería memoria.

Aquí esta nuestro código para tomar una fotografía con la cámara web:

LOCAL lcFile, lnImageHandle, lnReply
lcFile = "c:testtest_image.bmp"
* Captura la imágen
lnImageHandle = TWAIN_AcquireNative(0,0)
* copia la imagen a un archivo
lnReply = ;
  TWAIN_WriteNativeToFilename(lnImageHandle,lcFile)
* Libera la memoria del manejador de la imágen
TWAIN_FreeNative(lnImageHandle)
* Chequear errores
IF lnReply = 0
  * imagen fue exitosamente grabada
ELSE
  * algo no estuvo bien
ENDIF

Note que la respuesta de TWAIN_WriteNativeToFilename() le dice a usted si el archivo fue escrito de forma exitosa. Sin embargo, esto no le dice a usted si el trabajo para obtener la imagen trabajó apropiadamente - la captura podría haber fallado por alguna razón, o podría haber sido cancelada por el usuario. Una manera de probarlo es verificando el tamaño del archivo resultante; si es cero, entonces ninguna imagen fue capturada.

Múltiples dispositivos

El código anterior captura una imagen desde cualquier dispositivo TWAIN que usted tenga instalado. Si usted tiene un escaner o una camera, el código iniciará el proceso de digitalización y grabará la imagen.

Pero que pasa si uste necesita manejar dos dispositivos de captura para la misma PC? Este fue el caso de nuestra aplicación, en la cual el usuario necesitaba controlar tanto la cámara como el escaner de tarjeta de negocios.

Por defecto, TWAIN_AcquireNative() capturará del primer dispositivo TWAIN que encuentre. Sin embargo, el EZTWAIN DLL tiene una función llamada TWAIN_SelectImageSource(), la cual le da al usuario la oportunidad de seleccionar un diferente dispositivo de captura. Cuando uste llama a esta función (usualmente con 0 como parámetro), el usuario ve el diálogo estñandar de la Figura 2 de las fuentes de disposivitos TWAIN que tiene disponibles para seleccionar. La función retorna 0 si el usuario cancela el dialogo o si no hay dispositivos de captura instaladps, de otra manera este retorna 1.


Figura 2: Dialogo estándar para seleccionar la fuente de captura de los dispositivos TWAIN.

En nuestro caso, nosotros no hemos querido utilizar esto para ver el díalogo. Porque nuestra aplicación tenía un botón específico para la cámara y otro para el digitalizador de tarjetas, nosotros quisimos seleccionar el dispositivo de forma progrmática.

Para hacer eso, nosotros utilizamos las dos siguientes funciones: TWAIN_GetSourceList(), la cual lee una lista de nombres de dispositivos dentro de la memoria del EZTWAIN; y TWAIN_GetNextSourceName(), la cual trae el siguiente dispositivo de la lista. Después llamamos a TWAIN_GetSourceList() una vez, y llamamos a TWAIN_GetNextSourceName() repetidamente hasta que este retorne 0 para indicar que no hay más nombre en la lista.

Como un ejemplo, aquí esta algo de código que usted podría utilizar para llenar un combo box con los nombres de los dispositivos disponibles:

LOCAL lcSource, lnReply
* Obtiene la lista de los dispositivos en memoria
TWAIN_GetSourceList()
lcSource = SPACE(255)
DO WHILE .T.
  * Obtiene el siguiente nombre de dispositivo
  lnReply = TWAIN_GetNextSourceName(@lcSource)
  IF lnReply = 0
    * No hay más nombres de dispositivos
    EXIT
  ENDIF
  
  * quitar los nulos, etc
  lcSource = ;
    LEFT(lcSource,AT(CHR(0),lcSource)-1)
  
* Agreagar al combo
  THISFORM.cboDevices.AddItem(lcSource)
ENDDO

Una vez que usted conozca los nombres de las fuentes, usted puede pasar estos a la función TWAIN_OpenSource(). Esta establecerá el dispositivo para la siguiente llamada a TWAIN_AcquireNative().

Por defecto, TWAIN_AcquireNative() cerrará la fuente de captura despùes de que finalice el proceso. Así que, si usted tiene más de un dispositivo, usted necesitará llamar a TWAIN_OpenSource() antes de cada llamada a TWAIN_AcquireNative(). Desafortunadamente, abrir la fuente de captura consume tiempo. Dependiendo del dispositivo, los usuarios podrían notar un retraso de algunos segundo antes de que la captura pueda empezar.

Como una alternativa usted puede llamar a la función TWAIN_SetMultiTransfer(1) para decirle al EZTWAIN que deje la fuente de captura abierta. De esta manera, usted solo necesita llamar a TWAIN_OpenSource() cuando usted quiera cambiar a un dispostivo diferente. Cuando nosotros tratamos de hacer esto, sin embargo, encontramos que the el visor para la cámar web ToUcam permanecia en la pantalla, frente a la ventana de nuestras aplicaciones todo el tiempo. Esto obstruía parte de la ventana de nuestra aplicación. Por esta razón, nosotros escogimos no mantener el dispositivo abierto.

Ir más lejos....

En este artículo, nosotros hemos tratado de darle a usted un pequeñ bocado del EZTWAIN DLL. Esta es una herrmienta extremadamente capaz. Con muchas más funciones que nosotros no tendriamos espacio para describir aquí. Si usted necesita contolar uno o más dispositivos TWAIN devices desde su aplicación Visual Foxpro, porque no descarga una copia y la explorae por si mismo.

Para más información acerca de EZTWAIN y otros productos relacionados con TWAIN, y para descargar una copia de la DLL, visite www.dosadi.com.

Si usted quiere conecer más acerca de Philips ToUcam web camera:
El escaner "Targus card-scanner" cuesta alrededor de US$125:
Mike Lewis Consultants Ltd. February 2003

6 de febrero de 2015

¿Cuándo ocurren los eventos?


Texto original: When Does It Happen?
http://www.jamesbooth.com/eventorder.htm
Autor: Jim Booth
Traducido por: Ana María Bisbé York

En el desarrollo de aplicaciones en Visual FoxPro somos siempre dependientes de la ocurrencia de los eventos. Cada vez que tecleamos o damos clic del ratón es muy importante para el correcto funcionamiento de la aplicación. En realidad ¿cuánto conoce acerca de cuándo ocurren las cosas en VFP? Este mes vamos a examinar algunos de los eventos importantes y encontrar realmente en qué momento ocurren.

Eventos y métodos.

Primero, antes de meternos en los eventos que se ejecutan durante varias operaciones en Visual FoxPro, permítanme clasificar los eventos y métodos que se encuentran disponibles en el producto. Prefiero referirme a estas localizaciones de código como métodos de evento y métodos. La diferencia entre un método de evento y un simple método está en cómo se activa. Un método simple solo se dispara si una línea de código lo ha llamado, puede tener un comportamiento predeterminado dentro de VFP o puede simplemente estar donde se pueda escribir el código y luego llamar a ese código cuando desee.

Un método de evento es automáticamente llamado si un evento ocurre, por ejemplo el método de evento KeyPress de un control será automáticamente llamado en cualquier momento que el usuario presione una tecla mientras el foco lo tenga este control. Es importante diferenciar entre el evento (presionar la tecla) y el método de evento (KeyPress) que se activa como resultado del evento que ocurre. Llamar a un método de evento en un código, no causa que el evento asociado ocurra. Simplemente corre el código de ese método de evento.

Tomando y perdiendo el foco.

Existe un número de métodos de evento y métodos asociados con el recibir y/o perder foco del control. Los métodos de evento relacionados con el recibimiento o pérdida del foco son When (método de evento), GotFocus (método de evento), SetFocus (método), Valid (método de evento) y LostFocus (método de evento).

Para utilizar las ventajas del modelo de eventos en Visual FoxPro es necesario entender claramente los métodos de eventos que son activados y el punto en el que son activados. En el proceso de recibir el foco la secuencia de métodos de eventos es 1 – When y luego 2 – GotFocus

El método de evento when puede ser pensado como el lugar para determinar si se le permitirá o no al foco llegar al control. Devolviendo un valor .F. desde el método de evento When evita que el foco llegue al control. Una vez que el evento devuelve un valor .T. el GotFocus es disparado (si el When devuelve .F. no se ejecuta el GotFocus.) El GotFocus puede ser clasificado como un método de evento en el cual se prepara al control para recibir el foco, ya que solo se dispara si el control en efecto recibirá el foco.

El método SetFocus tiene un comportamiento predeterminado en el inicio del proceso de recibir el foco por el control. El método SetFocus de un control es similar a la variable de foxpro2.x _curobj Existen dos controles que actúan ligeramente diferente con relación al método de evento When. Son el ListBox y Combobox. Con estos dos controles, el evento When se dispara de igual forma que para otros controles, cuando llega el foco y antes de que el objeto en realidad lo tome. El When puede devolver .F. y evita que el foco llegue al control.

Sin embargo, con listas y combos, el When también se dispara a cada momento cuando el item seleccionado cambia. Debido a este comportamiento es importante ser cuidadosos con el código que se coloca en el evento When de listas y combos. Debe estar seguro que el When se disparará tantas veces para estos controles, como el usuario navegue por sus listas.

El proceso de pérdida del foco es similar. Existen dos métodos de evento involucrados, Valid y LostFocus. El Valid se dispara primero y puede ser utilizado para evitar que el control pierda el foco. Devolver .F. en el Valid ocasionará que el control retendrá el foco. Devolver .T. del valid permitirá al foco abandonar el control y con seguridad se disparará el LostFocus. Igual que el When y GotFocus, el Valid puede ser usado para decidir si se permitirá al foco abandonar el control y el LostFocus puede ser utilizado para controlar las reacciones del control cuando pierda el foco.

Actualizando los datos.

Visual FoxPro es una herramienta de administración de Datos y como tal tiene mucho poder y es muy fácil de manipular las capacidades de amarre. Muchos de los controles de VFP son capaces de ser atados a fuentes de datos fijando sus valores en la propiedad ControlSource. El uso de la propiedad ControlSource provoca que el control automáticamente actualice el origen de datos (ControlSource) cuando el valor del control es cambiado.


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.