11 de febrero de 2013

Los métodos Access y Assign pueden ser útiles!

Artículo original: Access and Assign methods can be useful!
http://weblogs.foxite.com/andykramek/archive/2008/06/21/6300.aspx
Autor: Andy Kramek (http://weblogs.foxite.com/andykramek)

Traducido por: Luis María Guayán

Los métodos Access y Assign se introdujeron en VFP allá por la versión 6,0, pero para muchos desarrolladores, todavía esto es en gran medida una "característica desconocida". Mi objetivo hoy es mostrar un par de ejemplos en donde los métodos Access y Assign pueden ser muy útiles. Vamos a empezar por ver que son los métodos Access y Assign, y la forma de crearlos.

Básicamente, estos métodos son eventos-manejados en el sentido de que, si se define uno para una propiedad, éste se dispara cuando ocurre el evento asociado. Por lo tanto, para un método Assign, el evento es (no sorprendentemente) la asignación de un valor a la propiedad. Tenga en cuenta que no importa la forma en que la asignación se haga (es decir, si usted utiliza el comando STORE, o simplemente un "=") el método se dispara. De la misma manera para un método Access, el evento disparador es cualquier referencia a la propiedad. Una vez más, el método actual no importa; el método se dispara cuando el valor de la propiedad está siendo leído en una variable, o su usado en una referencia al objeto, o incluso utilizando la salida "?".

Puede agregar métodos Access y Assign a cualquier propiedad personalizada que usted cree, y a la mayoría de las propiedades nativas de VFP, a través del cuadro de díalogo Editar Propiedad/Método. Las excepciones son la propiedad Value de un control y las propiedades nativas de los controles ActiveX (aunque se puede añadir a las propiedades de un VFP OLE Control que aloja un control ActiveX).

Para crear un método Access o Assign para una propiedad todo lo que necesita hacer es definir un método que es el nombre de la propiedad más el sufijo "_Access" o "_Assign". Esto es de manera automática en los diseñadores visuales, tanto en los cuadros de diálogos "Nueva propiedad" y "Modificar Propiedad/Método" donde usted simplemente marca la casilla correspondiente para crear el método. En código simplemente declare el método en la definición de clase como cualquier otro método. Tenga en cuenta que a método Assign debe definir un parámetro de entrada para recibir el nuevo valor, mientras que un método Access no tiene parámetros.
DEFINE CLASS xObj AS SESSION
  *** Defina una nueva propiedad
  iLastID = 0
  *** Cree un método Assign para la nueva propiedad
  PROTECTED FUNCTION iLastID_Assign ( tuInval )
    *** Unicamente permite valores >= 0
    IF VARTYPE( tuInval ) = "N" AND tuInval >= 0
      This.iLastid = tuInval
    ENDIF
  ENDFUNC
  *** Cree un método de Access para la propiedad nativa DataSessionID 
  PROTECTED FUNCTION DataSessionID_Access
    *** Retorna la DataSession NO la DataSessionId! 
    RETURN This.DataSession
  ENDFUNC
ENDDEFINE
En esta simple definición he definido una propiedad personalizada que sólo acepta valores numéricos que son mayores o iguales a cero. Tratar de establecer a esta propiedad cualquier otra cosa, es simplemente ignorado (que podría, por supuesto, devolver un error). En el caso de la propiedad DataSessionID, el método Access trata de leer el valor actual de la propiedad, pero en su lugar, el número de la actual DataSession será retornado. Por lo tanto, la propiedad DataSessionID esta efectivamente oculta.

Creación de propiedades fuertemente tipificadas (Strongly Typed)

Un uso realmente práctico para un método Assign consiste en crear propiedades fuertemente tipificadas (strongly typed) a fin de que los errores no se produzcan en el código, porque una propiedad tiene un valor inadecuado. Esto puede reducir de manera efectiva la necesidad de comprobar los valores en repetidas ocasiones en el código que manejo el chequeo en el punto en que un valor es asignado. Si falla la prueba, la propiedad rechaza el valor. La metodología básica se muestra arriba, pero el código puede ser, y es por lo general, mucho más riguroso de lo está indicado allí.

Un escenario donde yo uso un método Assign es cuando se trata de almacenar un número de registro, un valor ID. Obviamente que no quiero un número inválido en este caso, por lo que utilizando un método Assign fuerza que el valor debe ser un número entero en un rango apropiado. Aquí está el código de una propiedad usada para almacenar el valor actual valor de un ID de usuario:
PROTECTED FUNCTION iUserID_Assign( tnValue )
  IF VARTYPE( tuInval ) <> 'N' OR EMPTY( tnValue )
    *** Not a number, ignore it!
    ASSERT .F. MESSAGE "User ID must be passed as an integer"
  ELSE
    IF NOT INT( tnValue ) == tnValue 
       *** Not an integer, ignore it
       ASSERT .F. MESSAGE "User ID must be passed as an integer"
    ELSE
      IF tnValue < 0
         *** Not 0 or higher, ignore it
         ASSERT .F. MESSAGE "User ID must be passed as positive integer"
      ELSE
        *** We may allow this one – check it
        SELECT userid FROM usertable WHERE userid = tnValue TO SCREEN NOCONSOLE
        IF _TALLY = 1
          *** This is a valid ID
          This.iUserID = tnValue
        ELSE
          *** Ignore it 
          ASSERT .F. MESSAGE "User ID must be passed as positive integer"
        ENDIF
      ENDIF
    ENDIF
  ENDIF
ENDFUNC

Observe el uso de "TO SCREEN NOCONSOLE" en la consulta SQL. Este es un viejo truco que suprime la salida de una consulta y es útil cuando queremos saber si existe un registro, o cuantos registros cumplen con el criterio, efectivamente esto es ejecutando un tipo de sentencia SQL que normalmente no se permite en VFP:

_TALLY = SELECT COUNT(*) FROM [source_table] WHERE [condition]

Ejecutando código con SETALL()

Otro uso para un método Assign es habilitar código que debe ejecutarse en varios objetos utilizando SetAll(). Este método, que existe en todas las clases contenedoreas de VFP, se utiliza para establecer a la misma propiedad, un valor específico a todos los objetos que tienen dicha propiedad. Si un objeto no tiene la propiedad en cuestión, la instrucción se ignora para ese objeto y no causa ningún error.
Hay algunas escenarios donde sería muy útil poder ejecutar algún código específico sobre todos los objetos de un formulario, o de algún contenedor, pero sólo si realmente se tiene el método especificado. Pero SetAll() sólo se aplica a las propiedades, no a los métodos, por lo que para ello tendría que necesitar un ciclo a través de la colección de objetos y el uso de PEMSTATUS() para determinar si cada objeto tiene el método pertinente. Si es así, llamar a éste y luego proceder con el siguiente objeto.

Una opción mucho más fácil es simplemente crear una propiedad con un método Assign en los objetos que llaman al método requerido desde adentro de Assign. Después de todo, no hay nada en las reglas de aplicación que diga que un método Assign deba realmente asignar un valor! Así que en cualquier momento que un valor se asigna a la propiedad, el método apropiado es llamado. Dado que podemos utilizar SetAll() para establecer propiedades, debemos evitar la necesidad de un ciclo a través de los objetos colleccion y pruebas: El código es extremadamente simple:

PROTECTED FUNCTION iRunCode_Assign( tnValue )
  This.UpdateSelf()
ENDFUNC

Pero en un formulario simplemente podemos tener:

ThisForm.SetAll( 'iRunCode', .T. )

Por lo tanto, cualquier objeto que tiene la propiedad iRunCode será llamado inmmediatamente por UpdateSelf() o cualquier otro método que lo requiera. Puede incluso crear esta propiedad y método Assign asociados como un método 'plantilla'. En otras palabras, crear la propiedad en su clase base, dejando, sin embargo el método Assign vacio. En un caso concreto puede llamar el método que quiera, simplemente suministrando el código necesario en el método Assign.

La única salvedad a esto es que usted no puede utilizar este método cuando una ejecución secuencial es necesaria, porque no hay ninguna forma para controlar, cuando usamos SetAll(), el orden en que los objetos ejecutarán su código. Sin embargo, aún puede establecer la propiedad explícitamente cuando necesite objetos para ejecutar su código en un orden específico, aunque en ese caso usted puede llamar los métodos directamente.

Un beneficio adicional del uso de métodos Assign para ejecutar código, es que el código que llama se pueden ocultar para objetos externos, o incluso desde sub clases, porque la propiedad en cuestión se define en el mismo nivel que el método en sí, y puede ejecutar código, incluso cuando el método no puede ser llamado directamente.

Creación de objetos dentro de un método Access

Un método Access se dispara cada vez que la propiedad se accede, por lo que si la propiedad se utiliza para tomar una referencia de un objeto, podemos usar el método Access para determinar si el objeto existe. Si el objeto existe, simplemente retornamos la referencia, de otro forma, podemos intentar crear el objeto sobre la marcha, y si se tiene éxito, entonces retornar referencia. ¿Por qué preocuparse?

Así se evita la necesidad de comprobar la propiedad que referencia a un objeto, para garantizar que el objeto existe. Aquí está el código del método Access de una clase formulario que utiliza nuestro objeto Data Manager.

PROTECTED FUNCTION oDM_Access
IF VARTYPE( This.oDM ) = "O"
  RETURN This.oDM
ELSE
  IF PEMSTATUS( _Screen, 'oDM', 5 ) AND VARTYPE( _Screen.oDM ) = 'O'
    *** There is an object reference out there already, grab it
    This.oDm = _Screen.oDM 
  ELSE
    *** Add the property (no error if already there...)
    ADDPROPERTY( _Screen, 'oDM', NULL )
    *** And instantiate the Data Manager
    SET PROCEDURE TO dataclass, dsetbase, datamgr ADDITIVE
    _Screen.oDM = CREATEOBJECT( 'xDatMgr', This.cDSNToUse )
    IF VARTYPE( _Screen.oDM ) = "O"
      This.oDM = _Screen.oDM
    ELSE
      This.oDM = NULL
    ENDIF
  ENDIF
ENDIF
RETURN This.oDM

Nuestra práctica general cuando trabajamos es utilizar una propiedad en el objeto _Screen de VFP para almacenar la referencia del objeto Data Manager. Por lo tanto, este código primero comprueba si el objeto existe, si existe, simplemente toma la referencia y la almacena en la propiedad. Si no hay Data Manager, este código añade la propiedad, incluso si la propiedad ya existe (esto no causará un error). Luego, se instancia el objeto Data Manager y se inicializa utilizando su propia información de conexión (la propiedad cDSNToUSe posee esto). Si tiene éxito la creación se retorna la referencia del objeto, de lo contrario NULL es retornado.

Al encapsular el proceso de instanciar el objeto de esta manera, podemos incluirlo en la clase base de nuestro formulario y olvidarnos de esto. Cualquier formulario basado en esta clase, tomará la referencia del objeto ya existente, o creará una nueva para todos los formularios a usar, sin necesidad de ningún código específico en el inicio de la aplicación.

Retornando otros valores cuando accedemos a una propiedad

Otro uso del método Access es retornar algo más que el valor actual que tiene una propiedad. Un caso donde usted puede querer hacer esto, es cuando una propiedad se utiliza para almacenar un valor de ID, pero lo que realmente necesitamos es consultar ese valor. Obviamente, esto puede hacerse en el código, pero si lo necesita con frecuencia, entonces tiene sentido tener un método Access como el siguiente:

PROTECTED FUNCTION nKeyCode_Access
LOCAL lcRetVal
  IF SEEK( This.Value, 'KeyTable', 'KeyCode' )
    lcRetVal = KeyTable.KeyDesc
  ELSE
    lcRetVal = ''
  ENDIF
  RETURN lcRetVal
ENDFUNC

Por extensión, la misma metodología podría ser utilizada para retornar un objeto con varios valores, por ejemplo, todo un registro de una tabla creada utilizando SCATTER NAME. Un código similar al de arriba que simplemente retorna un objeto de datos, o un NULL:

PROTECTED FUNCTION cDataKey_Access
LOCAL loRetVal
  IF SEEK( This.Value, 'KeyTable', 'KeyCode' )
    lnSel = SELECT()
    SELECT keytable
    SCATTER NAME loRetVal
    SELECT (lnSel)
  ELSE
    loRetVal = NULL
  ENDIF
  RETURN loRetVal
ENDFUNC

Hay muchos más casos en donde los métodos Access y Assign se pueden utilizar para simplificar el código y me interesaría conocer de cualquier otro uso que ha ya encontrado para estos métodos.


Comentarios

Rick Schummer me envió este comentario:
Andy,
Este es un gran envío. Me gusta especialmente el truco relacionado al SQL Select TO SCREEN NOCONSOLE para las consultas donde solo se busca contar los registro. Tengo conocimiento sobre esto por años, pero no puedo decir que lo he usado en mi código. Esto es un gran recuerdo para mí.
Yo uso la técnica, que usted ha mencionado, de crear el objeto dentro de los métodos Access todo el tiempo. Una cosa que necesita tener cuidado de cuando hacerlo, es referenciar con frecuencia al objeto en este código.


this.oBizObj.SetParameter ( "vp_cEmployee_PK", this.cEmployee_PK) 
this.oBizObj.Requery() 
loValues = this.oBizObj.GetValues() 

Este código incursiona tres veces a través en el código del método Access. La clave para mejorar el rendimiento es esto:


LOCAL loBiz 
loBiz = this.oBizObj 
loBiz.SetParameter("vp_cEmployee_PK", this.cEmployee_PK) 
loBiz.Requery() 
loValues = loBiz.GetValues() 
loBiz = .NULL.

Una de las técnicas que uso con frecuencia con los métodos Access y Assign es con las rutas y los nombres de los archivos. Las rutas con las que trabajo generalmente necesitan la barra invertida, y me gusta asegurarme la extensión del archivo que tienen por omisión. Antes de la llegada de los métodos Access / Assign en VFP, podría ver un código como este:


this.cFileName = ADDBS(this.cDirectory) + FORCEEXT(this.cFile, "txt") 

Actualmente uso el método Assign para añadir la barra a las propiedades cDirectory y forzar la extensión de cfile. Además, utilizo un método Acceso para cFileName.

PROTECTED FUNCTION cFileName_Access() 
  this.cFileName = this.cDirectory + this.cFile 
RETURN this.cFileName 

El código es generalmente sencillo. Gracias por este envío.
-- Rick



Rick,
El uso de una variable local para evitar la repetición de incursiones a través del método Acceso es un excelente punto, y sin duda vale la pena hacerlo, incluso cuando, como en mi ejemplo, todo el método Access es para retornar la referencia al objeto existente.

Una cosa más que voy a añadir aquí (cuando tenga unos minutos libres) es el código para crear una propiedad de sólo lectura propiedad que usa un método Assign para asegurar que el valor sólo se puede establecer cuando se ejecuta a partir de un determinado programa o método (por lo general, en el inicio del programa) y que hace caso omiso de todos los demás intentos de cambiar el valor.
-- Andy

No hay comentarios. :

Publicar un comentario