12 de septiembre de 2005

Alcance en Visual FoxPro - Parte 2

Artículo original: Scoping in Visual FoxPro - Part 2
http://weblogs.foxite.com/andykramek/archive/2005/05/13/446.aspx
Autor: Andy Kramek
Traducido por: Ana María Bisbé York

... continuación de "Alcance en Visual FoxPro - Parte 1"

Como he prometido, hoy continuaré nuestra revisión de temas relativos al alcance en Visual FoxPro con una mirada a cómo el alcance afecta las propiedades (y métodos) de los objetos. Creo que será más fácil pensar en las propiedades como "variables que están limitadas por los objetos". En otras palabras, mientras existan los objetos, existirán sus propiedades. Tan pronto como sea liberado el objeto, sus propiedades (y sus valores) se librarán también. En este sentido, al menos, cabe la analogía con las variables de memoria y su alcance (público, privado y local). Sin embargo, a pesar de la primera apariencia, no se crean por igual todas las propiedades.

VFP es algo inusual entre los lenguajes orientados a objeto en que todas sus propiedades, eventos y métodos (PEMs) de sus clases se crean de forma predeterminada como PUBLIC. Es importante entender que en el contexto de un objeto, la palabra "public" se refiere a la visibilidad de una propiedad (o método) de otro objeto u otros objetos. De hecho, hablando adecuadamente, el conjunto de PEMs que expone un objeto al mundo exterior es su "Interfaz pública". En muchos lenguajes orientados a objeto las propiedades, métodos y eventos nativos no se exponen en la interfaz pública y es el desarrollador quien se encarga de definir los que desea exponer.

VFP hace justamente lo contrario.  A menos que el desarrollador decida otra cosa, todas las PEMs nativas o de usuario, se exponen como parte de la interfaz pública de un objeto y tienen alcance público.

Esto, por supuesto, es de modo general, algo muy bueno, en una herramienta de desarrollo de aplicaciones como Visual FoxPro. Imagine qué tortura sería si cada vez que desee referirse a una propiedad de un objeto como un textbox (por ejemplo: Value, Background Color, Height, Width, Font) desde cualquier otro objeto, antes, tener que definir esta propiedad como "PUBLIC" o escribir un código en algún método especial del objeto en cuestión. Esto es, sin embargo, como ocurre en muchos otros lenguajes orientados a objeto. Vea las propiedades definidas en C# (o en una interfaz COM) y va a ver que existen dos métodos relacionados con ellas - un método "PUT" para asignar un valor y un método "GET" para devolver el valor. ¡ Contraste esto con la facilidad de crear y trabajar con propiedades en VFP !

Sin embargo, hay momentos en los que no deseamos que una propiedad (o método) específicos sean visibles a los objetos externos. ¿Porqué? Puede ser porque la propiedad es esencial en trabajos internos del objeto y no desea cambiarlos salvo  condiciones específicas y controladas o debido a que ese método debe ser llamado solamente como parte de, o en conjunto, con alguna otra operación. Por esta razón VFP implementa dos palabras reservadas adicionales que se pueden emplear para definir el alcance de PEMs.
  • PROTECTED - Las PEMs con alcance PROTECTED (protegidos) son solamente accesibles por los objetos que son instanciados desde una clase que define la PEM o desde una clase que herede desde la clase que lo define. Debido a que Visual FoxPro no implementa herencia múltiple, podemos definir brevemente: "son visibles solamente por la clase que la origina y sus subclases"
  • HIDDEN - Las PEMs con alcance HIDDEN (ocultos) son accesibles solamente por el objeto que los ha instanciado, directamente de la clase que define la PEMs. En otras palabras ellos son: "visibles solamente dentro de la clase original"
Pudiera preguntarse, en este punto, qué ocurre si trata de acceder a una PEM que esté protegida u oculta. La respuesta es sencilla, obtiene un error. Pegue el siguiente código en un PRG y ejecútelo:
oTest = CREATEOBJECT('xObj')
CLEAR
?
? "La siguiente línea va a causar un error - oprima Ignorar"
? "Accede a la propiedad directamente para ver el valor: " + oTest.cProperty
?
? "Llama al método Get y lee el valor: " + oTest.GetProperty()

DEFINE CLASS xObj AS Session
  cProperty = 'Esta propiedad no es visible'' 
  PROTECTED cProperty
  
  FUNCTION GetProperty()
    RETURN This.cProperty
  ENDFUNC
ENDDEFINE
Como puede ver, al declarar la propiedad protegida obtenemos un error " No existe la propiedad <nombre>" al tratar de acceder directamente desde fuera. Sin embargo, cuando llamamos al método GetProperty() - que es público - no hay problemas. Este método está definido en la clase que define la propiedad, y entonces puede ver la propiedad, y devolver su valor incluso si quien lo llama no puede ver la propiedad directamente. Por supuesto, el método pudo hacer mucho más que sencillamente devolver un valor. Los usos frecuentes para una propiedad protegida, con un método Get() que la exponga, incluyen cálculo secuencial, o valores acumulados (ejecutando totales). Cada vez que se llama al método, genera el siguiente número (o agrega algún valor) a la propiedad y devuelve el valor nuevo. El punto es que la UNICA vía para cambiar el valor desde fuera del objeto es llamar al método y que nos permita verificar que el valor se cambie sólo cuando se suponga que cambie. (contraste esto con hacer ThisForm.Text1.Value = "xxxxx").


El ejemplo 1 que se mostró antes, muestra cómo una propiedad de usuario puede ser definida como PROTECTED (o HIDDEN) al crear la definición de clase por código. Primero, declaramos el nombre de la propiedad, la inicializamos y luego definimos su alcance. (Esto se puede hacer dentro de la definición de clase; pero fuera de cualquier definición de método (Procedimiento o Función).

Para proteger u ocultar una propiedad nativa que ya ha sido declarada de forma predeterminada en la clase, solamente necesitamos incluirla en el comando PROTECTED o HIDDEN, de la siguiente forma:
DEFINE CLASS xObj AS session
  cProperty = "Esta no está visible"
  *** Ahora protegemos nuestra propiedad de usuario y una propiedad nativa
  PROTECTED cProperty, datasessionid
Los métodos también se pueden proteger u ocultar incluyendo simplemente la palabra clave adecuada como parte de la declaración. El ejemplo siguiente define un método oculto y uno protegido en una clase:
HIDDEN FUNCTION SetValue(tuInVal)
  <aquí va código>
ENDFUNC

PROTECTED PROCEDURE AnotherSet
  LPARAMETERS tuInVal
  <aquí va código>
ENDPROC
En realidad no importa si define sus métodos como funciones (aunque yo lo prefiero ya que los métodos siempre devuelven un valor y entonces, en realidad son "funciones") o como procedimientos. Tampoco importa si emplea definición implícita de parámetros (nuevamente, yo prefiero hacerlo así, en clases definidas por código, debido a que a que la presentación del método es parte del prototipo de la llamada) o explícitamente una definición de parámetros (local o privado).
Nota: Si está trabajando con clases visuales, entonces puede definir (t cambiar) el alcance para los PEMs en el diálogo "Modificar Propiedad / Método" al que se accede desde Menú - Clase.
Entonces, como hemos visto, definir una PEM como PROTECTED o HIDDEN, impide que se pueda acceder desde fuera del objeto, entonces en este sentido, no importa qué alcance utilice. De forma similar, si un objeto es instanciado directamente desde la clase que define la PEM, entonces, todas las PEMs son visibles para cualquier método en el objeto aunque sean públicas, privadas u ocultas. Sin embargo, cuando trabajan desde subclases la diferencia entre estos alcances se torna significativa.

El siguiente código define una clase ("propobj") con tres propiedades de usuario: una pública, una protegida y una oculta. El método público "SelfTest()" muestra simplemente los valores para cada una de las tres propiedades. Además, tenemos un par de métodos (un Get y un Set) relativos a cada una de las propiedades restringidas.
*** Clase que define tres propiedades
DEFINE CLASS propobj AS Session
  cPub = "Esta es una propiedad Pública"
  cPro = "Esta es una propiedad Protegida"
  cHid = "Esta es una propiedad Oculta"
  
  PROTECTED cPro
  HIDDEN cHid
  
  *** Función para la comprobación
  FUNCTION SelfTest
    ACTIVATE SCREEN
    ? This.Name
    ? This.cPub
    ? This.cPro
    ? This.cHid
    ?
    RETURN "Comprobación completada"
  ENDFUNC
  
  *** Método Get para devolver un valor protegido
  FUNCTION GetcPro
    RETURN This.cPro
  ENDFUNC
  
  *** Método Set para actualizar un valor protegido
  *** Vea que restringe los valores a cadenas de caracteres
  *** Cualquier otro tipo de dato es ignorado
  FUNCTION SetcPro(tcValue AS String)
    IF VARTYPE(tcValue) = "C"
      This.cPro = tcValue
    ENDIF
  ENDFUNC
  
  *** Método Get para devolver un valor oculto
  FUNCTION GetcHid
    RETURN This.cHid
  ENDFUNC
  
  *** Método Set para actualizar un valor oculto
  FUNCTION SetcHid(tcValue AS String)
    IF VARTYPE(tcValue) = "C"
      This.cHid = tcValue
    ENDIF
  ENDFUNC
ENDDEFINE
Si guarda este código y luego instancia un objeto basado en esta clase, y llama a su método SelfTest() verá el nombre del objeto, el contenido de las tres propiedades y el mensaje devuelto mostrado en pantalla:
SET PROCEDURE TO demo
oTest = CREATEOBJECT('propobj')
? oTest.SelfTest()

Muestra:

Propobj
Esta es una propiedad Pública
Esta es una propiedad Protegida
Esta es una propiedad Oculta
Comprobación completada


La clase siguiente define una subclase de propobj (llamada "SonOfProp")
DEFINE CLASS SonOfProp AS propobj
  *** Función para la comprobación
  FUNCTION TestSelf
    ACTIVATE SCREEN
    ? This.Name
    ? This.cPub
    ? This.cPro
    ? This.cHid
    ?
    RETURN "Comprobación completada"
  ENDFUNC
ENDDEFINE
Si repetimos la prueba anterior, sustituyendo SonOfProp por Propobj, aparentemente no hay diferencia. La subclase devuelve exactamente el mismo resultado que la padre (exceptuando, por supuesto, el nombre del objeto)

SonOfProp
Esta es una propiedad Pública
Esta es una propiedad Protegida
Esta es una propiedad Oculta
Comprobación completada


Esto no debe ser tan sorprendente, debido a que el código que se está ejecutando es el que se ha definido en la clase padre. Sin embargo, si llamamos al método "TestSelf", que ha sido definido en la subclase (aunque sea exactamente el mismo código que se definió en la clase padre) veremos inmediatamente una diferencia, de hecho, tendremos un error al tratar de acceder a una propiedad oculta, (porque el código que se está ejecutando no está definido en la misma clase que definió la propiedad), por tanto vamos a perder una línea al obtener el resultado.

SonOfProp
Esta es una propiedad Pública
Esta es una propiedad Protegida
Comprobación completada


Si sobreescribimos el método SelfTest en la subclase (es decir, re-escribimos el código exacto en la subclase) el resultado sería el mismo, ¡ un error ! Sin embargo, si sobre escribimos el método en la subclase con un código diferente, y utilizando un DODEFAULT() funciona bien, ya que, una vez más, el código que se está ejecutando está definido en la misma clase como una propiedad oculta.

Vea que los métodos GetHid() y SetHid() trabajan bien en la subclase, Estos métodos públicos se definen en la clase padre y pueden leer y escribir, la propiedad oculta no importa desde donde es llamada (vea además que en realidad evitan que la propiedad sea de otro tipo que no sea cadena de caracteres).

La clase final en esta pequeña demostración ("norelation") no está relacionada con la clase propobj. En su lugar, define sus propias propiedades públicas ("oRef") y en su método Init() crea una instancia de la clase "PropObj" y la asigna a esa propiedad.  Tiene también un método self-test que al ser llamado intenta acceder a las tres propiedades de su clase original. Pero ahora tenemos dos errores. La única propiedad que puede ser accedida directamente es aquella que fue definida como pública:
DEFINE CLASS NoRelation AS Session
  oRef = NULL && Public property to hold a reference;
    to the Property Definition Object
  FUNCTION Init
    This.oRef = CREATEOBJECT("propobj")
  ENDFUNC
  
  *** Función para verificar
  FUNCTION SelfTest
    ACTIVATE SCREEN
    ? This.Name
    ? This.oRef.cPub
    ? This.oRef.cPro
    ? This.oRef.cHid
    ?
    RETURN "Comprobación completada"
  ENDFUNC
ENDDEFINE
El resultado ahora es que tenemos dos líneas ausentes.

Norelation
Esta es una propiedad pública
Comprobación completada


Esto se debe, por supuesto, a que la clase en la que se basa el objeto no hereda de PropObj y no puede incluso acceder a las propiedades que están definidas como "Protected", deja solo las que son en realidad "Hidden". Sin embargo, vea que puede acceder a sus propiedades para llamar los métodos apropiados. This.oRef.GetcHid() y This.oRef.SetcHid() trabaja bien, y hace los métodos equivalentes de la propiedad protegida.

Todo esto es muy interesante, ¿es realmente útil?

Puede ser que se esté preguntando esto. La respuesta corta es: extremadamente útil. He mencionado dos razones para propiedades protegidas (secuencias, y las creaciones de propiedades "fuertemente tipadas"). Para métodos, uno de los mejores beneficios que veo es que reduce drásticamente el monto del código que necesito escribir. He aquí a lo que me refiero.

¿Con qué frecuencia incluye código en sus métodos para verificar que un parámetro se ha pasado correctamente? Si es un programador como yo, que estoy a la defensiva, la respuesta es "demasiado tiempo - y especialmente cuando se guarda el parámetro y se reutiliza en otro lugar". Al proteger los métodos que no necesita que sean llamados externamente, elimina la necesidad de verificar el paso de parámetros. Después de todo, el único lugar desde dónde se llama es desde dentro de su propio código, luego usted puede asegurarse (en todas las llamadas al método de que los valores que se pasan son correctos y se aceptan sin verificar nuevamente dentro del método.)

No hay comentarios. :

Publicar un comentario