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.

5 comentarios :

  1. Me parece increíble que después de tantos años utilizando el amado zorro aún pueda yo seguir descubriendo tantas herramientas, técnicas y métodos para mejorar. Como siempre muy agradecido por la existencia de este sitio y muy complacido que se publiquen artículos periódicamente.

    ResponderBorrar
  2. Es excelente este sitio me ha sacado de muchos apuros, muchas gracias.

    ResponderBorrar
  3. Me he encontrado con un problema un tanto curioso. En mi computadora corro un reporte y sale correctamente pero cuando paso del programa o otro equipo y corro el mismo reporte sale con menos información.
    Aclaro, que las tablas son las mismas pero los resultados son diferentes.

    Si alguien me puede dar una luz sobre que debo de cambiar o revisar en el programa se los agradeceria mucho

    ResponderBorrar
  4. Buenísimo. Muchas gracias por compartir

    ResponderBorrar

Los comentarios son moderados, por lo que pueden demorar varias horas para su publicación.