Introducción a la POO en Visual FoxPro : Conceptos básicos (I)

Con este artículo empezamos una pequeña serie que nos acercará a la Programación Orientada a Objeto (POO) en Visual FoxPro. En este, el primero, haremos una descripción de los conceptos básicos sobre los que se sustenta la POO a fin de introducirnos en es ta forma de programar.

Si Vd. es nuevo en esto de la POO es posible que en algunos momentos se sienta perdido. No se preocupe, es normal. La orientación a objeto tiene un gran número de conceptos interrelacionados, por ello cuando describimos uno de forma separada parece carece r de sentido. No dude en releer el texto una y otra vez, de esa forma conseguirá tener una visión de conjunto.

Si por el contrario ya lleva algún tiempo aplicando estas técnicas lo que le interesa realmente es la implementación que de estás técnicas de programación hace Visual Foxpro (VFP), en parte podrá ver algunas de sus características en este artículo, pero e s realmente en los próximos donde se desarrollará toda la potencia de VFP. Hemos intentado en todo momento ser rigurosos, pero espero que disculpe que en algunas ocasiones se haga una descripción poco formal de las características de la POO a fin de facil itar la compresión.

Si ya conoce algunas de las características de VFP sabrá que es posible realizar POO casi sin darse cuenta, pero en este artículo haremos hincapié en los entresijos de este tipo de programación desde la codificación más tradicional, a fin de saber perfect amente lo que estamos haciendo en cada momento. En próximos artículos describiremos la POO cuando utilizamos Form Designer, el generador de pantallas, o Class Designer, la herramienta visual para definición de clases, así como el manejo de herramientas de apoyo como el Class Browser.

No es tan difícil

Está de moda hablar de POO, y no es nuevo este concepto, pero es ahora cuando se está generalizando su uso. Esta moda a provocado alguna confusión, muchas herramientas han dicho estar orientadas a objeto sin serlo, otras han realizado implementaciones muy extrañas, y algunas, como VFP, han sabido mantener un equilibrio y una buena implementación, por lo que son verdaderamente recomendables para desarrollar con este nuevo sistema de programación.

Quizás uno de los problemas a la hora de afrontar por primera vez la POO, estriba en el temor que puede producir tantos y tantos nuevos conceptos que se ciernen sobre nosotros. Lo cierto es que la POO no es tan fiera como la pintan, pero en la medida que cambia algo nuestra forma de pensar los programas, sus componentes y el modo que se relacionan entre si, requiere un cierto esfuerzo.

Los que llevan años en esto de la programación recuerdan lo difícil y extraño que fue el paso de la programación lineal (con goto) a la programación estructurada (con call), ahora toca el paso de la programación estructurada a la POO. La POO viene a comp letar algunas de las lagunas de la programación estructurada, como esta vino a solucionar algunos de los problemas de la programación lineal. No es la soluciona a todos nuestros problemas, pero sí facilita nuestra labor de programación y mantenimiento.

Objetivos de la POO

La POO intenta ser un mejor sistema para el desarrollo de aplicaciones. Como toda técnica de programación si se hace mal puede ser desastrosa -pensemos los líos que se pueden provocar si aplicamos mal la programación estructurada- pero es un mejor sistema de desarrollo.

Una de las primeras mejoras que obtenemos por el uso de la POO es el permitir afrontar programas más complejos y de mayor tamaño con menos esfuerzo. El hecho de trabajar con pequeños elementos bien definidos, como son los objetos, nos permite aisla r cada componente de la aplicación del resto y de esa forma aprovechar en mayor medida nuestro esfuerzo.

Una vez adiestrados en las nuevas técnicas de orientación a objeto obtendremos también una mejora considerable en nuestro rendimiento de desarrollo. Las grandes facilidades para el reaprovechamiento del código que nos ofrece la orientación a objeto harán que desarrollemos con mayor velocidad, pero manteniendo unos buenos niveles de calidad.

La reutilización de código en la POO nos otorga una gran flexibilidad. La existencia de la herencia permite modificar las características que necesitemos de una clase de objeto con seguridad de no alterar las especificaciones del mismo y aprovechan do todo el desarrollo realizado en el mismo.

El mantenimiento de aplicaciones se ha visto como uno de los grandes problemas de la programación actual. Con las técnicas de POO es más sencillo realizar este mantenimiento. Los objetos son elementos de pequeño tamaño, bien definidos, y por lo tan to más fáciles de mantener. Además la existencia de la herencia nos va a asegurar que la modificación de algunas características de la clase no van a afectar a los desarrollos ya terminados.

La definición correcta de los objetos permitirá tener una mejor estructuración de nuestros programas. Con la POO se tiene un acercamiento más natural a los problemas y por lo tanto los análisis de aplicaciones orientadas a objeto tienen un acercami ento a la realidad mucho más completa que con la programación estructurada. Esto no quiere decir que al principio no cueste un poco realizar este tipo de análisis, pero una vez adquirida las nuevas técnicas, es mucho más sencillo.

La POO es muy fácilmente compresible en entornos gráficos, pues el hecho de que estos entornos manejen objetos gráficos hace muy recomendable este tipo de programación. Pero debemos tener claro que es totalmente posible realizar POO en entornos de tipo ca rácter y es posible programar entornos gráficos sin POO.

No todo son ventajas.

Tampoco podemos decir que todo sean facilidades. Por una parte la POO nos va ha obligar a cambiar nuestra forma de pensar los programas y por lo tanto es necesario un tiempo para que las nuevas técnicas se nos hagan habituales y automáticamente nos salga programar de esta forma.

No basta con dominar estas técnicas para que alcancemos las mejores cotas de productividad, posiblemente sea necesario elaborarnos unas buenas librerías de clases. Para ello es conveniente desarrollar un par de aplicaciones bajo esta técnicas para darnos cuenta de que es lo que realmente es factible de ser reutilizado y de esa forma organizar nuestras propias librerías de clases.

La depuración de código orientado a objeto es algo más compleja que la depuración de código estructurado. Esto no quiere decir que nuestro código va a ser peor o va ha tener más errores, pero sí es cierto que en el caso de producirse un error deberemos re correr todo el árbol de herencia para encontrarlo, algo que en programación estructurada no tenemos que hacer.

Pero en general, podemos decir que los inconvenientes son realmente menores que las ventajas, y por lo tanto es realmente recomendable el desarrollo con POO.

Elementos de la POO

Vamos a hacer un repaso de los distintos elementos que componen la POO, y como cada uno de ellos es implementado en VFP. En algunos momentos es posible que no entienda algunos de los conceptos, no se preocupe, poco a poco intentaremos aclarar todo est e entramado de conceptos y sintaxis. Clase y Objeto. Son los dos primeros elementos de la POO. Se puede decir que la clase es la generalización de los objetos y los objetos son la concreción de la clase, aun cuando parezca un galimatías.

Se suelen poner ejemplos bastante filosóficos para describir la relación entre el objeto y la clase (variando según sea el autor idealista o empirista). Podemos decir que existen un objeto Pedro, otro Antonio y otro Luisa. Todos estos objetos tienen eleme ntos en común y por ello decimos que son de la clase persona. De la misma forma otros dicen que tenemos una idea clara de lo que es ser una persona y luego somos capaces de distinguir hombres concretos.

Para poner otro tipo de ejemplo podemos observar los botones de un entorno gráfico. Todos sabemos como es un botón en un entorno gráfico, si bien cada botón es diferente de los demás, todos se parecen y por lo tanto podemos decir que la clase botón es la generalización de las propiedades y comportamientos de todos los botones de los entorno gráficos.

Definir una clase. Las clases son la descripción de los elementos comunes de los objetos que generalizan. Así las clases se definen y pueden ser usadas para crear innumerables objetos de este tipo. Para definir una clase utilizaremos una sencilla s intaxis de VFP. Vamos a empezar definiendo una clase denominada Persona. Es una clase vacía, pero nos sirve para comenzar :

	DEFINE CLASS Persona AS CUSTOM
	ENDDEFINE

Para poder utilizar esta definición debemos incluirla en un fichero .PRG y cargado con SET PROCEDURE TO o bien incluirla al final de nuestro fichero .PRG.

Crear un objeto. Ya podemos crear objetos basado en esta clase, para ello utilizamos la siguiente expresión :

	oPersona1 = CREATEOBJECT( "Persona" )
	oPersona2 = CREATEOBJECT( "Persona" )

Debemos tener clara la diferencia entre clase y objeto. La clase es una plantilla, donde definimos las características de cada uno de los objetos. Los objetos que hemos creado, oPersona1 y oPersona2, comparten la plantilla que con la que se han creado, la clase Persona, pero son diferentes entre si.

Borrar un objeto. Los objetos se asemejan a las variables en cuanto a que pueden declararse como LOCAL, PRIVATE o PUBLIC. Esta es la única similitud que tienen las variables y lo s objetos, pero es una característica muy importante.

Por defecto, los objetos son de tipo PRIVATE y por lo tanto existirán mientras se ejecute el programa que los creó. Una vez salgamos de este programa el objeto se borrará automáticamente. El objeto puede ser usado en el programa que lo creó y en todos lo programas llamados desde él.

Si declaramos el objeto como LOCAL el objeto persistirá hasta la salida del programa que lo creó, pero los programas que sean llamados desde el programa de creación no podrán hacer uso de este objeto, pues permanece oculto par a ellos, evitando así posibles problemas con los nombres de los objetos.

Al declarar un objeto como PUBLIC estamos indicando que permanezca hasta que lo borremos explícitamente o salgamos de VFP. Este objeto podrá ser utilizado por cualquier programa desde el momento que es creado hasta que sea bor rado.

Para borrar un objeto de forma explícita debemos hacer uso del comando RELEASE. Podemos borrar de forma explícita no solo los objetos públicos, sino también los locales y privados.

Veamos un ejemplo muy simple de creación de un objeto público y su destrucción :

	PUBLIC oPrueba
	oPrueba = CREATEOBJECT( "Persona" )
	...
	RELEASE oPrueba

Encapsulación. Aunque parece un termino extraño, es muy habitual en POO. Hace referencia a la capacidad de los objetos para incluir dentro de si tanto datos como acciones. Las clases de distinguen unas de otras justamente por tener unos datos y acciones que las diferencian. Los objetos de una misma clase se diferencian entre si por tener datos diferentes.

Los datos que caracterizan a una clase se denominan propiedades y sus acciones (o programas) se denominan métodos. Se dice por lo tanto que la clase encapsula métodos y propiedades, es decir, que agrupa dentro si tanto métodos como propiedades.

Las propiedades y métodos de una clase se denominan habitualmente propiedades y métodos miembro.

Veamos poco a poco estos nuevos conceptos.

Propiedades. Como hemos dicho, las propiedades son los datos que manejan las clases. Estas propiedades se declaran en la definición de la clase y permanecen en todo momento asociados a los objetos creados bajo esa clase.

Para verlo con más claridad vamos a dar contenido a esta definición de la clase Persona que iniciamos hace un momento. Para ello debemos estudiar que propiedades posee este tipo de objeto. Podemos decir que todas la personas tienen un nombre, uno s apellidos y una fecha de nacimiento. Hay muchas otras propiedades para una clase de este tipo, pero empecemos con estas. La implementación en VFP se haría de la siguiente manera :

	DEFINE CLASS persona AS CUSTOM
	  cNombre = ""
	  cApellidos = ""
	  dFechaNacimiento = {}
	ENDDEFINE

En nuestra definición de clase declaramos las propiedades con unos valores iniciales, que pueden ser de cualquiera de los tipos de datos definidos en VFP. Por ejemplo, cNombre y cApellidos los hemos inicializado como una cadena vacía y dFechaNacimiento como una fecha también vacía, pero pueden ser numéricos, datetime o de cualquier otro tipo.

Tambien podríamos haber definido las propiedades con cualquier otro tipo de valor por defecto : una texto o una fecha en concreto, de esta forma, al crear un objeto ya tendría este valor la propiedad.

Por ejemplo podríamos ampliar la definición de la clase incluyendo una propiedad denominada cEstadoCivil que por defecto fuera la cadena Soltero.

	DEFINE CLASS persona AS CUSTOM
	  cNombre = ""
	  cApellidos = ""
	  dFechaNacimiento = {}
	  cEstadoCivil = "Soltero"
	ENDDEFINE

Una vez creado un objeto, si quedemos dar valores a cada una de sus propiedades haremos uso de operador punto. Para ello pondremos el nombre del objeto, un punto y el nombre de la propiedad :

	oPersona1 = CREATEOBJECT( "persona" )
	oPersona1.cNombre = "María"
	oPersona1.cApellidos = "Pérez González"
	oPersona1.dFechaNacimiento = {20-10-75}

	oPersona2 = CREATEOBJECT( "persona" )
	oPersona2.cNombre = "Pedro"
	oPersona1.cApellidos = "Jiménez Nieto "
	oPersona2.dFechaNacimiento = {04-12-69}
	oPersona2.cEstadoCivil = "Casado"

Si, como en este caso, vamos a dar valores a muchas propiedades de un objeto podemos utilizar la sintaxis abreviada, de la forma siguiente :

	WITH oPersona2
	  .cNombre = "Pedro"
	  .cApellidos = "Jiménez Nieto "
	  .dFechaNacimiento =  {04-12-69}
	  .cEstadoCivil = "Casado"
	ENDWITH

Desde ese momento podemos hacer uso de estas propiedades, usando también el operador punto:

	WAIT WIND oPersona1.cNombre
	WAIT WIND oPersona2.cNombre

Como decíamos, las propiedades están asociadas a cada objeto, de esta forma el valor de la propiedad cNombre es diferente entre los objetos oPersona1 y oPersona2, aun cuando en la definición de la clase hubiéramos otorgado un va lor por defecto a esta propiedad. Es importante tener clara esta diferencia. Las propiedades se declaran en la definición de la clase, pero los valores de las propiedades pueden ser diferentes para cada uno de los objetos de esta clase.

Métodos. El otro elemento característico de una clase son los métodos. Los métodos son acciones que pueden realizar los objetos, es decir, son funciones o procedimientos asociados a este tipo objeto.

En el caso de las personas podemos decir que pueden nacer, morir, casarse, tener hijos, etc... Para dar un primer ejemplo de esto veamos uno de sus métodos :

	DEFINE CLASS persona AS CUSTOM

	  cNombre = "" 
	  cApellidos = "" 
	  dFechaNacimiento = {} 
	  cEstadoCivil = "Soltero" 

	  PROCEDURE Nacer 
	  LPARAMETER cNombre, ;
	             cApellidos, ;
	             dFecha
	    This.cNombre = cNombre 
	    This.cApellidos = cApellidos 
	    This.dFechaNacimiento = dFecha 
	  ENDPROC

	ENDDEFINE

En este método damos valor a tres propiedades del objetos a partir de los parámetros que se nos han pasado. Quizás sea todavía poco evidente este código, pero poco a poco iremos entendiendolo.

En primer lugar debemos diferenciar entre los parámetros de este método y las propiedades. Los parámetros son variables y se perderán al finalizar la ejecución del método. Las propiedades, que son la que empiezan con la sintaxis This., permanecen mientras dure la existencia del objeto.

Por otra parte, este procedimiento, denominado Nacer, se diferencia de los procedimientos que estamos acostumbrados a escribir en que sólo es llamable asociado a un objeto de la clase Persona y no puede ser invocado de forma independient e. Esta es una de las grandes diferencias entre la programación estructurada y la POO.

Mensajes. Cuando llamamos a un método de un objeto se dice que estamos enviando un mensaje al objeto para que realice una determinada acción. Así cuando enviamos un mensaje de nacer a un objeto persona estamos ejecutando el método correspondiente :

	oPersona1 = CREATEOBJECT( "Persona" )
	oPersona1.Nacer( "María", ; 
	                 "Pérez González", ;
	                 {20-10-75} )
	? oPersona1.cNombre 
	? oPersona1.cApellidos

Operador This.

Vemos dentro del código del método Nacer que hemos utilizado una extraña sintaxis, algo como THIS. Decíamos al principio que la clase es una plantilla y cuando definimos un método dentro de esta plantill a que es la clase no sabemos cual será el nombre del objeto que utilizará este método, por eso, cuando vamos a utilizar una propiedad o un método de la clase, debemos anteponer al operador punto el operador This, para indicar que se trataran las propiedades del objeto que recibe el mensaje, es decir, que ha sido invocado, y no para otro.

Deciamos que las propiedades mantienen valores diferentes para cada uno de los objetos, pero los métodos comparten su código entre todos los objetos de una clase. Un método varía en la medida que las propiedades del objeto que lo llama son diferentes, por ello es tan importante el operador This.

De esta forma cuando ejecutamos:

	oPersona1.Nacer( "María", ;
	                 "Pérez González", ;
	                 {20-10-75} )

el código del método Nacer asocia el primer parámetro, denominado cNombre, a la propiedad cNombre del objeto que recibe el mensaje. Así This.cNombre es una propiedad del objeto y cNombre es una variable que cor responde al primer parámetro del método invocado en el mensaje.

	PROCEDURE Nacer
	LPARAMAMETER cNombre, ;
	             cApellidos, ;
	             cFecha

	  This.cNombre = cNombre
	  ...

	ENDPROC

Ocultación. Una de las mejoras que implementa la POO, es la posibilidad de limitar el acceso a determinadas propiedades o métodos. Con ello conseguimos que la utilización del la clase se haga de forma ordenada.

Las propiedades o métodos protegidos sólo son utilizables desde los métodos pertenecientes a esta clase y no pueden usarse directamente por otros programas. Vamos con un ejemplo :

	DEFINE CLASS Nivel AS CUSTOM

	  PROTECTED nContador
	  nContador = 0

	  PROCEDURE Mas
	  LPARAMETER nCantidad
	    This.nContador = This.nContador ;
	                       + nCantidad
	  ENDPROC

	  PROCEDURE Menos
	  LPARAMETER nCantidad
	    This.nContador = This.nContador ;
	                     - nCantidad
	  ENDPROC

	  PROCEDURE Ver
	    RETURN This.nContador
	  ENDPROC

	ENDDEFINE

Esta clase define un interface por medio de los métodos Mas, Menos y Ver. Si intentamos modificar el valor de nContador de forma directa nos dará un error, pues esta propiedad está protegida.

	oTemperatura = CREATEOBJECT( "Nivel" )

	oTemperatura.Mas (  10 )
	? oTemperatura.Ver()
	oTemperatura.Menos ( 3 )
	? oTemperatura.Ver()

	* Proboca el Error :
	* Property NCONTADOR is not found.
	oTemperatura.nContador = 100

Al igual que podemos proteger propiedades, podemos proteger métodos. De esta forma podemos definir métodos que sólo sean usado por otros métodos de la clase y no puedan ser invocados a partir de los objetos de esta clase. Para ello basta colocar la cláus ula PROTECTED antes de PROCEDURE.

Polimorfismo. Cuando realizamos programación estructurada debemos tener cuidado de no llamar con el mismo nombre a dos variables o a dos procedimientos, sin embargo en la POO podemos llamar a un método o a una propiedad de una clase de igual forma que un método o propiedad de otra. Esta característica es lo que se denomina polimorfismo. Veamos un ejemplo con propiedades :

	DEFINE CLASS ClaseUno AS CUSTOM
	  Dato = 1000
	ENDDEFINE

	DEFINE CLASS ClaseDos AS CUSTOM
	  Dato = "Hola"
	ENDDEFINE

Esto, que puede parecer un problema, es muy sencillo de entender si vemos como los utilizamos los dos objetos :

	Objeto1 = CREATEOBJECT( "ClaseUno" )
	Objeto2 = CREATEOBJECT( "ClaseDos" )

	? Objeto1.Dato
	? Objeto2.Dato

Aun cuando en los dos objetos llaman a una propiedad denominada Dato, en el primer caso estamos llamando a una propiedad tipo numérica definida en ClaseUno, y el segundo caso estamos llamado a una propiedad de tipo carácter definida en ClaseDos.

Igual que las propiedades, en el caso de los métodos también es posible el polimorfismo. De esta forma podemos definir dos métodos denominados Imprimir, pero que cada uno hace una cosa bien distinta :

	DEFINE CLASS ClaseUno AS CUSTOM
	  PROCEDURE Imprimir
	    ? "Hola esto es una prueba ..."
	  ENDPROC
	ENDDEFINE

	DEFINE CLASS ClaseDos AS CUSTOM
	  PROCEDURE Imprimir
	    LIST STATUS
	  ENDPROC
	ENDDEFINE

En VFP no es posible definir dos métodos o dos propiedades con igual nombre dentro de una misma clase.

Eventos. Existe una serie de métodos especiales, que normalmente no se ejecutan por ser invocados de forma explícita, como los que hemos definido hasta ahora, sino que por denominarse de una forma determinada son lanzados cuando 'pasa algo', es dec ir, cuando se produce un evento. Estos eventos pueden ser un click, el movimiento del ratón, un pulsación de tecla, etc..

Los dos primeros eventos que vamos a tratar son los denominados Init y Destroy. El primero se ejecuta cuando se crea un objeto de esta clase y el segundo cuando se destruye. En otros lenguajes orientados a objeto estos métodos son denomi nados el constructor y el destructor de la clase, pero el VFP son tratados como eventos. Los eventos Init y Destroy los encontramos en absolutamente todos los objetos de VFP. Veamos como hacer uso de esta característica creando dos métod os con estos nombres :

	* Lanzamiento automático del
	* método INIT
	oPrueba = CREATEOBJECT( "Eventos" )
	...
	* Lanzamiento automático del
	* método DESTROY
	RELEASE oPrueba

	DEFINE CLASS Eventos AS CUSTOM
	  PROCEDURE Init
	    ? "Creación del objeto ..."
	  ENDPROC

	  PROCEDURE Destroy 
	    ? "Destrucción del objeto ..." 
	  ENDPROC 
	ENDDEFINE 

Cada tipo clase tiene unos eventos predefinidos y que no podemos añadir más. Así, por ejemplo, la clase CUSTOM tiene definidos los eventos Init, Destroy y Error, la clase COMMANDBUTTOM los eventos Click, Destroy, DragDrop, DragOver, Error, ErrorMessage, GotFocus, Init, InteractiveChange, KeyPress, LostFocus, Message, MouseDown, MouseMove, MouseUp, RightClick, UIEnable, Valid, When, etc.. La mayoría de estos eventos los veremos en próximos artículos de forma detallada. De momento quedémonos con el concepto de evento.

Objetos como propiedades. Existe la posibilidad, en algunas clases, de definir objetos como propiedades miembro. Por ejemplo, podemos querer definir una clase matrimonio con dos objetos tipo persona. Así definiríamos :

	DEFINE CLASS Matrimonio AS CUSTOM
	  dFecha = {}
	  ADD OBJECT Conyuge1 AS Persona
	  ADD OBJECT Conyuge2 AS Persona
	ENDDEFINE

Para poder hacer uso de ellos no tenemos más que indicar el nombre del objeto miembro. Por ejemplo :

	oPareja1 = CREATEOBJECT( "Matrimonio" )
	oPareja1.Conyuge1.Nombre = "María"
	oPareja2.Conyuge2.Nombre = "Pedro"

No todas las clases admiten que sean definidos objetos como miembros de la misma, sólo las clases denominadas contenedoras, entre la que se encuentra CUSTOM.

Continua....